mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-02 22:18:56 -07:00
Merge remote-tracking branch 'mntm/dev' into kiisu-mntm
This commit is contained in:
+25
-2
@@ -1,14 +1,37 @@
|
||||
### Added:
|
||||
- Nothing
|
||||
- SubGHz:
|
||||
- UL: Roger (static 28 bit) with add manually support (by @xMasterX & @mishamyte)
|
||||
- UL: V2 Phoenix full support (button switch, add manually, counter decrypt/encrypt) (by @xMasterX & @RocketGod-git, original code by @Skorpionm)
|
||||
- UL: Add Keeloq support for - Motorline (with add manually support), Rosh, Pecinin, Rossi, Merlin, Steelmate (by @xMasterX & @RocketGod-git)
|
||||
- UL: Nero Radio static parse and display more data (by @xMasterX)
|
||||
- UL: Marantec protocol implement CRC verification display and add manually support (by @xMasterX & @li0ard, original code by @Skorpionm)
|
||||
- UL: Keeloq Comunello add manually support (by @xMasterX)
|
||||
- RFID: Support writing Securakey, Jablotron and FDX-B to EM4305 cards (#434 by @jamisonderek)
|
||||
- BT Remote: Add Rename Option, simplify Bad KB BLE profile (#439 by @aaronjamt & @WillyJL)
|
||||
- MNTM Settings:
|
||||
- Add Main Menu support for directories and generic files (including JS files) (#331 by @956MB & @WillyJL)
|
||||
- Add Skip Sliding Animations option for Lockscreen (#436 by @aaronjamt)
|
||||
- Desktop: Add Keybinds support for directories (#331 by @956MB & @WillyJL)
|
||||
- Input Settings: Add Vibro Trigger option (#429 by @956MB)
|
||||
|
||||
### Updated:
|
||||
- Apps:
|
||||
- Combo Cracker: Allow press and hold to change values (by @TAxelAnderson)
|
||||
- Asteroids: Bugfixes, title screen, Drone Buddy power-up (by @SimplyMinimal)
|
||||
- Combo Cracker: Allow press and hold to change values, add tutorial (by @TAxelAnderson)
|
||||
- FlipDownloader: Added a new option to download GitHub repositories (by @jblanked)
|
||||
- Flipper Blackhat: Add Deauth Broadcast command (by @o7-machinehum)
|
||||
- KeyCopier: Added Weiser WR3 key format (by @lightos)
|
||||
- NFC Playlist: Refactor playlist worker, new settings layout, loop setting, controls to move between items (by @acegoal07)
|
||||
- Sentry Safe: New interface, settings & help page (by @H4ckd4ddy)
|
||||
- Sub-GHz:
|
||||
- UL: Add 868.46 MHz to default subghz freqs list (by @xMasterX)
|
||||
- UL: Reduce less popular freqs in default hopper preset, make it faster (by @xMasterX)
|
||||
- UL: Docs: Update Sub-GHz DoorHan programming instructions (by @li0ard)
|
||||
|
||||
### Fixed:
|
||||
- Bad KB: Fix modifier keys with HOLD/RELEASE commands (by @WillyJL)
|
||||
- Desktop: Fix lock screen hang (#438 by @aaronjamt)
|
||||
- NFC: Fix incorrect Saflok year formula (#433 by @Eltrick)
|
||||
|
||||
### Removed:
|
||||
- Nothing
|
||||
|
||||
+1
-1
Submodule applications/external updated: c8336cab2f...65ff6249aa
@@ -1,4 +1,5 @@
|
||||
#include "archive_i.h"
|
||||
#include "helpers/archive_browser.h"
|
||||
|
||||
static bool archive_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
@@ -136,11 +137,25 @@ void archive_show_loading_popup(ArchiveApp* context, bool show) {
|
||||
}
|
||||
|
||||
int32_t archive_app(void* p) {
|
||||
UNUSED(p);
|
||||
FuriString* path = (FuriString*)p;
|
||||
|
||||
ArchiveApp* archive = archive_alloc();
|
||||
view_dispatcher_attach_to_gui(
|
||||
archive->view_dispatcher, archive->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// If we are sent a path from context, set it in the browser
|
||||
if(path && !furi_string_empty(path)) {
|
||||
archive_set_tab(archive->browser, ArchiveTabBrowser);
|
||||
furi_string_set(archive->browser->path, path);
|
||||
archive->browser->is_root = false;
|
||||
archive_file_browser_set_path(
|
||||
archive->browser,
|
||||
archive->browser->path,
|
||||
archive_get_tab_ext(ArchiveTabBrowser),
|
||||
false,
|
||||
!momentum_settings.show_hidden_files);
|
||||
}
|
||||
|
||||
scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneBrowser);
|
||||
view_dispatcher_run(archive->view_dispatcher);
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ static void archive_long_load_cb(void* context) {
|
||||
browser->view, ArchiveBrowserViewModel * model, { model->folder_loading = true; }, true);
|
||||
}
|
||||
|
||||
static void archive_file_browser_set_path(
|
||||
void archive_file_browser_set_path(
|
||||
ArchiveBrowserView* browser,
|
||||
FuriString* path,
|
||||
const char* filter_ext,
|
||||
|
||||
@@ -80,6 +80,12 @@ inline bool archive_is_known_app(ArchiveFileTypeEnum type) {
|
||||
return type < ArchiveFileTypeUnknown;
|
||||
}
|
||||
|
||||
void archive_file_browser_set_path(
|
||||
ArchiveBrowserView* browser,
|
||||
FuriString* path,
|
||||
const char* filter_ext,
|
||||
bool skip_assets,
|
||||
bool hide_dot_files);
|
||||
bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx);
|
||||
bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model);
|
||||
void archive_update_offset(ArchiveBrowserView* browser);
|
||||
@@ -104,6 +110,7 @@ void archive_add_file_item(ArchiveBrowserView* browser, bool is_folder, const ch
|
||||
void archive_show_file_menu(ArchiveBrowserView* browser, bool show, bool manage);
|
||||
void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active);
|
||||
|
||||
void archive_set_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab);
|
||||
void archive_switch_tab(ArchiveBrowserView* browser, InputKey key);
|
||||
void archive_enter_dir(ArchiveBrowserView* browser, FuriString* name);
|
||||
void archive_leave_dir(ArchiveBrowserView* browser);
|
||||
|
||||
@@ -15,6 +15,8 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder
|
||||
file->is_app = is_app;
|
||||
if(is_app) {
|
||||
file->type = archive_get_app_filetype(archive_get_app_type(path));
|
||||
} else if(is_folder) {
|
||||
file->type = ArchiveFileTypeFolder;
|
||||
} else {
|
||||
for(size_t i = 0; i < COUNT_OF(known_ext); i++) {
|
||||
if((known_ext[i][0] == '?') || (known_ext[i][0] == '*')) continue;
|
||||
@@ -53,11 +55,7 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder
|
||||
}
|
||||
}
|
||||
|
||||
if(is_folder) {
|
||||
file->type = ArchiveFileTypeFolder;
|
||||
} else {
|
||||
file->type = ArchiveFileTypeUnknown;
|
||||
}
|
||||
file->type = ArchiveFileTypeUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "../views/archive_browser_view.h"
|
||||
#include "archive/scenes/archive_scene.h"
|
||||
|
||||
#include <desktop/desktop_i.h>
|
||||
|
||||
#define TAG "ArchiveSceneBrowser"
|
||||
|
||||
#define SCENE_STATE_DEFAULT (0)
|
||||
@@ -182,6 +184,12 @@ static void
|
||||
}
|
||||
} else if(selected->type == ArchiveFileTypeApplication) {
|
||||
loader_start_detached_with_gui_error(loader, furi_string_get_cstr(selected->path), NULL);
|
||||
} else if(selected->type == ArchiveFileTypeFolder) {
|
||||
// Folders are handled by archive, so we should only get here with run_with_default_app() outside archive
|
||||
furi_check(browser == NULL, "What you doin?");
|
||||
Desktop* desktop = furi_record_open(RECORD_DESKTOP);
|
||||
desktop_launch_archive(desktop, furi_string_get_cstr(selected->path));
|
||||
furi_record_close(RECORD_DESKTOP);
|
||||
} else {
|
||||
archive_show_file(loader, furi_string_get_cstr(selected->path));
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ App(
|
||||
icon="A_BadUsb_14",
|
||||
order=70,
|
||||
resources="resources",
|
||||
fap_libs=["assets"],
|
||||
fap_libs=["assets", "ble_profile"],
|
||||
fap_icon="icon.png",
|
||||
fap_category="Tools",
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "bad_usb_hid.h"
|
||||
#include "ble_hid_profile.h"
|
||||
#include "ble_hid_ext_profile.h"
|
||||
#include <bt/bt_service/bt.h>
|
||||
#include <bt/bt_service/bt_i.h>
|
||||
#include <storage/storage.h>
|
||||
@@ -173,7 +173,7 @@ void* hid_ble_init(BadUsbHidConfig* hid_cfg) {
|
||||
bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME));
|
||||
|
||||
hid_ble_adjust_config(hid_cfg);
|
||||
ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, &hid_cfg->ble);
|
||||
ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid_ext, &hid_cfg->ble);
|
||||
furi_check(ble_hid->profile);
|
||||
|
||||
furi_hal_bt_start_advertising();
|
||||
|
||||
@@ -7,7 +7,7 @@ extern "C" {
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include "ble_hid_profile.h"
|
||||
#include "ble_hid_ext_profile.h"
|
||||
|
||||
typedef enum {
|
||||
BadUsbHidInterfaceUsb,
|
||||
@@ -16,7 +16,7 @@ typedef enum {
|
||||
} BadUsbHidInterface;
|
||||
|
||||
typedef struct {
|
||||
BleProfileHidParams ble;
|
||||
BleProfileHidExtParams ble;
|
||||
FuriHalUsbHidConfig usb;
|
||||
} BadUsbHidConfig;
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "ble_hid_ext_profile.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
static FuriHalBleProfileBase* ble_profile_hid_ext_start(FuriHalBleProfileParams profile_params) {
|
||||
UNUSED(profile_params);
|
||||
|
||||
return ble_profile_hid->start(NULL);
|
||||
}
|
||||
|
||||
static void ble_profile_hid_ext_stop(FuriHalBleProfileBase* profile) {
|
||||
ble_profile_hid->stop(profile);
|
||||
}
|
||||
|
||||
static void
|
||||
ble_profile_hid_ext_get_config(GapConfig* config, FuriHalBleProfileParams profile_params) {
|
||||
furi_check(config);
|
||||
furi_check(profile_params);
|
||||
BleProfileHidExtParams* hid_ext_profile_params = profile_params;
|
||||
|
||||
// Setup config with basic profile
|
||||
ble_profile_hid->get_gap_config(config, NULL);
|
||||
|
||||
// Set MAC address
|
||||
memcpy(config->mac_address, hid_ext_profile_params->mac, sizeof(config->mac_address));
|
||||
|
||||
// Set advertise name (skip first byte which is the ADV type)
|
||||
strlcpy(config->adv_name + 1, hid_ext_profile_params->name, sizeof(config->adv_name) - 1);
|
||||
|
||||
// Set bonding mode
|
||||
config->bonding_mode = hid_ext_profile_params->bonding;
|
||||
|
||||
// Set pairing method
|
||||
config->pairing_method = hid_ext_profile_params->pairing;
|
||||
}
|
||||
|
||||
static const FuriHalBleProfileTemplate profile_callbacks = {
|
||||
.start = ble_profile_hid_ext_start,
|
||||
.stop = ble_profile_hid_ext_stop,
|
||||
.get_gap_config = ble_profile_hid_ext_get_config,
|
||||
};
|
||||
|
||||
const FuriHalBleProfileTemplate* ble_profile_hid_ext = &profile_callbacks;
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <ble_profile/extra_profiles/hid_profile.h>
|
||||
|
||||
/**
|
||||
* Optional arguments to pass along with profile template as
|
||||
* FuriHalBleProfileParams for tuning profile behavior
|
||||
**/
|
||||
typedef struct {
|
||||
char name[FURI_HAL_BT_ADV_NAME_LENGTH]; /**< Full device name */
|
||||
uint8_t mac[GAP_MAC_ADDR_SIZE]; /**< Full device address */
|
||||
bool bonding; /**< Save paired devices */
|
||||
GapPairing pairing; /**< Pairing security method */
|
||||
} BleProfileHidExtParams;
|
||||
|
||||
/** Hid Keyboard Profile descriptor */
|
||||
extern const FuriHalBleProfileTemplate* ble_profile_hid_ext;
|
||||
@@ -1,429 +0,0 @@
|
||||
#include "ble_hid_profile.h"
|
||||
|
||||
// Based on <lib/ble_profile/extra_profiles/hid_profile.c>
|
||||
|
||||
#include <furi_hal_usb_hid.h>
|
||||
#include <services/dev_info_service.h>
|
||||
#include <services/battery_service.h>
|
||||
#include "ble_hid_service.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <usb_hid.h>
|
||||
#include <ble/ble.h>
|
||||
|
||||
#define HID_INFO_BASE_USB_SPECIFICATION (0x0101)
|
||||
#define HID_INFO_COUNTRY_CODE (0x00)
|
||||
#define BLE_PROFILE_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01)
|
||||
#define BLE_PROFILE_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK (0x02)
|
||||
|
||||
#define BLE_PROFILE_HID_KB_MAX_KEYS (6)
|
||||
#define BLE_PROFILE_CONSUMER_MAX_KEYS (1)
|
||||
|
||||
// Report ids cant be 0
|
||||
enum HidReportId {
|
||||
ReportIdKeyboard = 1,
|
||||
ReportIdMouse = 2,
|
||||
ReportIdConsumer = 3,
|
||||
};
|
||||
// Report numbers corresponded to the report id with an offset of 1
|
||||
enum HidInputNumber {
|
||||
ReportNumberKeyboard = 0,
|
||||
ReportNumberMouse = 1,
|
||||
ReportNumberConsumer = 2,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t mods;
|
||||
uint8_t reserved;
|
||||
uint8_t key[BLE_PROFILE_HID_KB_MAX_KEYS];
|
||||
} FURI_PACKED FuriHalBtHidKbReport;
|
||||
|
||||
typedef struct {
|
||||
uint8_t btn;
|
||||
int8_t x;
|
||||
int8_t y;
|
||||
int8_t wheel;
|
||||
} FURI_PACKED FuriHalBtHidMouseReport;
|
||||
|
||||
typedef struct {
|
||||
uint16_t key[BLE_PROFILE_CONSUMER_MAX_KEYS];
|
||||
} FURI_PACKED FuriHalBtHidConsumerReport;
|
||||
|
||||
// keyboard+mouse+consumer hid report
|
||||
static const uint8_t ble_profile_hid_report_map_data[] = {
|
||||
// Keyboard Report
|
||||
HID_USAGE_PAGE(HID_PAGE_DESKTOP),
|
||||
HID_USAGE(HID_DESKTOP_KEYBOARD),
|
||||
HID_COLLECTION(HID_APPLICATION_COLLECTION),
|
||||
HID_REPORT_ID(ReportIdKeyboard),
|
||||
HID_USAGE_PAGE(HID_DESKTOP_KEYPAD),
|
||||
HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL),
|
||||
HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI),
|
||||
HID_LOGICAL_MINIMUM(0),
|
||||
HID_LOGICAL_MAXIMUM(1),
|
||||
HID_REPORT_SIZE(1),
|
||||
HID_REPORT_COUNT(8),
|
||||
HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
|
||||
HID_REPORT_COUNT(1),
|
||||
HID_REPORT_SIZE(8),
|
||||
HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
|
||||
HID_USAGE_PAGE(HID_PAGE_LED),
|
||||
HID_REPORT_COUNT(8),
|
||||
HID_REPORT_SIZE(1),
|
||||
HID_USAGE_MINIMUM(1),
|
||||
HID_USAGE_MAXIMUM(8),
|
||||
HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
|
||||
HID_REPORT_COUNT(BLE_PROFILE_HID_KB_MAX_KEYS),
|
||||
HID_REPORT_SIZE(8),
|
||||
HID_LOGICAL_MINIMUM(0),
|
||||
HID_LOGICAL_MAXIMUM(101),
|
||||
HID_USAGE_PAGE(HID_DESKTOP_KEYPAD),
|
||||
HID_USAGE_MINIMUM(0),
|
||||
HID_USAGE_MAXIMUM(101),
|
||||
HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE),
|
||||
HID_END_COLLECTION,
|
||||
// Mouse Report
|
||||
HID_USAGE_PAGE(HID_PAGE_DESKTOP),
|
||||
HID_USAGE(HID_DESKTOP_MOUSE),
|
||||
HID_COLLECTION(HID_APPLICATION_COLLECTION),
|
||||
HID_USAGE(HID_DESKTOP_POINTER),
|
||||
HID_COLLECTION(HID_PHYSICAL_COLLECTION),
|
||||
HID_REPORT_ID(ReportIdMouse),
|
||||
HID_USAGE_PAGE(HID_PAGE_BUTTON),
|
||||
HID_USAGE_MINIMUM(1),
|
||||
HID_USAGE_MAXIMUM(3),
|
||||
HID_LOGICAL_MINIMUM(0),
|
||||
HID_LOGICAL_MAXIMUM(1),
|
||||
HID_REPORT_COUNT(3),
|
||||
HID_REPORT_SIZE(1),
|
||||
HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
|
||||
HID_REPORT_SIZE(1),
|
||||
HID_REPORT_COUNT(5),
|
||||
HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
|
||||
HID_USAGE_PAGE(HID_PAGE_DESKTOP),
|
||||
HID_USAGE(HID_DESKTOP_X),
|
||||
HID_USAGE(HID_DESKTOP_Y),
|
||||
HID_USAGE(HID_DESKTOP_WHEEL),
|
||||
HID_LOGICAL_MINIMUM(-127),
|
||||
HID_LOGICAL_MAXIMUM(127),
|
||||
HID_REPORT_SIZE(8),
|
||||
HID_REPORT_COUNT(3),
|
||||
HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE),
|
||||
HID_END_COLLECTION,
|
||||
HID_END_COLLECTION,
|
||||
// Consumer Report
|
||||
HID_USAGE_PAGE(HID_PAGE_CONSUMER),
|
||||
HID_USAGE(HID_CONSUMER_CONTROL),
|
||||
HID_COLLECTION(HID_APPLICATION_COLLECTION),
|
||||
HID_REPORT_ID(ReportIdConsumer),
|
||||
HID_LOGICAL_MINIMUM(0),
|
||||
HID_RI_LOGICAL_MAXIMUM(16, 0x3FF),
|
||||
HID_USAGE_MINIMUM(0),
|
||||
HID_RI_USAGE_MAXIMUM(16, 0x3FF),
|
||||
HID_REPORT_COUNT(BLE_PROFILE_CONSUMER_MAX_KEYS),
|
||||
HID_REPORT_SIZE(16),
|
||||
HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE),
|
||||
HID_END_COLLECTION,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
FuriHalBleProfileBase base;
|
||||
|
||||
FuriHalBtHidKbReport* kb_report;
|
||||
FuriHalBtHidMouseReport* mouse_report;
|
||||
FuriHalBtHidConsumerReport* consumer_report;
|
||||
|
||||
BleServiceBattery* battery_svc;
|
||||
BleServiceDevInfo* dev_info_svc;
|
||||
BleServiceHid* hid_svc;
|
||||
} BleProfileHid;
|
||||
_Static_assert(offsetof(BleProfileHid, base) == 0, "Wrong layout");
|
||||
|
||||
static FuriHalBleProfileBase* ble_profile_hid_start(FuriHalBleProfileParams profile_params) {
|
||||
UNUSED(profile_params);
|
||||
|
||||
BleProfileHid* profile = malloc(sizeof(BleProfileHid));
|
||||
|
||||
profile->base.config = ble_profile_hid;
|
||||
|
||||
profile->battery_svc = ble_svc_battery_start(true);
|
||||
profile->dev_info_svc = ble_svc_dev_info_start();
|
||||
profile->hid_svc = ble_svc_hid_start();
|
||||
|
||||
// Configure HID Keyboard
|
||||
profile->kb_report = malloc(sizeof(FuriHalBtHidKbReport));
|
||||
profile->mouse_report = malloc(sizeof(FuriHalBtHidMouseReport));
|
||||
profile->consumer_report = malloc(sizeof(FuriHalBtHidConsumerReport));
|
||||
|
||||
// Configure Report Map characteristic
|
||||
ble_svc_hid_update_report_map(
|
||||
profile->hid_svc,
|
||||
ble_profile_hid_report_map_data,
|
||||
sizeof(ble_profile_hid_report_map_data));
|
||||
// Configure HID Information characteristic
|
||||
uint8_t hid_info_val[4] = {
|
||||
HID_INFO_BASE_USB_SPECIFICATION & 0x00ff,
|
||||
(HID_INFO_BASE_USB_SPECIFICATION & 0xff00) >> 8,
|
||||
HID_INFO_COUNTRY_CODE,
|
||||
BLE_PROFILE_HID_INFO_FLAG_REMOTE_WAKE_MSK |
|
||||
BLE_PROFILE_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK,
|
||||
};
|
||||
ble_svc_hid_update_info(profile->hid_svc, hid_info_val);
|
||||
|
||||
return &profile->base;
|
||||
}
|
||||
|
||||
static void ble_profile_hid_stop(FuriHalBleProfileBase* profile) {
|
||||
furi_check(profile);
|
||||
furi_check(profile->config == ble_profile_hid);
|
||||
|
||||
BleProfileHid* hid_profile = (BleProfileHid*)profile;
|
||||
ble_svc_battery_stop(hid_profile->battery_svc);
|
||||
ble_svc_dev_info_stop(hid_profile->dev_info_svc);
|
||||
ble_svc_hid_stop(hid_profile->hid_svc);
|
||||
|
||||
free(hid_profile->kb_report);
|
||||
free(hid_profile->mouse_report);
|
||||
free(hid_profile->consumer_report);
|
||||
}
|
||||
|
||||
bool ble_profile_hid_kb_press(FuriHalBleProfileBase* profile, uint16_t button) {
|
||||
furi_check(profile);
|
||||
furi_check(profile->config == ble_profile_hid);
|
||||
|
||||
BleProfileHid* hid_profile = (BleProfileHid*)profile;
|
||||
FuriHalBtHidKbReport* kb_report = hid_profile->kb_report;
|
||||
for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) {
|
||||
if(kb_report->key[i] == 0) {
|
||||
kb_report->key[i] = button & 0xFF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
kb_report->mods |= (button >> 8);
|
||||
return ble_svc_hid_update_input_report(
|
||||
hid_profile->hid_svc,
|
||||
ReportNumberKeyboard,
|
||||
(uint8_t*)kb_report,
|
||||
sizeof(FuriHalBtHidKbReport));
|
||||
}
|
||||
|
||||
bool ble_profile_hid_kb_release(FuriHalBleProfileBase* profile, uint16_t button) {
|
||||
furi_check(profile);
|
||||
furi_check(profile->config == ble_profile_hid);
|
||||
|
||||
BleProfileHid* hid_profile = (BleProfileHid*)profile;
|
||||
|
||||
FuriHalBtHidKbReport* kb_report = hid_profile->kb_report;
|
||||
for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) {
|
||||
if(kb_report->key[i] == (button & 0xFF)) {
|
||||
kb_report->key[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
kb_report->mods &= ~(button >> 8);
|
||||
return ble_svc_hid_update_input_report(
|
||||
hid_profile->hid_svc,
|
||||
ReportNumberKeyboard,
|
||||
(uint8_t*)kb_report,
|
||||
sizeof(FuriHalBtHidKbReport));
|
||||
}
|
||||
|
||||
bool ble_profile_hid_kb_release_all(FuriHalBleProfileBase* profile) {
|
||||
furi_check(profile);
|
||||
furi_check(profile->config == ble_profile_hid);
|
||||
|
||||
BleProfileHid* hid_profile = (BleProfileHid*)profile;
|
||||
FuriHalBtHidKbReport* kb_report = hid_profile->kb_report;
|
||||
for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) {
|
||||
kb_report->key[i] = 0;
|
||||
}
|
||||
kb_report->mods = 0;
|
||||
return ble_svc_hid_update_input_report(
|
||||
hid_profile->hid_svc,
|
||||
ReportNumberKeyboard,
|
||||
(uint8_t*)kb_report,
|
||||
sizeof(FuriHalBtHidKbReport));
|
||||
}
|
||||
|
||||
bool ble_profile_hid_consumer_key_press(FuriHalBleProfileBase* profile, uint16_t button) {
|
||||
furi_check(profile);
|
||||
furi_check(profile->config == ble_profile_hid);
|
||||
|
||||
BleProfileHid* hid_profile = (BleProfileHid*)profile;
|
||||
FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report;
|
||||
for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008
|
||||
if(consumer_report->key[i] == 0) {
|
||||
consumer_report->key[i] = button;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ble_svc_hid_update_input_report(
|
||||
hid_profile->hid_svc,
|
||||
ReportNumberConsumer,
|
||||
(uint8_t*)consumer_report,
|
||||
sizeof(FuriHalBtHidConsumerReport));
|
||||
}
|
||||
|
||||
bool ble_profile_hid_consumer_key_release(FuriHalBleProfileBase* profile, uint16_t button) {
|
||||
furi_check(profile);
|
||||
furi_check(profile->config == ble_profile_hid);
|
||||
|
||||
BleProfileHid* hid_profile = (BleProfileHid*)profile;
|
||||
FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report;
|
||||
for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008
|
||||
if(consumer_report->key[i] == button) {
|
||||
consumer_report->key[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ble_svc_hid_update_input_report(
|
||||
hid_profile->hid_svc,
|
||||
ReportNumberConsumer,
|
||||
(uint8_t*)consumer_report,
|
||||
sizeof(FuriHalBtHidConsumerReport));
|
||||
}
|
||||
|
||||
bool ble_profile_hid_consumer_key_release_all(FuriHalBleProfileBase* profile) {
|
||||
furi_check(profile);
|
||||
furi_check(profile->config == ble_profile_hid);
|
||||
|
||||
BleProfileHid* hid_profile = (BleProfileHid*)profile;
|
||||
FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report;
|
||||
for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008
|
||||
consumer_report->key[i] = 0;
|
||||
}
|
||||
return ble_svc_hid_update_input_report(
|
||||
hid_profile->hid_svc,
|
||||
ReportNumberConsumer,
|
||||
(uint8_t*)consumer_report,
|
||||
sizeof(FuriHalBtHidConsumerReport));
|
||||
}
|
||||
|
||||
bool ble_profile_hid_mouse_move(FuriHalBleProfileBase* profile, int8_t dx, int8_t dy) {
|
||||
furi_check(profile);
|
||||
furi_check(profile->config == ble_profile_hid);
|
||||
|
||||
BleProfileHid* hid_profile = (BleProfileHid*)profile;
|
||||
FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report;
|
||||
mouse_report->x = dx;
|
||||
mouse_report->y = dy;
|
||||
bool state = ble_svc_hid_update_input_report(
|
||||
hid_profile->hid_svc,
|
||||
ReportNumberMouse,
|
||||
(uint8_t*)mouse_report,
|
||||
sizeof(FuriHalBtHidMouseReport));
|
||||
mouse_report->x = 0;
|
||||
mouse_report->y = 0;
|
||||
return state;
|
||||
}
|
||||
|
||||
bool ble_profile_hid_mouse_press(FuriHalBleProfileBase* profile, uint8_t button) {
|
||||
furi_check(profile);
|
||||
furi_check(profile->config == ble_profile_hid);
|
||||
|
||||
BleProfileHid* hid_profile = (BleProfileHid*)profile;
|
||||
FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report;
|
||||
mouse_report->btn |= button;
|
||||
return ble_svc_hid_update_input_report(
|
||||
hid_profile->hid_svc,
|
||||
ReportNumberMouse,
|
||||
(uint8_t*)mouse_report,
|
||||
sizeof(FuriHalBtHidMouseReport));
|
||||
}
|
||||
|
||||
bool ble_profile_hid_mouse_release(FuriHalBleProfileBase* profile, uint8_t button) {
|
||||
furi_check(profile);
|
||||
furi_check(profile->config == ble_profile_hid);
|
||||
|
||||
BleProfileHid* hid_profile = (BleProfileHid*)profile;
|
||||
FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report;
|
||||
mouse_report->btn &= ~button;
|
||||
return ble_svc_hid_update_input_report(
|
||||
hid_profile->hid_svc,
|
||||
ReportNumberMouse,
|
||||
(uint8_t*)mouse_report,
|
||||
sizeof(FuriHalBtHidMouseReport));
|
||||
}
|
||||
|
||||
bool ble_profile_hid_mouse_release_all(FuriHalBleProfileBase* profile) {
|
||||
furi_check(profile);
|
||||
furi_check(profile->config == ble_profile_hid);
|
||||
|
||||
BleProfileHid* hid_profile = (BleProfileHid*)profile;
|
||||
FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report;
|
||||
mouse_report->btn = 0;
|
||||
return ble_svc_hid_update_input_report(
|
||||
hid_profile->hid_svc,
|
||||
ReportNumberMouse,
|
||||
(uint8_t*)mouse_report,
|
||||
sizeof(FuriHalBtHidMouseReport));
|
||||
}
|
||||
|
||||
bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta) {
|
||||
furi_check(profile);
|
||||
furi_check(profile->config == ble_profile_hid);
|
||||
|
||||
BleProfileHid* hid_profile = (BleProfileHid*)profile;
|
||||
FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report;
|
||||
mouse_report->wheel = delta;
|
||||
bool state = ble_svc_hid_update_input_report(
|
||||
hid_profile->hid_svc,
|
||||
ReportNumberMouse,
|
||||
(uint8_t*)mouse_report,
|
||||
sizeof(FuriHalBtHidMouseReport));
|
||||
mouse_report->wheel = 0;
|
||||
return state;
|
||||
}
|
||||
|
||||
// AN5289: 4.7, in order to use flash controller interval must be at least 25ms + advertisement, which is 30 ms
|
||||
// Since we don't use flash controller anymore interval can be lowered to 7.5ms
|
||||
#define CONNECTION_INTERVAL_MIN (0x0006)
|
||||
// Up to 45 ms
|
||||
#define CONNECTION_INTERVAL_MAX (0x24)
|
||||
|
||||
static GapConfig template_config = {
|
||||
.adv_service =
|
||||
{
|
||||
.UUID_Type = UUID_TYPE_16,
|
||||
.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID,
|
||||
},
|
||||
.appearance_char = GAP_APPEARANCE_KEYBOARD,
|
||||
.bonding_mode = true,
|
||||
.pairing_method = GapPairingPinCodeVerifyYesNo,
|
||||
.conn_param =
|
||||
{
|
||||
.conn_int_min = CONNECTION_INTERVAL_MIN,
|
||||
.conn_int_max = CONNECTION_INTERVAL_MAX,
|
||||
.slave_latency = 0,
|
||||
.supervisor_timeout = 0,
|
||||
},
|
||||
};
|
||||
|
||||
static void ble_profile_hid_get_config(GapConfig* config, FuriHalBleProfileParams profile_params) {
|
||||
furi_check(profile_params);
|
||||
BleProfileHidParams* hid_profile_params = profile_params;
|
||||
|
||||
furi_check(config);
|
||||
memcpy(config, &template_config, sizeof(GapConfig));
|
||||
|
||||
// Set MAC address
|
||||
memcpy(config->mac_address, hid_profile_params->mac, sizeof(config->mac_address));
|
||||
|
||||
// Set advertise name
|
||||
config->adv_name[0] = furi_hal_version_get_ble_local_device_name_ptr()[0];
|
||||
strlcpy(config->adv_name + 1, hid_profile_params->name, sizeof(config->adv_name) - 1);
|
||||
|
||||
// Set bonding mode
|
||||
config->bonding_mode = hid_profile_params->bonding;
|
||||
|
||||
// Set pairing method
|
||||
config->pairing_method = hid_profile_params->pairing;
|
||||
}
|
||||
|
||||
static const FuriHalBleProfileTemplate profile_callbacks = {
|
||||
.start = ble_profile_hid_start,
|
||||
.stop = ble_profile_hid_stop,
|
||||
.get_gap_config = ble_profile_hid_get_config,
|
||||
};
|
||||
|
||||
const FuriHalBleProfileTemplate* ble_profile_hid = &profile_callbacks;
|
||||
@@ -1,109 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// Based on <lib/ble_profile/extra_profiles/hid_profile.h>
|
||||
|
||||
#include <furi_ble/profile_interface.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Optional arguments to pass along with profile template as
|
||||
* FuriHalBleProfileParams for tuning profile behavior
|
||||
**/
|
||||
typedef struct {
|
||||
char name[FURI_HAL_BT_ADV_NAME_LENGTH]; /**< Full device name */
|
||||
uint8_t mac[GAP_MAC_ADDR_SIZE]; /**< Full device address */
|
||||
bool bonding; /**< Save paired devices */
|
||||
GapPairing pairing; /**< Pairing security method */
|
||||
} BleProfileHidParams;
|
||||
|
||||
/** Hid Keyboard Profile descriptor */
|
||||
extern const FuriHalBleProfileTemplate* ble_profile_hid;
|
||||
|
||||
/** Press keyboard button
|
||||
*
|
||||
* @param profile profile instance
|
||||
* @param button button code from HID specification
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool ble_profile_hid_kb_press(FuriHalBleProfileBase* profile, uint16_t button);
|
||||
|
||||
/** Release keyboard button
|
||||
*
|
||||
* @param profile profile instance
|
||||
* @param button button code from HID specification
|
||||
*
|
||||
* @return true on success
|
||||
*/
|
||||
bool ble_profile_hid_kb_release(FuriHalBleProfileBase* profile, uint16_t button);
|
||||
|
||||
/** Release all keyboard buttons
|
||||
*
|
||||
* @param profile profile instance
|
||||
* @return true on success
|
||||
*/
|
||||
bool ble_profile_hid_kb_release_all(FuriHalBleProfileBase* profile);
|
||||
|
||||
/** Set the following consumer key to pressed state and send HID report
|
||||
*
|
||||
* @param profile profile instance
|
||||
* @param button key code
|
||||
*/
|
||||
bool ble_profile_hid_consumer_key_press(FuriHalBleProfileBase* profile, uint16_t button);
|
||||
|
||||
/** Set the following consumer key to released state and send HID report
|
||||
*
|
||||
* @param profile profile instance
|
||||
* @param button key code
|
||||
*/
|
||||
bool ble_profile_hid_consumer_key_release(FuriHalBleProfileBase* profile, uint16_t button);
|
||||
|
||||
/** Set consumer key to released state and send HID report
|
||||
*
|
||||
* @param profile profile instance
|
||||
* @param button key code
|
||||
*/
|
||||
bool ble_profile_hid_consumer_key_release_all(FuriHalBleProfileBase* profile);
|
||||
|
||||
/** Set mouse movement and send HID report
|
||||
*
|
||||
* @param profile profile instance
|
||||
* @param dx x coordinate delta
|
||||
* @param dy y coordinate delta
|
||||
*/
|
||||
bool ble_profile_hid_mouse_move(FuriHalBleProfileBase* profile, int8_t dx, int8_t dy);
|
||||
|
||||
/** Set mouse button to pressed state and send HID report
|
||||
*
|
||||
* @param profile profile instance
|
||||
* @param button key code
|
||||
*/
|
||||
bool ble_profile_hid_mouse_press(FuriHalBleProfileBase* profile, uint8_t button);
|
||||
|
||||
/** Set mouse button to released state and send HID report
|
||||
*
|
||||
* @param profile profile instance
|
||||
* @param button key code
|
||||
*/
|
||||
bool ble_profile_hid_mouse_release(FuriHalBleProfileBase* profile, uint8_t button);
|
||||
|
||||
/** Set mouse button to released state and send HID report
|
||||
*
|
||||
* @param profile profile instance
|
||||
* @param button key code
|
||||
*/
|
||||
bool ble_profile_hid_mouse_release_all(FuriHalBleProfileBase* profile);
|
||||
|
||||
/** Set mouse wheel position and send HID report
|
||||
*
|
||||
* @param profile profile instance
|
||||
* @param delta number of scroll steps
|
||||
*/
|
||||
bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,325 +0,0 @@
|
||||
#include "ble_hid_service.h"
|
||||
|
||||
// Based on <lib/ble_profile/extra_services/hid_service.c>
|
||||
|
||||
#include "app_common.h" // IWYU pragma: keep
|
||||
#include <ble/ble.h>
|
||||
#include <furi_ble/event_dispatcher.h>
|
||||
#include <furi_ble/gatt.h>
|
||||
|
||||
#include <furi.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define TAG "BleHid"
|
||||
|
||||
#define BLE_SVC_HID_REPORT_MAP_MAX_LEN (255)
|
||||
#define BLE_SVC_HID_REPORT_MAX_LEN (255)
|
||||
#define BLE_SVC_HID_REPORT_REF_LEN (2)
|
||||
#define BLE_SVC_HID_INFO_LEN (4)
|
||||
#define BLE_SVC_HID_CONTROL_POINT_LEN (1)
|
||||
|
||||
#define BLE_SVC_HID_INPUT_REPORT_COUNT (3)
|
||||
#define BLE_SVC_HID_OUTPUT_REPORT_COUNT (0)
|
||||
#define BLE_SVC_HID_FEATURE_REPORT_COUNT (0)
|
||||
#define BLE_SVC_HID_REPORT_COUNT \
|
||||
(BLE_SVC_HID_INPUT_REPORT_COUNT + BLE_SVC_HID_OUTPUT_REPORT_COUNT + \
|
||||
BLE_SVC_HID_FEATURE_REPORT_COUNT)
|
||||
|
||||
typedef enum {
|
||||
HidSvcGattCharacteristicProtocolMode = 0,
|
||||
HidSvcGattCharacteristicReportMap,
|
||||
HidSvcGattCharacteristicInfo,
|
||||
HidSvcGattCharacteristicCtrlPoint,
|
||||
HidSvcGattCharacteristicCount,
|
||||
} HidSvcGattCharacteristicId;
|
||||
|
||||
typedef struct {
|
||||
uint8_t report_idx;
|
||||
uint8_t report_type;
|
||||
} HidSvcReportId;
|
||||
|
||||
static_assert(sizeof(HidSvcReportId) == sizeof(uint16_t), "HidSvcReportId must be 2 bytes");
|
||||
|
||||
static const Service_UUID_t ble_svc_hid_uuid = {
|
||||
.Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID,
|
||||
};
|
||||
|
||||
static bool ble_svc_hid_char_desc_data_callback(
|
||||
const void* context,
|
||||
const uint8_t** data,
|
||||
uint16_t* data_len) {
|
||||
const HidSvcReportId* report_id = context;
|
||||
*data_len = sizeof(HidSvcReportId);
|
||||
if(data) {
|
||||
*data = (const uint8_t*)report_id;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const void* data_ptr;
|
||||
uint16_t data_len;
|
||||
} HidSvcDataWrapper;
|
||||
|
||||
static bool ble_svc_hid_report_data_callback(
|
||||
const void* context,
|
||||
const uint8_t** data,
|
||||
uint16_t* data_len) {
|
||||
const HidSvcDataWrapper* report_data = context;
|
||||
if(data) {
|
||||
*data = report_data->data_ptr;
|
||||
*data_len = report_data->data_len;
|
||||
} else {
|
||||
*data_len = BLE_SVC_HID_REPORT_MAP_MAX_LEN;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const BleGattCharacteristicParams ble_svc_hid_chars[HidSvcGattCharacteristicCount] = {
|
||||
[HidSvcGattCharacteristicProtocolMode] =
|
||||
{.name = "Protocol Mode",
|
||||
.data_prop_type = FlipperGattCharacteristicDataFixed,
|
||||
.data.fixed.length = 1,
|
||||
.uuid.Char_UUID_16 = PROTOCOL_MODE_CHAR_UUID,
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP,
|
||||
.security_permissions = ATTR_PERMISSION_NONE,
|
||||
.gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT},
|
||||
[HidSvcGattCharacteristicReportMap] =
|
||||
{.name = "Report Map",
|
||||
.data_prop_type = FlipperGattCharacteristicDataCallback,
|
||||
.data.callback.fn = ble_svc_hid_report_data_callback,
|
||||
.data.callback.context = NULL,
|
||||
.uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID,
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.char_properties = CHAR_PROP_READ,
|
||||
.security_permissions = ATTR_PERMISSION_NONE,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_VARIABLE},
|
||||
[HidSvcGattCharacteristicInfo] =
|
||||
{.name = "HID Information",
|
||||
.data_prop_type = FlipperGattCharacteristicDataFixed,
|
||||
.data.fixed.length = BLE_SVC_HID_INFO_LEN,
|
||||
.data.fixed.ptr = NULL,
|
||||
.uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID,
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.char_properties = CHAR_PROP_READ,
|
||||
.security_permissions = ATTR_PERMISSION_NONE,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT},
|
||||
[HidSvcGattCharacteristicCtrlPoint] =
|
||||
{.name = "HID Control Point",
|
||||
.data_prop_type = FlipperGattCharacteristicDataFixed,
|
||||
.data.fixed.length = BLE_SVC_HID_CONTROL_POINT_LEN,
|
||||
.uuid.Char_UUID_16 = HID_CONTROL_POINT_CHAR_UUID,
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.char_properties = CHAR_PROP_WRITE_WITHOUT_RESP,
|
||||
.security_permissions = ATTR_PERMISSION_NONE,
|
||||
.gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT},
|
||||
};
|
||||
|
||||
static const BleGattCharacteristicDescriptorParams ble_svc_hid_char_descr_template = {
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID,
|
||||
.max_length = BLE_SVC_HID_REPORT_REF_LEN,
|
||||
.data_callback.fn = ble_svc_hid_char_desc_data_callback,
|
||||
.security_permissions = ATTR_PERMISSION_NONE,
|
||||
.access_permissions = ATTR_ACCESS_READ_WRITE,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_CONSTANT,
|
||||
};
|
||||
|
||||
static const BleGattCharacteristicParams ble_svc_hid_report_template = {
|
||||
.name = "Report",
|
||||
.data_prop_type = FlipperGattCharacteristicDataCallback,
|
||||
.data.callback.fn = ble_svc_hid_report_data_callback,
|
||||
.data.callback.context = NULL,
|
||||
.uuid.Char_UUID_16 = REPORT_CHAR_UUID,
|
||||
.uuid_type = UUID_TYPE_16,
|
||||
.char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY,
|
||||
.security_permissions = ATTR_PERMISSION_NONE,
|
||||
.gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS,
|
||||
.is_variable = CHAR_VALUE_LEN_VARIABLE,
|
||||
};
|
||||
|
||||
struct BleServiceHid {
|
||||
uint16_t svc_handle;
|
||||
BleGattCharacteristicInstance chars[HidSvcGattCharacteristicCount];
|
||||
BleGattCharacteristicInstance input_report_chars[BLE_SVC_HID_INPUT_REPORT_COUNT];
|
||||
BleGattCharacteristicInstance output_report_chars[BLE_SVC_HID_OUTPUT_REPORT_COUNT];
|
||||
BleGattCharacteristicInstance feature_report_chars[BLE_SVC_HID_FEATURE_REPORT_COUNT];
|
||||
GapSvcEventHandler* event_handler;
|
||||
};
|
||||
|
||||
static BleEventAckStatus ble_svc_hid_event_handler(void* event, void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
BleEventAckStatus ret = BleEventNotAck;
|
||||
hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data);
|
||||
evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data;
|
||||
// aci_gatt_attribute_modified_event_rp0* attribute_modified;
|
||||
|
||||
if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) {
|
||||
if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) {
|
||||
// Process modification events
|
||||
ret = BleEventAckFlowEnable;
|
||||
} else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) {
|
||||
// Process notification confirmation
|
||||
ret = BleEventAckFlowEnable;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
BleServiceHid* ble_svc_hid_start(void) {
|
||||
BleServiceHid* hid_svc = malloc(sizeof(BleServiceHid));
|
||||
|
||||
// Register event handler
|
||||
hid_svc->event_handler =
|
||||
ble_event_dispatcher_register_svc_handler(ble_svc_hid_event_handler, hid_svc);
|
||||
/**
|
||||
* Add Human Interface Device Service
|
||||
*/
|
||||
if(!ble_gatt_service_add(
|
||||
UUID_TYPE_16,
|
||||
&ble_svc_hid_uuid,
|
||||
PRIMARY_SERVICE,
|
||||
2 + /* protocol mode */
|
||||
(4 * BLE_SVC_HID_INPUT_REPORT_COUNT) + (3 * BLE_SVC_HID_OUTPUT_REPORT_COUNT) +
|
||||
(3 * BLE_SVC_HID_FEATURE_REPORT_COUNT) + 1 + 2 + 2 +
|
||||
2, /* Service + Report Map + HID Information + HID Control Point */
|
||||
&hid_svc->svc_handle)) {
|
||||
free(hid_svc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Maintain previously defined characteristic order
|
||||
ble_gatt_characteristic_init(
|
||||
hid_svc->svc_handle,
|
||||
&ble_svc_hid_chars[HidSvcGattCharacteristicProtocolMode],
|
||||
&hid_svc->chars[HidSvcGattCharacteristicProtocolMode]);
|
||||
|
||||
uint8_t protocol_mode = 1;
|
||||
ble_gatt_characteristic_update(
|
||||
hid_svc->svc_handle,
|
||||
&hid_svc->chars[HidSvcGattCharacteristicProtocolMode],
|
||||
&protocol_mode);
|
||||
|
||||
// reports
|
||||
BleGattCharacteristicDescriptorParams ble_svc_hid_char_descr;
|
||||
BleGattCharacteristicParams report_char;
|
||||
HidSvcReportId report_id;
|
||||
|
||||
memcpy(
|
||||
&ble_svc_hid_char_descr, &ble_svc_hid_char_descr_template, sizeof(ble_svc_hid_char_descr));
|
||||
memcpy(&report_char, &ble_svc_hid_report_template, sizeof(report_char));
|
||||
|
||||
ble_svc_hid_char_descr.data_callback.context = &report_id;
|
||||
report_char.descriptor_params = &ble_svc_hid_char_descr;
|
||||
|
||||
typedef struct {
|
||||
uint8_t report_type;
|
||||
uint8_t report_count;
|
||||
BleGattCharacteristicInstance* chars;
|
||||
} HidSvcReportCharProps;
|
||||
|
||||
HidSvcReportCharProps hid_report_chars[] = {
|
||||
{0x01, BLE_SVC_HID_INPUT_REPORT_COUNT, hid_svc->input_report_chars},
|
||||
{0x02, BLE_SVC_HID_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars},
|
||||
{0x03, BLE_SVC_HID_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars},
|
||||
};
|
||||
|
||||
for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars);
|
||||
report_type_idx++) {
|
||||
report_id.report_type = hid_report_chars[report_type_idx].report_type;
|
||||
for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count;
|
||||
report_idx++) {
|
||||
report_id.report_idx = report_idx + 1;
|
||||
ble_gatt_characteristic_init(
|
||||
hid_svc->svc_handle,
|
||||
&report_char,
|
||||
&hid_report_chars[report_type_idx].chars[report_idx]);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup remaining characteristics
|
||||
for(size_t i = HidSvcGattCharacteristicReportMap; i < HidSvcGattCharacteristicCount; i++) {
|
||||
ble_gatt_characteristic_init(
|
||||
hid_svc->svc_handle, &ble_svc_hid_chars[i], &hid_svc->chars[i]);
|
||||
}
|
||||
|
||||
return hid_svc;
|
||||
}
|
||||
|
||||
bool ble_svc_hid_update_report_map(BleServiceHid* hid_svc, const uint8_t* data, uint16_t len) {
|
||||
furi_assert(data);
|
||||
furi_assert(hid_svc);
|
||||
|
||||
HidSvcDataWrapper report_data = {
|
||||
.data_ptr = data,
|
||||
.data_len = len,
|
||||
};
|
||||
return ble_gatt_characteristic_update(
|
||||
hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicReportMap], &report_data);
|
||||
}
|
||||
|
||||
bool ble_svc_hid_update_input_report(
|
||||
BleServiceHid* hid_svc,
|
||||
uint8_t input_report_num,
|
||||
uint8_t* data,
|
||||
uint16_t len) {
|
||||
furi_assert(data);
|
||||
furi_assert(hid_svc);
|
||||
furi_assert(input_report_num < BLE_SVC_HID_INPUT_REPORT_COUNT);
|
||||
|
||||
HidSvcDataWrapper report_data = {
|
||||
.data_ptr = data,
|
||||
.data_len = len,
|
||||
};
|
||||
|
||||
return ble_gatt_characteristic_update(
|
||||
hid_svc->svc_handle, &hid_svc->input_report_chars[input_report_num], &report_data);
|
||||
}
|
||||
|
||||
bool ble_svc_hid_update_info(BleServiceHid* hid_svc, uint8_t* data) {
|
||||
furi_assert(data);
|
||||
furi_assert(hid_svc);
|
||||
|
||||
return ble_gatt_characteristic_update(
|
||||
hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicInfo], &data);
|
||||
}
|
||||
|
||||
void ble_svc_hid_stop(BleServiceHid* hid_svc) {
|
||||
furi_assert(hid_svc);
|
||||
ble_event_dispatcher_unregister_svc_handler(hid_svc->event_handler);
|
||||
// Delete characteristics
|
||||
for(size_t i = 0; i < HidSvcGattCharacteristicCount; i++) {
|
||||
ble_gatt_characteristic_delete(hid_svc->svc_handle, &hid_svc->chars[i]);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint8_t report_count;
|
||||
BleGattCharacteristicInstance* chars;
|
||||
} HidSvcReportCharProps;
|
||||
|
||||
HidSvcReportCharProps hid_report_chars[] = {
|
||||
{BLE_SVC_HID_INPUT_REPORT_COUNT, hid_svc->input_report_chars},
|
||||
{BLE_SVC_HID_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars},
|
||||
{BLE_SVC_HID_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars},
|
||||
};
|
||||
|
||||
for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars);
|
||||
report_type_idx++) {
|
||||
for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count;
|
||||
report_idx++) {
|
||||
ble_gatt_characteristic_delete(
|
||||
hid_svc->svc_handle, &hid_report_chars[report_type_idx].chars[report_idx]);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete service
|
||||
ble_gatt_service_delete(hid_svc->svc_handle);
|
||||
free(hid_svc);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
// Based on <lib/ble_profile/extra_services/hid_service.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct BleServiceHid BleServiceHid;
|
||||
|
||||
BleServiceHid* ble_svc_hid_start(void);
|
||||
|
||||
void ble_svc_hid_stop(BleServiceHid* service);
|
||||
|
||||
bool ble_svc_hid_update_report_map(BleServiceHid* service, const uint8_t* data, uint16_t len);
|
||||
|
||||
bool ble_svc_hid_update_input_report(
|
||||
BleServiceHid* service,
|
||||
uint8_t input_report_num,
|
||||
uint8_t* data,
|
||||
uint16_t len);
|
||||
|
||||
// Expects data to be of length BLE_SVC_HID_INFO_LEN (4 bytes)
|
||||
bool ble_svc_hid_update_info(BleServiceHid* service, uint8_t* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -257,7 +257,7 @@ static bool ducky_set_usb_id(BadUsbScript* bad_usb, const char* line) {
|
||||
}
|
||||
|
||||
static bool ducky_set_ble_id(BadUsbScript* bad_usb, const char* line) {
|
||||
BleProfileHidParams* ble_hid_cfg = &bad_usb->hid_cfg->ble;
|
||||
BleProfileHidExtParams* ble_hid_cfg = &bad_usb->hid_cfg->ble;
|
||||
|
||||
size_t line_len = strlen(line);
|
||||
size_t mac_len = sizeof(ble_hid_cfg->mac) * 3; // 2 hex chars + separator per byte
|
||||
|
||||
@@ -84,7 +84,7 @@ static void draw_menu(BadUsbApp* bad_usb) {
|
||||
item, bad_usb->interface == BadUsbHidInterfaceBle ? "BLE" : "USB");
|
||||
|
||||
if(bad_usb->interface == BadUsbHidInterfaceBle) {
|
||||
BleProfileHidParams* ble_hid_cfg = &bad_usb->script_hid_cfg.ble;
|
||||
BleProfileHidExtParams* ble_hid_cfg = &bad_usb->script_hid_cfg.ble;
|
||||
|
||||
item = variable_item_list_add(
|
||||
var_item_list,
|
||||
|
||||
@@ -201,22 +201,23 @@ void momentum_app_load_mainmenu_apps(MomentumApp* app) {
|
||||
if(furi_string_start_with(line, "/")) {
|
||||
if(!flipper_application_load_name_and_icon(
|
||||
line, app->storage, &unused_icon, label)) {
|
||||
furi_string_reset(label);
|
||||
const char* end = strrchr(furi_string_get_cstr(line), '/');
|
||||
furi_string_set(label, end ? end + 1 : furi_string_get_cstr(line));
|
||||
}
|
||||
} else {
|
||||
furi_string_reset(label);
|
||||
bool found = false;
|
||||
for(size_t i = 0; !found && i < FLIPPER_APPS_COUNT; i++) {
|
||||
if(!strcmp(furi_string_get_cstr(line), FLIPPER_APPS[i].name)) {
|
||||
furi_string_set(label, FLIPPER_APPS[i].name);
|
||||
found = true;
|
||||
}
|
||||
momentum_app_push_mainmenu_app(app, label, line);
|
||||
continue;
|
||||
}
|
||||
bool found = false;
|
||||
for(size_t i = 0; !found && i < FLIPPER_APPS_COUNT; i++) {
|
||||
if(!strcmp(furi_string_get_cstr(line), FLIPPER_APPS[i].name)) {
|
||||
furi_string_set(label, FLIPPER_APPS[i].name);
|
||||
found = true;
|
||||
}
|
||||
for(size_t i = 0; !found && i < FLIPPER_EXTERNAL_APPS_COUNT; i++) {
|
||||
if(!strcmp(furi_string_get_cstr(line), FLIPPER_EXTERNAL_APPS[i].name)) {
|
||||
furi_string_set(label, FLIPPER_EXTERNAL_APPS[i].name);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
for(size_t i = 0; !found && i < FLIPPER_EXTERNAL_APPS_COUNT; i++) {
|
||||
if(!strcmp(furi_string_get_cstr(line), FLIPPER_EXTERNAL_APPS[i].name)) {
|
||||
furi_string_set(label, FLIPPER_EXTERNAL_APPS[i].name);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if(furi_string_empty(label)) {
|
||||
|
||||
@@ -110,6 +110,15 @@ static void
|
||||
app->save_settings = true;
|
||||
}
|
||||
|
||||
static void
|
||||
momentum_app_scene_interface_lockscreen_lockscreen_skip_animation_changed(VariableItem* item) {
|
||||
MomentumApp* app = variable_item_get_context(item);
|
||||
bool value = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, value ? "ON" : "OFF");
|
||||
momentum_settings.lockscreen_skip_animation = value;
|
||||
app->save_settings = true;
|
||||
}
|
||||
|
||||
void momentum_app_scene_interface_lockscreen_on_enter(void* context) {
|
||||
MomentumApp* app = context;
|
||||
VariableItemList* var_item_list = app->var_item_list;
|
||||
@@ -220,6 +229,16 @@ void momentum_app_scene_interface_lockscreen_on_enter(void* context) {
|
||||
variable_item_set_current_value_text(
|
||||
item, momentum_settings.lockscreen_transparent ? "ON" : "OFF");
|
||||
|
||||
item = variable_item_list_add(
|
||||
var_item_list,
|
||||
"Skip Sliding Animation",
|
||||
2,
|
||||
momentum_app_scene_interface_lockscreen_lockscreen_skip_animation_changed,
|
||||
app);
|
||||
variable_item_set_current_value_index(item, momentum_settings.lockscreen_skip_animation);
|
||||
variable_item_set_current_value_text(
|
||||
item, momentum_settings.lockscreen_skip_animation ? "ON" : "OFF");
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
var_item_list, momentum_app_scene_interface_lockscreen_var_item_list_callback, app);
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
enum VarItemListIndex {
|
||||
VarItemListIndexMenuStyle,
|
||||
VarItemListIndexResetMenu,
|
||||
VarItemListIndexApp,
|
||||
VarItemListIndexAddApp,
|
||||
VarItemListIndexMoveApp,
|
||||
VarItemListIndexRemoveApp,
|
||||
VarItemListIndexItem,
|
||||
VarItemListIndexAddItem,
|
||||
VarItemListIndexMoveItem,
|
||||
VarItemListIndexRemoveItem,
|
||||
};
|
||||
|
||||
void momentum_app_scene_interface_mainmenu_var_item_list_callback(void* context, uint32_t index) {
|
||||
@@ -40,7 +40,7 @@ static void momentum_app_scene_interface_mainmenu_app_changed(VariableItem* item
|
||||
item, *CharList_get(app->mainmenu_app_labels, app->mainmenu_app_index));
|
||||
size_t count = CharList_size(app->mainmenu_app_labels);
|
||||
char label[20];
|
||||
snprintf(label, sizeof(label), "App %u/%u", 1 + app->mainmenu_app_index, count);
|
||||
snprintf(label, sizeof(label), "Item %u/%u", 1 + app->mainmenu_app_index, count);
|
||||
variable_item_set_item_label(item, label);
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ static void momentum_app_scene_interface_mainmenu_move_app_changed(VariableItem*
|
||||
CharList_swap_at(app->mainmenu_app_exes, idx, idx - 1);
|
||||
app->mainmenu_app_index--;
|
||||
}
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, VarItemListIndexMoveApp);
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, VarItemListIndexMoveItem);
|
||||
}
|
||||
variable_item_set_current_value_index(item, 1);
|
||||
}
|
||||
@@ -84,11 +84,11 @@ void momentum_app_scene_interface_mainmenu_on_enter(void* context) {
|
||||
|
||||
size_t count = CharList_size(app->mainmenu_app_labels);
|
||||
item = variable_item_list_add(
|
||||
var_item_list, "App", count, momentum_app_scene_interface_mainmenu_app_changed, app);
|
||||
var_item_list, "Item", count, momentum_app_scene_interface_mainmenu_app_changed, app);
|
||||
if(count) {
|
||||
app->mainmenu_app_index = CLAMP(app->mainmenu_app_index, count - 1, 0U);
|
||||
char label[20];
|
||||
snprintf(label, sizeof(label), "App %u/%u", 1 + app->mainmenu_app_index, count);
|
||||
char label[21];
|
||||
snprintf(label, sizeof(label), "Item %u/%u", 1 + app->mainmenu_app_index, count);
|
||||
variable_item_set_item_label(item, label);
|
||||
variable_item_set_current_value_text(
|
||||
item, *CharList_get(app->mainmenu_app_labels, app->mainmenu_app_index));
|
||||
@@ -98,15 +98,15 @@ void momentum_app_scene_interface_mainmenu_on_enter(void* context) {
|
||||
}
|
||||
variable_item_set_current_value_index(item, app->mainmenu_app_index);
|
||||
|
||||
variable_item_list_add(var_item_list, "Add App", 0, NULL, app);
|
||||
variable_item_list_add(var_item_list, "Add Item", 0, NULL, app);
|
||||
|
||||
item = variable_item_list_add(
|
||||
var_item_list, "Move App", 3, momentum_app_scene_interface_mainmenu_move_app_changed, app);
|
||||
var_item_list, "Move Item", 3, momentum_app_scene_interface_mainmenu_move_app_changed, app);
|
||||
variable_item_set_current_value_text(item, "");
|
||||
variable_item_set_current_value_index(item, 1);
|
||||
variable_item_set_locked(item, count < 2, "Can't move\nwith less\nthan 2 apps!");
|
||||
|
||||
variable_item_list_add(var_item_list, "Remove App", 0, NULL, app);
|
||||
variable_item_list_add(var_item_list, "Remove Item", 0, NULL, app);
|
||||
|
||||
variable_item_list_set_enter_callback(
|
||||
var_item_list, momentum_app_scene_interface_mainmenu_var_item_list_callback, app);
|
||||
@@ -133,7 +133,7 @@ bool momentum_app_scene_interface_mainmenu_on_event(void* context, SceneManagerE
|
||||
case VarItemListIndexResetMenu:
|
||||
scene_manager_next_scene(app->scene_manager, MomentumAppSceneInterfaceMainmenuReset);
|
||||
break;
|
||||
case VarItemListIndexRemoveApp:
|
||||
case VarItemListIndexRemoveItem:
|
||||
if(!CharList_size(app->mainmenu_app_labels)) break;
|
||||
if(!CharList_size(app->mainmenu_app_exes)) break;
|
||||
free(*CharList_get(app->mainmenu_app_labels, app->mainmenu_app_index));
|
||||
@@ -143,27 +143,27 @@ bool momentum_app_scene_interface_mainmenu_on_event(void* context, SceneManagerE
|
||||
CharList_remove_v(
|
||||
app->mainmenu_app_exes, app->mainmenu_app_index, app->mainmenu_app_index + 1);
|
||||
/* fall through */
|
||||
case VarItemListIndexMoveApp: {
|
||||
case VarItemListIndexMoveItem: {
|
||||
app->save_mainmenu_apps = true;
|
||||
size_t count = CharList_size(app->mainmenu_app_labels);
|
||||
VariableItem* item = variable_item_list_get(app->var_item_list, VarItemListIndexApp);
|
||||
VariableItem* item = variable_item_list_get(app->var_item_list, VarItemListIndexItem);
|
||||
if(count) {
|
||||
app->mainmenu_app_index = CLAMP(app->mainmenu_app_index, count - 1, 0U);
|
||||
char label[20];
|
||||
snprintf(label, sizeof(label), "App %u/%u", 1 + app->mainmenu_app_index, count);
|
||||
char label[21];
|
||||
snprintf(label, sizeof(label), "Item %u/%u", 1 + app->mainmenu_app_index, count);
|
||||
variable_item_set_item_label(item, label);
|
||||
variable_item_set_current_value_text(
|
||||
item, *CharList_get(app->mainmenu_app_labels, app->mainmenu_app_index));
|
||||
} else {
|
||||
app->mainmenu_app_index = 0;
|
||||
variable_item_set_item_label(item, "App");
|
||||
variable_item_set_item_label(item, "Item");
|
||||
variable_item_set_current_value_text(item, "None");
|
||||
}
|
||||
variable_item_set_current_value_index(item, app->mainmenu_app_index);
|
||||
variable_item_set_values_count(item, count);
|
||||
break;
|
||||
}
|
||||
case VarItemListIndexAddApp:
|
||||
case VarItemListIndexAddItem:
|
||||
scene_manager_next_scene(app->scene_manager, MomentumAppSceneInterfaceMainmenuAdd);
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexMainApp,
|
||||
SubmenuIndexExternalApp,
|
||||
SubmenuIndexFileDirectory,
|
||||
};
|
||||
|
||||
static bool fap_selector_item_callback(
|
||||
@@ -26,28 +27,39 @@ static void
|
||||
case SubmenuIndexMainApp:
|
||||
scene_manager_next_scene(app->scene_manager, MomentumAppSceneInterfaceMainmenuAddMain);
|
||||
break;
|
||||
case SubmenuIndexExternalApp: {
|
||||
case SubmenuIndexExternalApp:
|
||||
case SubmenuIndexFileDirectory: {
|
||||
const bool is_file_dir = index == SubmenuIndexFileDirectory;
|
||||
const DialogsFileBrowserOptions browser_options = {
|
||||
.extension = ".fap",
|
||||
.extension = is_file_dir ? "*" : ".fap",
|
||||
.icon = &I_unknown_10px,
|
||||
.skip_assets = true,
|
||||
.hide_ext = true,
|
||||
.hide_ext = !is_file_dir,
|
||||
.item_loader_callback = fap_selector_item_callback,
|
||||
.item_loader_context = app,
|
||||
.base_path = EXT_PATH("apps"),
|
||||
.base_path = is_file_dir ? STORAGE_EXT_PATH_PREFIX : EXT_PATH("apps"),
|
||||
.select_right = is_file_dir,
|
||||
};
|
||||
FuriString* temp_path = furi_string_alloc_set_str(EXT_PATH("apps"));
|
||||
FuriString* temp_path = furi_string_alloc_set_str(browser_options.base_path);
|
||||
|
||||
if(dialog_file_browser_show(app->dialogs, temp_path, temp_path, &browser_options)) {
|
||||
CharList_push_back(app->mainmenu_app_exes, strdup(furi_string_get_cstr(temp_path)));
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
uint8_t* unused_icon = malloc(FAP_MANIFEST_MAX_ICON_SIZE);
|
||||
flipper_application_load_name_and_icon(temp_path, storage, &unused_icon, temp_path);
|
||||
free(unused_icon);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
if(furi_string_start_with_str(temp_path, "[")) {
|
||||
size_t trim = furi_string_search_str(temp_path, "] ", 1);
|
||||
if(trim != FURI_STRING_FAILURE) {
|
||||
furi_string_right(temp_path, trim + 2);
|
||||
if(is_file_dir) {
|
||||
const char* path = furi_string_get_cstr(temp_path);
|
||||
const char* end = strrchr(path, '/');
|
||||
furi_string_set_str(temp_path, end ? end + 1 : path);
|
||||
} else {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
uint8_t* unused_icon = malloc(FAP_MANIFEST_MAX_ICON_SIZE);
|
||||
flipper_application_load_name_and_icon(
|
||||
temp_path, storage, &unused_icon, temp_path);
|
||||
free(unused_icon);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
if(furi_string_start_with_str(temp_path, "[")) {
|
||||
size_t trim = furi_string_search_str(temp_path, "] ", 1);
|
||||
if(trim != FURI_STRING_FAILURE) {
|
||||
furi_string_right(temp_path, trim + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
CharList_push_back(app->mainmenu_app_labels, strdup(furi_string_get_cstr(temp_path)));
|
||||
@@ -68,7 +80,7 @@ void momentum_app_scene_interface_mainmenu_add_on_enter(void* context) {
|
||||
MomentumApp* app = context;
|
||||
Submenu* submenu = app->submenu;
|
||||
|
||||
submenu_set_header(submenu, "Add Menu App:");
|
||||
submenu_set_header(submenu, "Add Menu Item:");
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
@@ -84,6 +96,13 @@ void momentum_app_scene_interface_mainmenu_add_on_enter(void* context) {
|
||||
momentum_app_scene_interface_mainmenu_add_submenu_callback,
|
||||
app);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"File / Directory (right btn)",
|
||||
SubmenuIndexFileDirectory,
|
||||
momentum_app_scene_interface_mainmenu_add_submenu_callback,
|
||||
app);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, MomentumAppViewSubmenu);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ void momentum_app_scene_interface_mainmenu_reset_on_enter(void* context) {
|
||||
MomentumApp* app = context;
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, "Reset Menu Apps?", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_header(dialog_ex, "Reset Menu Items?", 64, 10, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_text(dialog_ex, "Your edits will be lost!", 64, 32, AlignCenter, AlignCenter);
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Cancel");
|
||||
dialog_ex_set_right_button_text(dialog_ex, "Reset");
|
||||
|
||||
@@ -347,8 +347,8 @@ bool saflok_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
uint8_t interval_minute = decodedBA[10] & 0x3F;
|
||||
|
||||
// Bytes 11-13: Creation date since 1980 Jan 1st
|
||||
uint16_t creation_year = (((decodedBA[11] & 0xF0) >> 4) + SAFLOK_YEAR_OFFSET) |
|
||||
creation_year_bits;
|
||||
uint16_t creation_year =
|
||||
(creation_year_bits | ((decodedBA[11] & 0xF0) >> 4)) + SAFLOK_YEAR_OFFSET;
|
||||
uint8_t creation_month = decodedBA[11] & 0x0F;
|
||||
uint8_t creation_day = (decodedBA[12] >> 3) & 0x1F;
|
||||
uint8_t creation_hour = ((decodedBA[12] & 0x07) << 2) | (decodedBA[13] >> 6);
|
||||
|
||||
@@ -82,16 +82,20 @@ typedef enum {
|
||||
SetTypeSomfyTelis,
|
||||
SetTypeANMotorsAT4,
|
||||
SetTypeAlutechAT4N,
|
||||
SetTypePhoenix_V2_433,
|
||||
SetTypeHCS101_433_92,
|
||||
SetTypeDoorHan_315_00,
|
||||
SetTypeDoorHan_433_92,
|
||||
SetTypeBeninca433,
|
||||
SetTypeBeninca868,
|
||||
SetTypeComunello433,
|
||||
SetTypeComunello868,
|
||||
SetTypeAllmatic433,
|
||||
SetTypeAllmatic868,
|
||||
SetTypeCenturion433,
|
||||
SetTypeMonarch433,
|
||||
SetTypeJollyMotors433,
|
||||
SetTypeMotorline433,
|
||||
SetTypeSommer_FM_434,
|
||||
SetTypeSommer_FM_868,
|
||||
SetTypeSommer_FM238_434,
|
||||
@@ -132,6 +136,9 @@ typedef enum {
|
||||
SetTypeHollarm_433,
|
||||
SetTypeReversRB2_433,
|
||||
SetTypeMarantec24_868,
|
||||
SetTypeMarantec_433,
|
||||
SetTypeMarantec_868,
|
||||
SetTypeRoger_433,
|
||||
SetTypeLinear_300_00,
|
||||
// SetTypeNeroSketch, //Deleted in OFW
|
||||
// SetTypeNeroRadio, //Deleted in OFW
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <lib/subghz/protocols/secplus_v1.h>
|
||||
#include <lib/subghz/protocols/secplus_v2.h>
|
||||
#include <lib/subghz/protocols/nice_flor_s.h>
|
||||
#include <lib/subghz/protocols/marantec.h>
|
||||
|
||||
#include <flipper_format/flipper_format_i.h>
|
||||
#include <lib/toolbox/stream/stream.h>
|
||||
@@ -383,6 +384,34 @@ bool subghz_txrx_gen_secplus_v1_protocol(
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool subghz_txrx_gen_phoenix_v2_protocol(
|
||||
void* context,
|
||||
const char* preset_name,
|
||||
uint32_t frequency,
|
||||
uint32_t serial,
|
||||
uint16_t cnt) {
|
||||
SubGhzTxRx* txrx = context;
|
||||
|
||||
bool res = false;
|
||||
|
||||
txrx->transmitter =
|
||||
subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_PHOENIX_V2_NAME);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, NAN, NAN, NULL, 0);
|
||||
|
||||
if(txrx->transmitter && subghz_protocol_phoenix_v2_create_data(
|
||||
subghz_transmitter_get_protocol_instance(txrx->transmitter),
|
||||
txrx->fff_data,
|
||||
serial,
|
||||
cnt,
|
||||
txrx->preset)) {
|
||||
res = true;
|
||||
}
|
||||
|
||||
subghz_transmitter_free(txrx->transmitter);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void subghz_txrx_gen_serial_gangqi(uint64_t* result_key) {
|
||||
uint64_t randkey = (uint64_t)rand();
|
||||
uint16_t serial = (uint16_t)((randkey) & 0xFFFF);
|
||||
@@ -395,3 +424,27 @@ void subghz_txrx_gen_serial_gangqi(uint64_t* result_key) {
|
||||
// serial | const_and_button
|
||||
*result_key = (serial << 18) | (const_and_button << 10) | (bytesum << 2);
|
||||
}
|
||||
|
||||
void subghz_txrx_gen_key_marantec(uint64_t* result_key) {
|
||||
uint64_t randkey = (uint64_t)rand();
|
||||
uint32_t serial = (uint32_t)((randkey) & 0xFFFFF);
|
||||
// 0x130 is the constant
|
||||
// 0x4 is the button code
|
||||
// 0x86 is the serial constant
|
||||
// serial is random value that we pre generate above
|
||||
// At the end we will put the crc sum
|
||||
uint64_t full_key_no_crc = (uint64_t)((uint64_t)0x130 << 40 | (uint64_t)serial << 20 |
|
||||
(uint64_t)0x4 << 16 | (uint64_t)0x86 << 8);
|
||||
|
||||
uint8_t tdata[6] = {
|
||||
full_key_no_crc >> 48,
|
||||
full_key_no_crc >> 40,
|
||||
full_key_no_crc >> 32,
|
||||
full_key_no_crc >> 24,
|
||||
full_key_no_crc >> 16,
|
||||
full_key_no_crc >> 8};
|
||||
|
||||
uint8_t crc = subghz_protocol_marantec_crc8(tdata, sizeof(tdata));
|
||||
|
||||
*result_key = ((full_key_no_crc >> 8) << 8) | crc;
|
||||
}
|
||||
|
||||
@@ -115,6 +115,13 @@ bool subghz_txrx_gen_came_atomo_protocol(
|
||||
uint32_t serial,
|
||||
uint16_t cnt);
|
||||
|
||||
bool subghz_txrx_gen_phoenix_v2_protocol(
|
||||
void* context,
|
||||
const char* preset_name,
|
||||
uint32_t frequency,
|
||||
uint32_t serial,
|
||||
uint16_t cnt);
|
||||
|
||||
/**
|
||||
* Generate data SecPlus v2 protocol
|
||||
*
|
||||
@@ -153,3 +160,10 @@ bool subghz_txrx_gen_secplus_v1_protocol(
|
||||
* @return uint64_t if success
|
||||
*/
|
||||
void subghz_txrx_gen_serial_gangqi(uint64_t* result_key);
|
||||
|
||||
/**
|
||||
* Generate key for Marantec protocol
|
||||
*
|
||||
* @param result_key Pointer to a uint64_t where the key will be stored
|
||||
*/
|
||||
void subghz_txrx_gen_key_marantec(uint64_t* result_key);
|
||||
|
||||
@@ -1,63 +1,69 @@
|
||||
Filetype: Flipper SubGhz Keystore File
|
||||
Version: 0
|
||||
Encryption: 1
|
||||
IV: 4E 6F 20 66 75 72 69 20 63 68 65 63 6B 3F 21 3F
|
||||
2F0767B5B190608EB032D12BFA937D760A77D08D37F851E940767F1915E97ACF
|
||||
332F8DCCFDBF0485EC2EEED0C279F277E52A86A93BC5E4E96BE5F7276CC66713
|
||||
D9A02CC785FC0495063C424B0B1BAE7C120A2C24D4C0EE743F5D216718B16490
|
||||
4D9DD617090BDB100986B6987CAAC3652D2ADAB1AD9E368C5806D98562FF6B2F
|
||||
28D21748FF3826FA13C785A6721CC5927C81EDAB0C5CF31C92EAFF12AA91608298485D8A3AB443640237372ADF0DDC49
|
||||
5058E12C0A41EDCB5C0812554F619DADFB6E895B94421952ECD9255A04EE5E1A
|
||||
83A3EB8B22D94487A6B0F37856FB6AE9F42272BF25E1AE06DE03AA881A12D15F
|
||||
D0E207DE64402B43ECD0C341216B6BCDC449508116E81D8ACDE7FA0BFBEA56F7
|
||||
6C4F723DE3B775D4C07E12ED3C369250B4D2089ADE2207816DED130D4B498CDF
|
||||
B041911C56555E5F4676BF16819F61BF7A92402EB0427B8C2E7367B0AEA6B53C
|
||||
1AD460260F20146A763BF6D4CD26DF5139EE29FFF8B53F6C5367EA779E1BEE56D5DFD872EA0268FE27204175925079AA
|
||||
B1A9331AED36137CD078536A67775E2880D3CD7305373BC44A5649435E466AD2DC9FDE8AC1F572EF094D4B438C9509EA
|
||||
105819300A9152F16E3478151799ECBBB7CCCE63DADA3F6C6D16D46830E1E035
|
||||
354186E04BC90D672F76A427FC1CD35C2EFAE8D4D1C36247FFB71ACB862A3601
|
||||
84B533148282D0D8121E5BBBBD39DE16F398365B015E02417ECC535C88EB9C57
|
||||
E899C9DC779F82E798EE923D257D5F051E1254DCDA2A6A955882727AA8E98ED8
|
||||
B8EC34F9B75E61D34E9C075A5316FAFC395E8FBA4F76B215620C5B5C76C53DB7BF316E53582161AD20F64CAB8E3347B2
|
||||
966C3B0873F48889B51D7D9DACC0674CBC95B06E231E30F235C826285D529A54
|
||||
370DED014764D7278E342D3AB631AB37D8F8A8FAE08987E03D7FC279DEEEB913
|
||||
2318A2DA42EEA2A35FFC9BDFBB244DF3FF6933010D74B3465336C0E37FFDC48A200568F8D6003AB215388734B8AC1F20
|
||||
475B35437FECEE9792F53A671252E78566AA9894DE7A4DEC9AED70834864E804E87478009F424CE1424C00F162BB03C5
|
||||
01CE6251ED9682BA6366075081167196CD740D346C4DAC4E0012C7951C475AE7
|
||||
CB225891F937CA491B711AA942B04C61C7CFA6A8E912D91D0906B401737E03B4
|
||||
F35D279815DEF19C9C5BC9ED55E7C1A0366E37DCD0A93E6E168B5A2602201C7B
|
||||
3569D8DF2490797D40978F8325B94CC707383FEA1B46240BFDAECFEFB1F8176D
|
||||
3D7BAF13573BBF3102757C68D52236638CC57126FF3795A604CFFA2D3F1B9C26
|
||||
B9102C87D7DBCF35463F38B6B80B70408968B6E01A47F6A7E8A3E87A0577B4ED
|
||||
7673FAC14D94ABF72800A78E2DC4CAF2166FBB24719C22CFC1010492F4C87734
|
||||
1AF74DA07EA3A418EB86BB7ABAD6192B8E5A53F61B3E74CB898CB3EE4A7E244A
|
||||
832D18C44062DDE856384E19D1417FA48D809C2CB2107CDEC5281943559791A6
|
||||
CD482A8FAB2A2CBE25A0B4A4788F274CA7095AA24508C00DBB78DD12BFB11C37
|
||||
EAC52E802DB76B51058752D7EFA91BCB1212AB96B589F9A88465195C1DE3242E
|
||||
96CC75952A513AB5FE62A69AB6CDDA93C2156A3EA607C25B3201CE7284B3DAA9
|
||||
986E71EE87E860192141A1453929E575706E3FE72B7A9FEF5ACA696388649EB6
|
||||
FFF89FECC1C01FA3F266B95BDEF61A16F514E59599DAA07E908C604E9FE686C0
|
||||
ACC159D4AE78E26B5A1468F69D961028D0BF962D03E865415E7FE746553FEF15
|
||||
0FF46B2F9D4E907B9924675081D17C38C09957AA2F4C3C1F5568461DBA850F6301328CDC0FCEE83C7E8BA00CF8FC0F97
|
||||
7FD793C05E499739C3C4F8CC1D2D205A55928AB5BC60752A81D86DFBE30C50BD
|
||||
CE444F4A1BEB38C9E93579E1B7FB3E90B4F85D8DA94DFC622272DED093A35192
|
||||
C7C31D8AB9D717FAF842F850A3E0D6B404EB9A980D9679362ABA90594B1957AB
|
||||
1D48A6CFFBB6F14DD5BED1F8E74E6CC0292150546EDD76361792677434A8FE5F
|
||||
F7335B8877DDF17F0737ECF77E2E6286E78534CE28E72077360E8E24D7750DFE
|
||||
51051D9A8D5941661EBCF437B4640E6DA0B9B27518D761F3EF26BF7EABC691D4
|
||||
79F279733E18393FEDB50D654A0D0A532A64BED5ACBD13319439EEC007BC359C
|
||||
646666FDB75D439C0427A9E3EF47F145DBD4FF5FE2E244909D74F075B24FF5A9B47E7AF98271210057D937A0E4B1F46D
|
||||
DE7E814A2BD4D8823D9F2380EFAFFA1380A90391F87CBF24CE46BD46205EABAB
|
||||
1335C4C3E819E942F5C212E9BEFAF5D984316C0A2FF6E9886886B565625618A9
|
||||
65386F906F18FF9C3A20AB57F3241D4975FE312ACDEB7FB1B91F2B816CAA46E7
|
||||
DF8A8B33782D56667F4C98F8F91B49B71A9E83AF015D8841986D41663233A0DC
|
||||
27264455248878BB226FA1DED0922BD10313FF65F8A6A0E3CCDFB77890C838BB
|
||||
43A08F784F36A3E8049BA63A401F3F15B3CA2ED263F8638595B5F22A0B081369
|
||||
F9F82F89C15AD970320E3D7A83B272EB00CD0ED657E4D230AB33C995859EA77F
|
||||
70AD020D172E18E1011DF88E5F934F03F34DCE8148F8053B4FFA6F92CAC9FC93
|
||||
2B845F67BAB432CED64F2D68454A2B4B3BC46FFDC2A16D3340360C7BEA110BBB
|
||||
B85F16A2370B278FDB3C85E6455B8DA239D6413B727839DEFBCB5628A6C747266291AB9D9F4F5DA1826B219C1A29F956
|
||||
FFB7B10D96F241FDB994008AF85EC85D147A97AA599D05F5EE1BB2FC27644A26
|
||||
0BD42CA312CBBCAE556AA0159EC2CC2FA70BBB00D8DF7B63BBEA60A282481AED
|
||||
9CC73810056A21EA6F311B01BA7F44655A075D1F60947FBC9B6924C3BD0ED819
|
||||
024FCB96977ECA1C0D4B9C7C461361329D96E5AFF315124FEFC0DF2A400DE312F45D602DB40CD4EB088F144EB0B8DF41
|
||||
IV: 46 75 72 72 79 20 52 6F 63 6B 65 74 21 21 21 30
|
||||
05176EEFAC177FE261FE3EB5C8E103BE7CF9F2FEB32BDD6BB63D22EE9C17B9D2
|
||||
B645E3CAC0D5E26891249D326BCEB09850E4FB8F8E86A466E97E83437A9E0041
|
||||
AA4255FFA1ADE8FB840F80A93F8F1A2D1E39051131D24DE7258D66A8CF2066CF
|
||||
13ACA390FD5254B024084D5D1F41B8DDF5304FF00C3C85A9C26CD13A7A268654
|
||||
4CFBF498D5E2C85496985E83D91B0F4229A925E16A90C6712750032C3699EE0AA5D04123E579B6121573FC61766E89AD
|
||||
93DADC2AE4235470E171E0E85D24D04A84C37187284C38D1CBB48666FDA8CD6C
|
||||
DB13D8CCC0CB07685F29F33AE07DA2FD14C2AE4F4D001DB88465D5CFE8CFDAA9
|
||||
E51CD1B5074B63D26E274218A0AB3B2E435454EE094DCA5679F35477658A72F9
|
||||
10AFD5FD9C296E67EDD9504A60BA9EF84556F40213DEC4DE44F99B088BCC6A57
|
||||
EF7AA55F6A473DE093D648240D5FCEB05F8B3295DC37B3E83239A4AF320CD688
|
||||
A22892E71B9D0D7FAF92B27C724E76C4A6824DBE5F083F1006D11E42D153C4AC98D0A11C6A8D62F5921A24ECC7437485
|
||||
7A25416E390D81DA68A59C3BA30D4B7FC8269B5E0DAF77CA3A857B6F478A050585918485AEE72D375F02D177CB296E31
|
||||
94004BA0BB1E47965E60025949EF4CC2738C463F57C97FD2A89C76CCCDEA5397
|
||||
111CB1C19863A0165521D974F838CE718DA07948A8D9A8A7490E75032A62ECA2
|
||||
17B6E27C69FA002F6CF23D719DFE595140BEFA5083D12E774CF89E2CED53D68D
|
||||
73311E0FF8ABB3E9461AD14A4F52791647A50E2102D3B74188A73C35BC14EB55
|
||||
54E15840A6A6DCA85275E38E4218EE2B539E9E468E24C49428DA363C955C5FC81ACEE79EEB941B83EE4147A0817043BD
|
||||
7D0FBB417B99B3C6AB18C7B2DC82582D2DCD1E10515028874E73254188F7FEE9
|
||||
3F6E89BBCC133B85945234A8201539ECD8796909CC81FE67673F8DE1ECA63045
|
||||
39554C0DC1C3694FAAFF65537FF710D9593B7B461E011FC39D014F253F0432533A40276D8259AFD8C957A378237D574F
|
||||
E60F6CD7063B85F0F20ACB7E7A42B03DE4A9F6CCA54CB7F036AFA23A27D3E9E006BD523E5356260AA78206D9276E6E57
|
||||
9EB252EDA9352B966EC4F053D5B013772361D2AD4B217EF33F46A5CEC97A00F3
|
||||
AA6773E79BC6D76314BB523FDF203358E01ECB2BBCF3B5DD1EBD043663C74B05
|
||||
29B29A50F3F27F4D8C7B0FADA98CC004A7871078DAD1CBAC4846862C3DF82E02
|
||||
6E3A479D4334FF05606899B0383116125056A316621B279F904A02B842918C59
|
||||
3991732015F4A213E9912E34AC92515D88010C07DA0B118AD6F64A05DC38D2C5
|
||||
550B1866F7493C75812DF85DDADC38AF21D9B58189E4EE99A021328523881A9D
|
||||
77960CA031D28362586100F17DF94FF4E7D6EFAFAF23952887F9DF0507825A99
|
||||
01E6FC89E97B7729BF4D1ED8041F69005181BF3639F939C5833B009E96B9F2F7
|
||||
D1CC7C536706ECFC5826C8933135D2B110996F1CB13388A702B8453DA40E40AD
|
||||
B64D2F1E1A80E6DAB92283A512B40DB7FFC519F394AA94CC86C8532F69949723
|
||||
6399409A0AC0298DEDA76037C83042FC0870132CFF7F82E54AD0966BE16AC882
|
||||
D310536FA78F95BB0B408676990AA937117717BADE9D3B975C0ECE10FB586A1B
|
||||
A8149C0581DCC291D037E96EF321DB6214BD7CB25F1696226A9FE750AA23B334
|
||||
BA3BEBD564D8F571202CD6FE89BC33F89C8E01C03AE0814F2BEF37C33CE874B4
|
||||
88CD81AC7605A7F6EFF85FD62C65E0C9945335CFC085B92B27B69648C6E5BF6B
|
||||
8057C7CB5071DFFFAE4804FD9EC1EC1D3F54D06514906A34B17F6B6CB45A9D473992DF6BC8A9F9E146E39D6163209CC6
|
||||
9ABC8814C8FD1AB254374150177616F5C7B43049473C84329BEC855578B96002
|
||||
8BCA39A498B00245C71D94E3160CEE8ACA5BEB18AE0AD64A385AFCC018E99744
|
||||
5AD75C51CA5AE5FA9BBC6A41576C745F265CC28FC4DA2AD230B6692CF151FD61
|
||||
E86092E04CD72D874A92DE838035E811E75E411049C0A7BD0FE2AA9C802BE5AB
|
||||
CE70ADB22E85747FDC064F0B5974385CD57D41D376CE1C7490C1BEC8A3FC5A7A
|
||||
8F096E0A11682DB315825213D3DB5D725555C1CDF444169EB919E47E0F0FA6F7
|
||||
AD9C9A694D807BA77E5A54B248A88B55000757203D931506255BF8F4215C00D3
|
||||
F0E804B6C6B6E91916CB73EB44FB2D1992400BC90ED8B22DF5D038317588341207D74E08C00E529DF2CF2A64F2C7C0FF
|
||||
72212FCEED35E9C3A176B67DCDB84B284F4DFDCD0ECE8D3F6089C58C2B8A616C
|
||||
000F9F746BFB47FC10B23E3F08C2A84BCB3870D0C5AE974472849699884BC929
|
||||
7B8F9AB04E5F86D6DDCF6164A25EA927788A03F57977FC5C55E1D565279B09C4
|
||||
0E9CDCD07D1D4F1429E59F81B524960A75F19A464798C7E822E52728AC83784A
|
||||
F2DE2B108A1476BB6F85DD3CCB0F0527627B45179092BA7A56D5971490E3875C
|
||||
7F307358D988FEA12648739F58DD249EBDF0B1C44B73BA547C50EB576C071DAE
|
||||
2DFBA988592CEF3B62A76183DBA727E734359B89F53AFF3160441EF8709FC633
|
||||
57F7DC38DDC87C19CE956BC44C638DEF34D814A7BAB0AC8AD61855143FD984FD
|
||||
A8AADB687251FA6AC2BBC8EF1E3FA621893293DFBD8C1D07971BF82F22A00DC3
|
||||
65AEA1EE34E316C769E551AC2309D07FC2ED92EA044674E3A99CD7B543C730EB
|
||||
968ECC790E5590E7EB22AFD3546C28F4EB87EA4CEE35F72DDFE7153F74611EAA
|
||||
0F937930D4E1BDF0B729277CF94A47064BCB959938C70CDB3AC3C65DA68DA1FB
|
||||
A8AB66375D59E112104CD81B819D618BE43D6A6F159BAD35583653EF3547D25D
|
||||
A81D5DE2102F05D50750DC37C26E9C9502FA89EF98A2EB1EA546EE48C628E9C4
|
||||
EAFDE0A8936AF8EF718027937BC17CEF691E570996B403CF4762240D267EB305
|
||||
C48686348F0A94B07BC60AB825C1A0791C20DBBDD7DAE0ED47E8A7FBD9334EACF8E33DCEC36963E87929260DF769520B
|
||||
493D53BD7BB2B3E081AE793A3BADB3AB0F33C95B83677715D6DE2922F2BEC892
|
||||
63FFD3D8CAB980E45D49253A69C99A6813CBE6013992EFBC862173BAD0E26373
|
||||
2EF88F43C5A76EC87E02B780585B10957F4EA386F96710FAB98BC2C1E214DBFA
|
||||
A021CFA0E72AADFD75BC67FBE9345082B0A8B31782E933E81196F84B1797D83E8B2F81E1CF5C3F026D11B9DFC95222E2
|
||||
|
||||
@@ -20,13 +20,18 @@ static const char* submenu_names[SetTypeMAX] = {
|
||||
[SetTypeSomfyTelis] = "Somfy Telis 433MHz",
|
||||
[SetTypeANMotorsAT4] = "AN-Motors AT4 433MHz",
|
||||
[SetTypeAlutechAT4N] = "Alutech AT4N 433MHz",
|
||||
[SetTypeRoger_433] = "Roger 433MHz",
|
||||
[SetTypePhoenix_V2_433] = "V2 Phoenix 433MHz",
|
||||
[SetTypeHCS101_433_92] = "KL: HCS101 433MHz",
|
||||
[SetTypeDoorHan_315_00] = "KL: DoorHan 315MHz",
|
||||
[SetTypeDoorHan_433_92] = "KL: DoorHan 433MHz",
|
||||
[SetTypeBeninca433] = "KL: Beninca 433MHz",
|
||||
[SetTypeBeninca868] = "KL: Beninca 868MHz",
|
||||
[SetTypeComunello433] = "KL: Comunello 433MHz",
|
||||
[SetTypeComunello868] = "KL: Comunello 868MHz",
|
||||
[SetTypeAllmatic433] = "KL: Allmatic 433MHz",
|
||||
[SetTypeAllmatic868] = "KL: Allmatic 868MHz",
|
||||
[SetTypeMotorline433] = "KL: Motorline 433MHz",
|
||||
[SetTypeCenturion433] = "KL: Centurion 433MHz",
|
||||
[SetTypeMonarch433] = "KL: Monarch 433MHz",
|
||||
[SetTypeJollyMotors433] = "KL: Jolly Mot. 433MHz",
|
||||
@@ -69,6 +74,8 @@ static const char* submenu_names[SetTypeMAX] = {
|
||||
[SetTypeHollarm_433] = "Hollarm 433MHz",
|
||||
[SetTypeReversRB2_433] = "Revers RB2 433MHz",
|
||||
[SetTypeMarantec24_868] = "Marantec24 868MHz",
|
||||
[SetTypeMarantec_433] = "Marantec 433MHz",
|
||||
[SetTypeMarantec_868] = "Marantec 868MHz",
|
||||
[SetTypeBETT_433] = "BETT 433MHz",
|
||||
[SetTypeLinear_300_00] = "Linear 300MHz",
|
||||
// [SetTypeNeroSketch] = "Nero Sketch", // Deleted in OFW
|
||||
@@ -108,6 +115,7 @@ typedef enum {
|
||||
GenNiceFlorS,
|
||||
GenSecPlus1,
|
||||
GenSecPlus2,
|
||||
GenPhoenixV2,
|
||||
} GenType;
|
||||
|
||||
typedef struct {
|
||||
@@ -166,6 +174,10 @@ typedef struct {
|
||||
uint8_t btn;
|
||||
uint32_t cnt;
|
||||
} sec_plus_2;
|
||||
struct {
|
||||
uint32_t serial;
|
||||
uint16_t cnt;
|
||||
} phoenix_v2;
|
||||
};
|
||||
} GenInfo;
|
||||
|
||||
@@ -190,6 +202,9 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
|
||||
uint64_t gangqi_key;
|
||||
subghz_txrx_gen_serial_gangqi(&gangqi_key);
|
||||
|
||||
uint64_t marantec_key;
|
||||
subghz_txrx_gen_key_marantec(&marantec_key);
|
||||
|
||||
GenInfo gen_info = {0};
|
||||
switch(event.event) {
|
||||
case SetTypePricenton433:
|
||||
@@ -272,6 +287,16 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
|
||||
.data.bits = 24,
|
||||
.data.te = 0};
|
||||
break;
|
||||
case SetTypeRoger_433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenData,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.data.name = SUBGHZ_PROTOCOL_ROGER_NAME,
|
||||
.data.key = (key & 0xFFFF000) | 0x0000101, // button code 0x1 and (crc?) is 0x01
|
||||
.data.bits = 28,
|
||||
.data.te = 0};
|
||||
break;
|
||||
case SetTypeLinear_300_00:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenData,
|
||||
@@ -358,6 +383,28 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
|
||||
.data.bits = 24,
|
||||
.data.te = 0};
|
||||
break;
|
||||
case SetTypeMarantec_433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenData,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.data.name =
|
||||
SUBGHZ_PROTOCOL_MARANTEC_NAME, // Button code is 0x4 and crc sum to the end
|
||||
.data.key = marantec_key,
|
||||
.data.bits = 49,
|
||||
.data.te = 0};
|
||||
break;
|
||||
case SetTypeMarantec_868:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenData,
|
||||
.mod = "AM650",
|
||||
.freq = 868350000,
|
||||
.data.name =
|
||||
SUBGHZ_PROTOCOL_MARANTEC_NAME, // Button code is 0x4 and crc sum to the end
|
||||
.data.key = marantec_key,
|
||||
.data.bits = 49,
|
||||
.data.te = 0};
|
||||
break;
|
||||
case SetTypeFaacSLH_433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenFaacSLH,
|
||||
@@ -400,6 +447,26 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
|
||||
.keeloq.cnt = 0x05,
|
||||
.keeloq.manuf = "Beninca"};
|
||||
break;
|
||||
case SetTypeComunello433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x00FFFFFF,
|
||||
.keeloq.btn = 0x08,
|
||||
.keeloq.cnt = 0x05,
|
||||
.keeloq.manuf = "Comunello"};
|
||||
break;
|
||||
case SetTypeComunello868:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 868460000,
|
||||
.keeloq.serial = key & 0x00FFFFFF,
|
||||
.keeloq.btn = 0x08,
|
||||
.keeloq.cnt = 0x05,
|
||||
.keeloq.manuf = "Comunello"};
|
||||
break;
|
||||
case SetTypeAllmatic433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
@@ -585,16 +652,16 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
|
||||
.type = GenCameAtomo,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = (key & 0x0FFFFFFF) | 0x10000000,
|
||||
.keeloq.cnt = 0x03};
|
||||
.came_atomo.serial = (key & 0x0FFFFFFF) | 0x10000000,
|
||||
.came_atomo.cnt = 0x03};
|
||||
break;
|
||||
case SetTypeCameAtomo868:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenCameAtomo,
|
||||
.mod = "AM650",
|
||||
.freq = 868350000,
|
||||
.keeloq.serial = (key & 0x0FFFFFFF) | 0x10000000,
|
||||
.keeloq.cnt = 0x03};
|
||||
.came_atomo.serial = (key & 0x0FFFFFFF) | 0x10000000,
|
||||
.came_atomo.cnt = 0x03};
|
||||
break;
|
||||
case SetTypeBFTMitto:
|
||||
gen_info = (GenInfo){
|
||||
@@ -625,6 +692,16 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
|
||||
.somfy_telis.btn = 0x02,
|
||||
.somfy_telis.cnt = 0x03};
|
||||
break;
|
||||
case SetTypeMotorline433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x0FFFFFFF,
|
||||
.keeloq.btn = 0x01,
|
||||
.keeloq.cnt = 0x03,
|
||||
.keeloq.manuf = "Motorline"};
|
||||
break;
|
||||
case SetTypeDoorHan_433_92:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
@@ -820,6 +897,14 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
|
||||
.sec_plus_2.btn = 0x68,
|
||||
.sec_plus_2.cnt = 0xE500000};
|
||||
break;
|
||||
case SetTypePhoenix_V2_433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenPhoenixV2,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.phoenix_v2.serial = (key & 0x0FFFFFFF) | 0xB0000000,
|
||||
.phoenix_v2.cnt = 0x025D};
|
||||
break;
|
||||
default:
|
||||
furi_crash("Not implemented");
|
||||
break;
|
||||
@@ -927,6 +1012,14 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
|
||||
gen_info.sec_plus_2.btn,
|
||||
gen_info.sec_plus_2.cnt);
|
||||
break;
|
||||
case GenPhoenixV2:
|
||||
generated_protocol = subghz_txrx_gen_phoenix_v2_protocol(
|
||||
subghz->txrx,
|
||||
gen_info.mod,
|
||||
gen_info.freq,
|
||||
gen_info.phoenix_v2.serial,
|
||||
gen_info.phoenix_v2.cnt);
|
||||
break;
|
||||
default:
|
||||
furi_crash("Not implemented");
|
||||
break;
|
||||
|
||||
@@ -379,6 +379,8 @@ static Desktop* desktop_alloc(void) {
|
||||
|
||||
furi_record_create(RECORD_DESKTOP, desktop);
|
||||
|
||||
desktop->archive_dir = furi_string_alloc();
|
||||
|
||||
return desktop;
|
||||
}
|
||||
|
||||
@@ -494,6 +496,15 @@ void desktop_set_stealth_mode_state(Desktop* desktop, bool enabled) {
|
||||
desktop->in_transition = false;
|
||||
}
|
||||
|
||||
void desktop_launch_archive(Desktop* desktop, const char* open_dir) {
|
||||
if(open_dir) {
|
||||
furi_string_set(desktop->archive_dir, open_dir);
|
||||
} else {
|
||||
furi_string_reset(desktop->archive_dir);
|
||||
}
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventOpenArchive);
|
||||
}
|
||||
|
||||
/*
|
||||
* Public API
|
||||
*/
|
||||
|
||||
@@ -89,9 +89,12 @@ struct Desktop {
|
||||
|
||||
FuriPubSub* ascii_events_pubsub;
|
||||
FuriPubSubSubscription* ascii_events_subscription;
|
||||
|
||||
FuriString* archive_dir;
|
||||
};
|
||||
|
||||
void desktop_lock(Desktop* desktop, bool pin_lock);
|
||||
void desktop_unlock(Desktop* desktop);
|
||||
int32_t desktop_shutdown(void* context);
|
||||
void desktop_set_stealth_mode_state(Desktop* desktop, bool enabled);
|
||||
void desktop_launch_archive(Desktop* desktop, const char* open_dir);
|
||||
|
||||
@@ -203,7 +203,7 @@ void desktop_run_keybind(Desktop* desktop, InputType _type, InputKey _key) {
|
||||
} else if(furi_string_equal(keybind, "Apps Menu")) {
|
||||
loader_start_detached_with_gui_error(desktop->loader, LOADER_APPLICATIONS_NAME, NULL);
|
||||
} else if(furi_string_equal(keybind, "Archive")) {
|
||||
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventOpenArchive);
|
||||
desktop_launch_archive(desktop, NULL);
|
||||
} else if(furi_string_equal(keybind, "Clock")) {
|
||||
loader_start_detached_with_gui_error(
|
||||
desktop->loader, EXT_PATH("apps/Tools/nightstand.fap"), "");
|
||||
@@ -218,11 +218,11 @@ void desktop_run_keybind(Desktop* desktop, InputType _type, InputKey _key) {
|
||||
} else if(furi_string_equal(keybind, "Wipe Device")) {
|
||||
loader_start_detached_with_gui_error(desktop->loader, "Storage", "Wipe Device");
|
||||
} else {
|
||||
if(storage_common_exists(desktop->storage, furi_string_get_cstr(keybind))) {
|
||||
run_with_default_app(furi_string_get_cstr(keybind));
|
||||
const char* str = furi_string_get_cstr(keybind);
|
||||
if(storage_common_exists(desktop->storage, str)) {
|
||||
run_with_default_app(str);
|
||||
} else {
|
||||
loader_start_detached_with_gui_error(
|
||||
desktop->loader, furi_string_get_cstr(keybind), NULL);
|
||||
loader_start_detached_with_gui_error(desktop->loader, str, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,8 +101,7 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) {
|
||||
break;
|
||||
case DesktopLockedEventUpdate:
|
||||
if(desktop_view_locked_is_locked_hint_visible(desktop->locked_view)) {
|
||||
notification_message(
|
||||
desktop->notification, &sequence_display_backlight_off_delay_1000);
|
||||
notification_message(desktop->notification, &sequence_display_backlight_off);
|
||||
}
|
||||
desktop_view_locked_update(desktop->locked_view);
|
||||
consumed = true;
|
||||
|
||||
@@ -34,8 +34,10 @@ static void desktop_scene_main_interact_animation_callback(void* context) {
|
||||
}
|
||||
|
||||
#ifdef APP_ARCHIVE
|
||||
static void
|
||||
desktop_switch_to_app(Desktop* desktop, const FlipperInternalApplication* flipper_app) {
|
||||
static void desktop_switch_to_app(
|
||||
Desktop* desktop,
|
||||
const FlipperInternalApplication* flipper_app,
|
||||
void* context) {
|
||||
furi_assert(desktop);
|
||||
furi_assert(flipper_app);
|
||||
furi_assert(flipper_app->app);
|
||||
@@ -56,6 +58,7 @@ static void
|
||||
furi_thread_set_name(desktop->scene_thread, flipper_app->name);
|
||||
furi_thread_set_stack_size(desktop->scene_thread, flipper_app->stack_size);
|
||||
furi_thread_set_callback(desktop->scene_thread, flipper_app->app);
|
||||
furi_thread_set_context(desktop->scene_thread, context);
|
||||
|
||||
furi_thread_start(desktop->scene_thread);
|
||||
}
|
||||
@@ -114,7 +117,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
case DesktopMainEventOpenArchive:
|
||||
#ifdef APP_ARCHIVE
|
||||
desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE);
|
||||
desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE, desktop->archive_dir);
|
||||
#endif
|
||||
consumed = true;
|
||||
break;
|
||||
|
||||
@@ -288,10 +288,17 @@ void desktop_view_locked_free(DesktopViewLocked* locked_view) {
|
||||
void desktop_view_locked_close_cover(DesktopViewLocked* locked_view) {
|
||||
DesktopViewLockedModel* model = view_get_model(locked_view->view);
|
||||
furi_assert(model->view_state == DesktopViewLockedStateLocked);
|
||||
model->view_state = DesktopViewLockedStateCoverClosing;
|
||||
model->cover_offset = COVER_OFFSET_START;
|
||||
view_commit_model(locked_view->view, true);
|
||||
furi_timer_start(locked_view->timer, COVER_MOVING_INTERVAL_MS);
|
||||
|
||||
if(momentum_settings.lockscreen_skip_animation) {
|
||||
locked_view->callback(DesktopLockedEventCoversClosed, locked_view->context);
|
||||
model->cover_offset = COVER_OFFSET_END;
|
||||
view_commit_model(locked_view->view, true);
|
||||
} else {
|
||||
model->view_state = DesktopViewLockedStateCoverClosing;
|
||||
model->cover_offset = COVER_OFFSET_START;
|
||||
view_commit_model(locked_view->view, true);
|
||||
furi_timer_start(locked_view->timer, COVER_MOVING_INTERVAL_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void desktop_view_locked_lock(DesktopViewLocked* locked_view, bool pin_locked) {
|
||||
@@ -305,11 +312,19 @@ void desktop_view_locked_lock(DesktopViewLocked* locked_view, bool pin_locked) {
|
||||
void desktop_view_locked_unlock(DesktopViewLocked* locked_view) {
|
||||
locked_view->lock_count = 0;
|
||||
DesktopViewLockedModel* model = view_get_model(locked_view->view);
|
||||
model->view_state = DesktopViewLockedStateCoverOpening;
|
||||
model->cover_offset = COVER_OFFSET_END;
|
||||
model->pin_locked = false;
|
||||
view_commit_model(locked_view->view, true);
|
||||
furi_timer_start(locked_view->timer, COVER_MOVING_INTERVAL_MS);
|
||||
|
||||
if(momentum_settings.lockscreen_skip_animation) {
|
||||
model->view_state = DesktopViewLockedStateUnlocked;
|
||||
model->cover_offset = COVER_OFFSET_START;
|
||||
model->pin_locked = false;
|
||||
view_commit_model(locked_view->view, true);
|
||||
} else {
|
||||
model->view_state = DesktopViewLockedStateCoverOpening;
|
||||
model->cover_offset = COVER_OFFSET_END;
|
||||
model->pin_locked = false;
|
||||
view_commit_model(locked_view->view, true);
|
||||
furi_timer_start(locked_view->timer, COVER_MOVING_INTERVAL_MS);
|
||||
}
|
||||
}
|
||||
|
||||
bool desktop_view_locked_is_locked_hint_visible(DesktopViewLocked* locked_view) {
|
||||
|
||||
@@ -16,6 +16,7 @@ void dialog_file_browser_set_basic_options(
|
||||
options->hide_ext = true;
|
||||
options->item_loader_callback = NULL;
|
||||
options->item_loader_context = NULL;
|
||||
options->select_right = false;
|
||||
}
|
||||
|
||||
static DialogsApp* dialogs_app_alloc(void) {
|
||||
|
||||
@@ -26,6 +26,7 @@ typedef struct DialogsApp DialogsApp;
|
||||
* @param hide_ext true - hide extensions for files
|
||||
* @param item_loader_callback callback function for providing custom icon & entry name
|
||||
* @param hide_ext callback context
|
||||
* @param select_right true - select with right key, allows selecting directories
|
||||
*/
|
||||
typedef struct {
|
||||
const char* extension;
|
||||
@@ -36,6 +37,8 @@ typedef struct {
|
||||
bool hide_ext;
|
||||
FileBrowserLoadItemCallback item_loader_callback;
|
||||
void* item_loader_context;
|
||||
|
||||
bool select_right;
|
||||
} DialogsFileBrowserOptions;
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,6 +43,7 @@ bool dialog_file_browser_show(
|
||||
.item_callback = options ? options->item_loader_callback : NULL,
|
||||
.item_callback_context = options ? options->item_loader_context : NULL,
|
||||
.base_path = furi_string_get_cstr(base_path),
|
||||
.select_right = options ? options->select_right : false,
|
||||
}};
|
||||
|
||||
DialogsAppReturn return_data;
|
||||
|
||||
@@ -18,6 +18,8 @@ typedef struct {
|
||||
FileBrowserLoadItemCallback item_callback;
|
||||
void* item_callback_context;
|
||||
const char* base_path;
|
||||
|
||||
bool select_right;
|
||||
} DialogsAppMessageDataFileBrowser;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -46,6 +46,7 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow
|
||||
data->file_icon,
|
||||
data->hide_ext);
|
||||
file_browser_set_item_callback(file_browser, data->item_callback, data->item_callback_context);
|
||||
file_browser_set_select_right(file_browser, data->select_right);
|
||||
file_browser_start(file_browser, data->preselected_filename);
|
||||
|
||||
view_holder_set_view(view_holder, file_browser_get_view(file_browser));
|
||||
|
||||
@@ -127,6 +127,8 @@ struct FileBrowser {
|
||||
|
||||
FuriString* result_path;
|
||||
FuriTimer* scroll_timer;
|
||||
|
||||
bool select_right;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
@@ -234,10 +236,11 @@ void file_browser_configure(
|
||||
furi_check(browser);
|
||||
|
||||
browser->ext_filter = extension;
|
||||
browser->skip_assets = skip_assets;
|
||||
browser->hide_ext = hide_ext;
|
||||
browser->base_path = base_path;
|
||||
browser->skip_assets = skip_assets;
|
||||
browser->hide_dot_files = hide_dot_files;
|
||||
browser->hide_ext = hide_ext;
|
||||
browser->select_right = false;
|
||||
|
||||
with_view_model(
|
||||
browser->view,
|
||||
@@ -296,6 +299,11 @@ void file_browser_set_item_callback(
|
||||
browser->item_callback = callback;
|
||||
}
|
||||
|
||||
void file_browser_set_select_right(FileBrowser* browser, bool select_right) {
|
||||
furi_check(browser);
|
||||
browser->select_right = select_right;
|
||||
}
|
||||
|
||||
static bool browser_is_item_in_array(FileBrowserModel* model, uint32_t idx) {
|
||||
size_t array_size = items_array_size(model->items);
|
||||
|
||||
@@ -793,6 +801,31 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) {
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event->key == InputKeyRight) {
|
||||
if(event->type == InputTypeShort && browser->select_right) {
|
||||
BrowserItem_t* selected_item = NULL;
|
||||
with_view_model(
|
||||
browser->view,
|
||||
FileBrowserModel * model,
|
||||
{
|
||||
if(browser_is_item_in_array(model, model->item_idx)) {
|
||||
selected_item =
|
||||
items_array_get(model->items, model->item_idx - model->array_offset);
|
||||
}
|
||||
},
|
||||
false);
|
||||
|
||||
if(selected_item) {
|
||||
if(selected_item->type == BrowserItemTypeFile ||
|
||||
selected_item->type == BrowserItemTypeFolder) {
|
||||
furi_string_set(browser->result_path, selected_item->path);
|
||||
if(browser->callback) {
|
||||
browser->callback(browser->context);
|
||||
}
|
||||
}
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
if(event->type == InputTypeShort) {
|
||||
bool is_root = false;
|
||||
|
||||
@@ -48,6 +48,8 @@ void file_browser_set_item_callback(
|
||||
FileBrowserLoadItemCallback callback,
|
||||
void* context);
|
||||
|
||||
void file_browser_set_select_right(FileBrowser* browser, bool select_right);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -156,13 +156,14 @@ int32_t input_srv(void* p) {
|
||||
event.type = pin_states[i].state ? InputTypePress : InputTypeRelease;
|
||||
furi_pubsub_publish(event_pubsub, &event);
|
||||
// vibro signal if user setup vibro touch level in Settings-Input.
|
||||
if(settings->vibro_touch_level) {
|
||||
if(settings->vibro_touch_level &&
|
||||
((1 << event.type) & settings->vibro_touch_trigger_mask)) {
|
||||
//delay 1 ticks for compatibility with rgb_backlight_mod
|
||||
furi_delay_tick(1);
|
||||
furi_hal_vibro_on(true);
|
||||
furi_delay_tick(settings->vibro_touch_level);
|
||||
furi_hal_vibro_on(false);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
#include "input_settings.h"
|
||||
#include "input_settings_filename.h"
|
||||
#include "input.h"
|
||||
|
||||
#include <saved_struct.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define TAG "InputSettings"
|
||||
|
||||
#define INPUT_SETTINGS_VER (1) // version nnumber
|
||||
#define INPUT_SETTINGS_VER (2) // version number
|
||||
|
||||
#define INPUT_SETTINGS_MAGIC (0x29)
|
||||
|
||||
#define INPUT_SETTINGS_VIBRO_TOUCH_TRIGGER_MASK_DEFAULT \
|
||||
((1 << InputTypePress) | (1 << InputTypeRelease))
|
||||
|
||||
void input_settings_load(InputSettings* settings) {
|
||||
furi_assert(settings);
|
||||
|
||||
@@ -21,6 +25,18 @@ void input_settings_load(InputSettings* settings) {
|
||||
uint8_t version;
|
||||
if(!saved_struct_get_metadata(INPUT_SETTINGS_PATH, NULL, &version, NULL)) break;
|
||||
|
||||
if(version == 1) {
|
||||
struct {
|
||||
uint8_t vibro_touch_level;
|
||||
} v1;
|
||||
if(!saved_struct_load(INPUT_SETTINGS_PATH, &v1, sizeof(v1), INPUT_SETTINGS_MAGIC, 1))
|
||||
break;
|
||||
settings->vibro_touch_level = v1.vibro_touch_level;
|
||||
settings->vibro_touch_trigger_mask = INPUT_SETTINGS_VIBRO_TOUCH_TRIGGER_MASK_DEFAULT;
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// if config actual version - load it directly
|
||||
if(version == INPUT_SETTINGS_VER) {
|
||||
success = saved_struct_load(
|
||||
@@ -37,6 +53,7 @@ void input_settings_load(InputSettings* settings) {
|
||||
if(!success) {
|
||||
FURI_LOG_W(TAG, "Failed to load file, using defaults");
|
||||
memset(settings, 0, sizeof(InputSettings));
|
||||
settings->vibro_touch_trigger_mask = INPUT_SETTINGS_VIBRO_TOUCH_TRIGGER_MASK_DEFAULT;
|
||||
// input_settings_save(settings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
typedef struct {
|
||||
uint8_t vibro_touch_level;
|
||||
uint8_t vibro_touch_trigger_mask;
|
||||
} InputSettings;
|
||||
|
||||
void input_settings_load(InputSettings* settings);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <applications.h>
|
||||
#include <archive/helpers/archive_favorites.h>
|
||||
#include <toolbox/run_parallel.h>
|
||||
#include <archive/helpers/archive_helpers_ext.h>
|
||||
|
||||
#include "loader.h"
|
||||
#include "loader_i.h"
|
||||
@@ -130,7 +131,12 @@ static void loader_menu_apps_callback(void* context, uint32_t index) {
|
||||
LoaderMenuApp* app = context;
|
||||
const MenuApp* menu_app = MenuAppList_get(app->apps_list, index);
|
||||
const char* name = menu_app->path ? menu_app->path : menu_app->name;
|
||||
loader_menu_start(name);
|
||||
|
||||
if(menu_app->path && !strstr(menu_app->path, ".fap")) {
|
||||
run_with_default_app(menu_app->path);
|
||||
} else {
|
||||
loader_menu_start(name);
|
||||
}
|
||||
}
|
||||
|
||||
static void loader_menu_last_callback(void* context, uint32_t index) {
|
||||
@@ -225,6 +231,14 @@ static void loader_menu_add_app_entry(
|
||||
app);
|
||||
}
|
||||
|
||||
static const Icon* loader_menu_get_ext_icon(Storage* storage, const char* path) {
|
||||
if(storage_dir_exists(storage, path)) return &I_dir_10px;
|
||||
const char* ext = strrchr(path, '.');
|
||||
if(ext && strcasecmp(ext, ".js") == 0) return &I_js_script_10px;
|
||||
|
||||
return &I_file_10px;
|
||||
}
|
||||
|
||||
bool loader_menu_load_fap_meta(
|
||||
Storage* storage,
|
||||
FuriString* path,
|
||||
@@ -254,11 +268,9 @@ static void loader_menu_find_add_app(LoaderMenuApp* app, Storage* storage, FuriS
|
||||
if(furi_string_start_with(line, "/")) {
|
||||
path = strdup(furi_string_get_cstr(line));
|
||||
if(!loader_menu_load_fap_meta(storage, line, line, &icon)) {
|
||||
free((void*)path);
|
||||
path = NULL;
|
||||
} else {
|
||||
name = strdup(furi_string_get_cstr(line));
|
||||
icon = loader_menu_get_ext_icon(storage, path);
|
||||
}
|
||||
name = strdup(furi_string_get_cstr(line));
|
||||
} else {
|
||||
for(size_t i = 0; !name && i < FLIPPER_APPS_COUNT; i++) {
|
||||
if(furi_string_equal(line, FLIPPER_APPS[i].name)) {
|
||||
|
||||
@@ -31,7 +31,7 @@ typedef enum {
|
||||
typedef enum {
|
||||
DesktopSettingsAppKeybindActionTypeMainApp,
|
||||
DesktopSettingsAppKeybindActionTypeExternalApp,
|
||||
DesktopSettingsAppKeybindActionTypeOpenFile,
|
||||
DesktopSettingsAppKeybindActionTypeOpenFileOrDirectory,
|
||||
DesktopSettingsAppKeybindActionTypeMoreActions,
|
||||
DesktopSettingsAppKeybindActionTypeRemoveKeybind,
|
||||
} DesktopSettingsAppKeybindActionType;
|
||||
|
||||
+10
-6
@@ -31,7 +31,7 @@ static void
|
||||
scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneKeybindsAction);
|
||||
break;
|
||||
case DesktopSettingsAppKeybindActionTypeExternalApp:
|
||||
case DesktopSettingsAppKeybindActionTypeOpenFile: {
|
||||
case DesktopSettingsAppKeybindActionTypeOpenFileOrDirectory: {
|
||||
const char* base_path;
|
||||
const char* extension;
|
||||
bool hide_ext;
|
||||
@@ -53,9 +53,10 @@ static void
|
||||
.item_loader_callback = keybinds_fap_selector_item_callback,
|
||||
.item_loader_context = app,
|
||||
.base_path = base_path,
|
||||
.select_right = true,
|
||||
};
|
||||
FuriString* temp_path = furi_string_alloc_set_str(base_path);
|
||||
if(storage_file_exists(furi_record_open(RECORD_STORAGE), furi_string_get_cstr(keybind))) {
|
||||
if(storage_common_exists(furi_record_open(RECORD_STORAGE), furi_string_get_cstr(keybind))) {
|
||||
furi_string_set(temp_path, keybind);
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
@@ -98,8 +99,8 @@ void desktop_settings_scene_keybinds_action_type_on_enter(void* context) {
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Open File",
|
||||
DesktopSettingsAppKeybindActionTypeOpenFile,
|
||||
"File / Directory (right btn)",
|
||||
DesktopSettingsAppKeybindActionTypeOpenFileOrDirectory,
|
||||
desktop_settings_scene_keybinds_action_type_submenu_callback,
|
||||
app);
|
||||
|
||||
@@ -131,12 +132,15 @@ void desktop_settings_scene_keybinds_action_type_on_enter(void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
if(storage_file_exists(furi_record_open(RECORD_STORAGE), furi_string_get_cstr(keybind))) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
if(storage_file_exists(storage, furi_string_get_cstr(keybind))) {
|
||||
if(furi_string_end_with_str(keybind, ".fap")) {
|
||||
selected = DesktopSettingsAppKeybindActionTypeExternalApp;
|
||||
} else {
|
||||
selected = DesktopSettingsAppKeybindActionTypeOpenFile;
|
||||
selected = DesktopSettingsAppKeybindActionTypeOpenFileOrDirectory;
|
||||
}
|
||||
} else if(storage_dir_exists(storage, furi_string_get_cstr(keybind))) {
|
||||
selected = DesktopSettingsAppKeybindActionTypeOpenFileOrDirectory;
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
#define TAG "InputSettingsApp"
|
||||
|
||||
#define VIBRO_TOUCH_LEVEL_COUNT 10
|
||||
#define VIBRO_TOUCH_LEVEL_COUNT 10
|
||||
#define VIBRO_TOUCH_TRIGGER_MASK_COUNT 3
|
||||
|
||||
// vibro touch human readable levels
|
||||
const char* const vibro_touch_level_text[VIBRO_TOUCH_LEVEL_COUNT] = {
|
||||
"OFF",
|
||||
@@ -20,21 +22,45 @@ const char* const vibro_touch_level_text[VIBRO_TOUCH_LEVEL_COUNT] = {
|
||||
// vibro touch levels tick valies delay
|
||||
const uint32_t vibro_touch_level_value[VIBRO_TOUCH_LEVEL_COUNT] =
|
||||
{0, 13, 16, 19, 21, 24, 27, 30, 33, 36};
|
||||
// vibro touch trigger mask human readable values
|
||||
const char* const vibro_touch_trigger_mask_text[VIBRO_TOUCH_TRIGGER_MASK_COUNT] = {
|
||||
"Press",
|
||||
"Release",
|
||||
"Both",
|
||||
};
|
||||
// vibro touch trigger mask values
|
||||
const uint32_t vibro_touch_trigger_mask_value[VIBRO_TOUCH_TRIGGER_MASK_COUNT] = {
|
||||
(1 << InputTypePress),
|
||||
(1 << InputTypeRelease),
|
||||
(1 << InputTypePress) | (1 << InputTypeRelease),
|
||||
};
|
||||
|
||||
static void input_settings_vibro_touch_level_changed(VariableItem* item) {
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, vibro_touch_level_text[index]);
|
||||
|
||||
//change settings to selected
|
||||
InputSettingsApp* app = variable_item_get_context(item);
|
||||
app->settings->vibro_touch_level = vibro_touch_level_value[index];
|
||||
|
||||
// use RECORD for acces to input service instance and set settings
|
||||
// use RECORD for access to input service instance and set settings
|
||||
InputSettings* service_settings = furi_record_open(RECORD_INPUT_SETTINGS);
|
||||
service_settings->vibro_touch_level = vibro_touch_level_value[index];
|
||||
furi_record_close(RECORD_INPUT_SETTINGS);
|
||||
}
|
||||
|
||||
static void input_settings_vibro_touch_trigger_mask_changed(VariableItem* item) {
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, vibro_touch_trigger_mask_text[index]);
|
||||
|
||||
InputSettingsApp* app = variable_item_get_context(item);
|
||||
app->settings->vibro_touch_trigger_mask = vibro_touch_trigger_mask_value[index];
|
||||
|
||||
// use RECORD for access to input service instance and set settings
|
||||
InputSettings* service_settings = furi_record_open(RECORD_INPUT_SETTINGS);
|
||||
service_settings->vibro_touch_trigger_mask = vibro_touch_trigger_mask_value[index];
|
||||
furi_record_close(RECORD_INPUT_SETTINGS);
|
||||
}
|
||||
|
||||
static uint32_t input_settings_app_exit(void* context) {
|
||||
UNUSED(context);
|
||||
return VIEW_NONE;
|
||||
@@ -66,6 +92,20 @@ InputSettingsApp* input_settings_app_alloc(void) {
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, vibro_touch_level_text[value_index]);
|
||||
|
||||
item = variable_item_list_add(
|
||||
app->variable_item_list,
|
||||
"Vibro Trigger",
|
||||
VIBRO_TOUCH_TRIGGER_MASK_COUNT,
|
||||
input_settings_vibro_touch_trigger_mask_changed,
|
||||
app);
|
||||
|
||||
value_index = value_index_uint32(
|
||||
app->settings->vibro_touch_trigger_mask,
|
||||
vibro_touch_trigger_mask_value,
|
||||
VIBRO_TOUCH_TRIGGER_MASK_COUNT);
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, vibro_touch_trigger_mask_text[value_index]);
|
||||
|
||||
// create and setup view and view dispatcher
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#include "ble_hid_ext_profile.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
static FuriHalBleProfileBase* ble_profile_hid_ext_start(FuriHalBleProfileParams profile_params) {
|
||||
UNUSED(profile_params);
|
||||
|
||||
return ble_profile_hid->start(NULL);
|
||||
}
|
||||
|
||||
static void ble_profile_hid_ext_stop(FuriHalBleProfileBase* profile) {
|
||||
ble_profile_hid->stop(profile);
|
||||
}
|
||||
|
||||
static void
|
||||
ble_profile_hid_ext_get_config(GapConfig* config, FuriHalBleProfileParams profile_params) {
|
||||
furi_check(config);
|
||||
furi_check(profile_params);
|
||||
BleProfileHidExtParams* hid_ext_profile_params = profile_params;
|
||||
|
||||
// Setup config with basic profile
|
||||
ble_profile_hid->get_gap_config(config, NULL);
|
||||
|
||||
if(hid_ext_profile_params->name[0] != '\0') {
|
||||
// Set advertise name (skip first byte which is the ADV type)
|
||||
strlcpy(config->adv_name + 1, hid_ext_profile_params->name, sizeof(config->adv_name) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
static const FuriHalBleProfileTemplate profile_callbacks = {
|
||||
.start = ble_profile_hid_ext_start,
|
||||
.stop = ble_profile_hid_ext_stop,
|
||||
.get_gap_config = ble_profile_hid_ext_get_config,
|
||||
};
|
||||
|
||||
const FuriHalBleProfileTemplate* ble_profile_hid_ext = &profile_callbacks;
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <ble_profile/extra_profiles/hid_profile.h>
|
||||
|
||||
/**
|
||||
* Optional arguments to pass along with profile template as
|
||||
* FuriHalBleProfileParams for tuning profile behavior
|
||||
**/
|
||||
typedef struct {
|
||||
char name[FURI_HAL_BT_ADV_NAME_LENGTH]; /**< Full device name */
|
||||
} BleProfileHidExtParams;
|
||||
|
||||
/** Hid Keyboard Profile descriptor */
|
||||
extern const FuriHalBleProfileTemplate* ble_profile_hid_ext;
|
||||
@@ -4,9 +4,14 @@
|
||||
#include "views.h"
|
||||
#include <notification/notification_messages.h>
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define TAG "HidApp"
|
||||
|
||||
#define HID_BT_CFG_PATH APP_DATA_PATH(".bt_hid.cfg")
|
||||
#define HID_BT_CFG_FILE_TYPE "Flipper BT Remote Settings File"
|
||||
#define HID_BT_CFG_VERSION 1
|
||||
|
||||
bool hid_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
Hid* app = context;
|
||||
@@ -33,6 +38,60 @@ void bt_hid_remove_pairing(Hid* app) {
|
||||
furi_hal_bt_start_advertising();
|
||||
}
|
||||
|
||||
static void bt_hid_load_cfg(Hid* app) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* fff = flipper_format_file_alloc(storage);
|
||||
bool loaded = false;
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
uint32_t temp_uint = 0;
|
||||
|
||||
do {
|
||||
if(!flipper_format_file_open_existing(fff, HID_BT_CFG_PATH)) break;
|
||||
|
||||
if(!flipper_format_read_header(fff, temp_str, &temp_uint)) break;
|
||||
if((strcmp(furi_string_get_cstr(temp_str), HID_BT_CFG_FILE_TYPE) != 0) ||
|
||||
(temp_uint != HID_BT_CFG_VERSION))
|
||||
break;
|
||||
|
||||
if(flipper_format_read_string(fff, "name", temp_str)) {
|
||||
strlcpy(
|
||||
app->ble_hid_cfg.name,
|
||||
furi_string_get_cstr(temp_str),
|
||||
sizeof(app->ble_hid_cfg.name));
|
||||
} else {
|
||||
flipper_format_rewind(fff);
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
} while(0);
|
||||
|
||||
furi_string_free(temp_str);
|
||||
|
||||
flipper_format_free(fff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
if(!loaded) {
|
||||
app->ble_hid_cfg.name[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void bt_hid_save_cfg(Hid* app) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* fff = flipper_format_file_alloc(storage);
|
||||
|
||||
if(flipper_format_file_open_always(fff, HID_BT_CFG_PATH)) {
|
||||
do {
|
||||
if(!flipper_format_write_header_cstr(fff, HID_BT_CFG_FILE_TYPE, HID_BT_CFG_VERSION))
|
||||
break;
|
||||
if(!flipper_format_write_string_cstr(fff, "name", app->ble_hid_cfg.name)) break;
|
||||
} while(0);
|
||||
}
|
||||
|
||||
flipper_format_free(fff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static void bt_hid_connection_status_changed_callback(BtStatus status, void* context) {
|
||||
furi_assert(context);
|
||||
Hid* hid = context;
|
||||
@@ -89,6 +148,11 @@ Hid* hid_alloc() {
|
||||
app->dialog = dialog_ex_alloc();
|
||||
view_dispatcher_add_view(app->view_dispatcher, HidViewDialog, dialog_ex_get_view(app->dialog));
|
||||
|
||||
// Text input
|
||||
app->text_input = text_input_alloc();
|
||||
view_dispatcher_add_view(
|
||||
app->view_dispatcher, HidViewTextInput, text_input_get_view(app->text_input));
|
||||
|
||||
// Popup view
|
||||
app->popup = popup_alloc();
|
||||
view_dispatcher_add_view(app->view_dispatcher, HidViewPopup, popup_get_view(app->popup));
|
||||
@@ -177,6 +241,8 @@ void hid_free(Hid* app) {
|
||||
submenu_free(app->submenu);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewDialog);
|
||||
dialog_ex_free(app->dialog);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewTextInput);
|
||||
text_input_free(app->text_input);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewPopup);
|
||||
popup_free(app->popup);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote);
|
||||
@@ -266,7 +332,9 @@ int32_t hid_ble_app(void* p) {
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
app->ble_hid_profile = bt_profile_start(app->bt, ble_profile_hid, NULL);
|
||||
bt_hid_load_cfg(app);
|
||||
|
||||
app->ble_hid_profile = bt_profile_start(app->bt, ble_profile_hid_ext, &app->ble_hid_cfg);
|
||||
|
||||
furi_check(app->ble_hid_profile);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <furi_hal_usb.h>
|
||||
#include <furi_hal_usb_hid.h>
|
||||
|
||||
#include <extra_profiles/hid_profile.h>
|
||||
#include "helpers/ble_hid_ext_profile.h"
|
||||
|
||||
#include <bt/bt_service/bt.h>
|
||||
#include <gui/gui.h>
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
#include <gui/modules/popup.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include "views/hid_keynote.h"
|
||||
#include "views/hid_keyboard.h"
|
||||
#include "views/hid_numpad.h"
|
||||
@@ -40,6 +41,7 @@ typedef struct Hid Hid;
|
||||
|
||||
struct Hid {
|
||||
FuriHalBleProfileBase* ble_hid_profile;
|
||||
BleProfileHidExtParams ble_hid_cfg;
|
||||
Bt* bt;
|
||||
Gui* gui;
|
||||
NotificationApp* notifications;
|
||||
@@ -47,6 +49,7 @@ struct Hid {
|
||||
SceneManager* scene_manager;
|
||||
Submenu* submenu;
|
||||
DialogEx* dialog;
|
||||
TextInput* text_input;
|
||||
Popup* popup;
|
||||
HidKeynote* hid_keynote;
|
||||
HidKeyboard* hid_keyboard;
|
||||
@@ -64,6 +67,7 @@ struct Hid {
|
||||
};
|
||||
|
||||
void bt_hid_remove_pairing(Hid* app);
|
||||
void bt_hid_save_cfg(Hid* app);
|
||||
|
||||
void hid_hal_keyboard_press(Hid* instance, uint16_t event);
|
||||
void hid_hal_keyboard_release(Hid* instance, uint16_t event);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
ADD_SCENE(hid, start, Start)
|
||||
ADD_SCENE(hid, main, Main)
|
||||
ADD_SCENE(hid, unpair, Unpair)
|
||||
ADD_SCENE(hid, rename, Rename)
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "../hid.h"
|
||||
#include "../views.h"
|
||||
#include "hid_icons.h"
|
||||
|
||||
enum HidSceneRenameEvent {
|
||||
HidSceneRenameEventTextInput,
|
||||
HidSceneRenameEventPopup,
|
||||
};
|
||||
|
||||
static void hid_scene_rename_text_input_callback(void* context) {
|
||||
Hid* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, HidSceneRenameEventTextInput);
|
||||
}
|
||||
|
||||
void hid_scene_rename_popup_callback(void* context) {
|
||||
Hid* app = context;
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, HidSceneRenameEventPopup);
|
||||
}
|
||||
|
||||
void hid_scene_rename_on_enter(void* context) {
|
||||
Hid* app = context;
|
||||
|
||||
// Rename text input view
|
||||
text_input_reset(app->text_input);
|
||||
text_input_set_result_callback(
|
||||
app->text_input,
|
||||
hid_scene_rename_text_input_callback,
|
||||
app,
|
||||
app->ble_hid_cfg.name,
|
||||
sizeof(app->ble_hid_cfg.name),
|
||||
true);
|
||||
text_input_set_header_text(app->text_input, "Bluetooth Name");
|
||||
|
||||
// Rename success popup view
|
||||
popup_set_icon(app->popup, 48, 6, &I_DolphinDone_80x58);
|
||||
popup_set_header(app->popup, "Done", 14, 15, AlignLeft, AlignTop);
|
||||
popup_set_timeout(app->popup, 1500);
|
||||
popup_set_context(app->popup, app);
|
||||
popup_set_callback(app->popup, hid_scene_rename_popup_callback);
|
||||
popup_enable_timeout(app->popup);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewTextInput);
|
||||
}
|
||||
|
||||
bool hid_scene_rename_on_event(void* context, SceneManagerEvent event) {
|
||||
Hid* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
consumed = true;
|
||||
if(event.event == HidSceneRenameEventTextInput) {
|
||||
#ifdef HID_TRANSPORT_BLE
|
||||
furi_hal_bt_stop_advertising();
|
||||
|
||||
app->ble_hid_profile =
|
||||
bt_profile_start(app->bt, ble_profile_hid_ext, &app->ble_hid_cfg);
|
||||
furi_check(app->ble_hid_profile);
|
||||
|
||||
furi_hal_bt_start_advertising();
|
||||
#endif
|
||||
|
||||
bt_hid_save_cfg(app);
|
||||
|
||||
// Show popup
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, HidViewPopup);
|
||||
|
||||
} else if(event.event == HidSceneRenameEventPopup) {
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void hid_scene_rename_on_exit(void* context) {
|
||||
Hid* app = context;
|
||||
|
||||
text_input_reset(app->text_input);
|
||||
popup_reset(app->popup);
|
||||
}
|
||||
@@ -15,6 +15,7 @@ enum HidSubmenuIndex {
|
||||
HidSubmenuIndexMouseJiggler,
|
||||
HidSubmenuIndexMouseJigglerStealth,
|
||||
HidSubmenuIndexPushToTalk,
|
||||
HidSubmenuIndexRename,
|
||||
HidSubmenuIndexRemovePairing,
|
||||
};
|
||||
|
||||
@@ -81,6 +82,12 @@ void hid_scene_start_on_enter(void* context) {
|
||||
hid_scene_start_submenu_callback,
|
||||
app);
|
||||
#ifdef HID_TRANSPORT_BLE
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Bluetooth Remote Name",
|
||||
HidSubmenuIndexRename,
|
||||
hid_scene_start_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Bluetooth Unpairing",
|
||||
@@ -101,6 +108,8 @@ bool hid_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == HidSubmenuIndexRemovePairing) {
|
||||
scene_manager_next_scene(app->scene_manager, HidSceneUnpair);
|
||||
} else if(event.event == HidSubmenuIndexRename) {
|
||||
scene_manager_next_scene(app->scene_manager, HidSceneRename);
|
||||
} else {
|
||||
HidView view_id;
|
||||
|
||||
|
||||
@@ -16,4 +16,5 @@ typedef enum {
|
||||
HidViewPushToTalkHelp,
|
||||
HidViewDialog,
|
||||
HidViewPopup,
|
||||
HidViewTextInput,
|
||||
} HidView;
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct Hid Hid;
|
||||
typedef struct HidMusicMacos HidMusicMacos;
|
||||
|
||||
HidMusicMacos* hid_music_macos_alloc();
|
||||
HidMusicMacos* hid_music_macos_alloc(Hid* hid);
|
||||
|
||||
void hid_music_macos_free(HidMusicMacos* hid_music_macos);
|
||||
|
||||
|
||||
@@ -76,13 +76,20 @@ Watch this video to learn more and see how different boards can be programmed (v
|
||||
## Doorhan
|
||||
|
||||
With access to the receiver box:
|
||||
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> KL: Doorhan 433Mhz or 315Mhz depends on your receiver (find out by reading your existing remote)
|
||||
2. Open your new remote file
|
||||
3. Push `P` button for ~2 sec, led will start flashing
|
||||
4. Press `Send` on your flipper for ~2 seconds
|
||||
5. Led on the receiver board will flash and turn off
|
||||
6. Done!
|
||||
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> KL: Doorhan 433Mhz or 315Mhz depends on your receiver (find out by reading your existing remote or follow guide below)
|
||||
- Finding frequency
|
||||
|
||||
There are 2 frequencies for DoorHan: 315.00 / 433.92. To determine them it is enough to create a DoorHan remote control with one of the frequencies via Sub-GHz -> Add manually, press the button and watch the receiver's reaction. If you have guessed the frequency, the light bulb will turn on when we press the button on the FZ and turn off when we release it.
|
||||
|
||||
2. Binding the remote control
|
||||
|
||||
Once you have access to the receiver (removed the protective cover), look at the buttons:
|
||||
- If there are 4 buttons (Radio, Reverse, Auto, ...) then press and hold Radio until the LED lights up, then press the FZ button 2 times and the LED goes out;
|
||||
- If there are 4 buttons (R, P, +, -) and display, press R, then press 2 times the button on FZ and wait +/- 10 seconds;
|
||||
- If there are 4 buttons (+, -, F, TR) and display, press TR, then press 2 times the button on FZ and wait +/- 10 seconds;
|
||||
- In other cases there is a “universal” instruction: Press and hold the button “P” +/- 2 seconds until the LED flashes, then press 2 times the button on the FZ and the LED goes out.
|
||||
|
||||
In all cases it is recommended to wait until the receiver returns to normal mode.
|
||||
|
||||
With existing remote:
|
||||
1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> KL: Doorhan 433Mhz or 315Mhz depends on your receiver (find out by reading your existing remote)
|
||||
|
||||
@@ -24,6 +24,7 @@ if you need your custom one, make sure it doesn't listed here
|
||||
/* 300 - 348 */
|
||||
300000000,
|
||||
302757000,
|
||||
303000000,
|
||||
303875000,
|
||||
303900000,
|
||||
304250000,
|
||||
@@ -80,6 +81,7 @@ if you need your custom one, make sure it doesn't listed here
|
||||
779000000,
|
||||
868350000,
|
||||
868400000,
|
||||
868460000,
|
||||
868800000,
|
||||
868950000,
|
||||
906400000,
|
||||
@@ -111,10 +113,8 @@ Your frequencies will be added after default ones
|
||||
|
||||
### Default hopper list
|
||||
```
|
||||
310000000,
|
||||
315000000,
|
||||
318000000,
|
||||
418000000,
|
||||
433920000,
|
||||
434420000,
|
||||
868350000,
|
||||
```
|
||||
|
||||
@@ -406,8 +406,7 @@ bool protocol_electra_write_data(ProtocolElectra* protocol, void* data) {
|
||||
request->t5577.block[4] = protocol->encoded_epilogue & 0xFFFFFFFF;
|
||||
request->t5577.blocks_to_write = 5;
|
||||
result = true;
|
||||
}
|
||||
if(request->write_type == LFRFIDWriteTypeEM4305) {
|
||||
} else if(request->write_type == LFRFIDWriteTypeEM4305) {
|
||||
request->em4305.word[4] =
|
||||
(EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(64) | (8 << EM4x05_MAXBLOCK_SHIFT));
|
||||
uint64_t encoded_data_reversed = 0;
|
||||
|
||||
@@ -369,6 +369,21 @@ bool protocol_fdx_b_write_data(ProtocolFDXB* protocol, void* data) {
|
||||
request->t5577.block[4] = bit_lib_get_bits_32(protocol->encoded_data, 96, 32);
|
||||
request->t5577.blocks_to_write = 5;
|
||||
result = true;
|
||||
} else if(request->write_type == LFRFIDWriteTypeEM4305) {
|
||||
request->em4305.word[4] =
|
||||
(EM4x05_MODULATION_BIPHASE | EM4x05_SET_BITRATE(32) | (8 << EM4x05_MAXBLOCK_SHIFT));
|
||||
uint32_t encoded_data_reversed[4] = {0};
|
||||
for(uint8_t i = 0; i < 128; i++) {
|
||||
encoded_data_reversed[i / 32] =
|
||||
(encoded_data_reversed[i / 32] << 1) |
|
||||
(bit_lib_get_bit(protocol->encoded_data, (127 - i)) & 1);
|
||||
}
|
||||
request->em4305.word[5] = encoded_data_reversed[3];
|
||||
request->em4305.word[6] = encoded_data_reversed[2];
|
||||
request->em4305.word[7] = encoded_data_reversed[1];
|
||||
request->em4305.word[8] = encoded_data_reversed[0];
|
||||
request->em4305.mask = 0x1F0;
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -287,6 +287,20 @@ bool protocol_gproxii_write_data(ProtocolGProxII* protocol, void* data) {
|
||||
request->t5577.block[3] = bit_lib_get_bits_32(protocol->data, 64, 32);
|
||||
request->t5577.blocks_to_write = 4;
|
||||
result = true;
|
||||
} else if(request->write_type == LFRFIDWriteTypeEM4305) {
|
||||
request->em4305.word[4] =
|
||||
(EM4x05_MODULATION_BIPHASE | EM4x05_SET_BITRATE(64) | (7 << EM4x05_MAXBLOCK_SHIFT));
|
||||
uint32_t encoded_data_reversed[3] = {0};
|
||||
for(uint8_t i = 0; i < 96; i++) {
|
||||
encoded_data_reversed[i / 32] = (encoded_data_reversed[i / 32] << 1) |
|
||||
(bit_lib_get_bit(protocol->data, (95 - i)) & 1);
|
||||
encoded_data_reversed[i / 32] ^= 1; // Invert to make DIPHASE/BIPHASE.
|
||||
}
|
||||
request->em4305.word[5] = encoded_data_reversed[2];
|
||||
request->em4305.word[6] = encoded_data_reversed[1];
|
||||
request->em4305.word[7] = encoded_data_reversed[0];
|
||||
request->em4305.mask = 0xF0;
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -182,6 +182,19 @@ bool protocol_jablotron_write_data(ProtocolJablotron* protocol, void* data) {
|
||||
request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32);
|
||||
request->t5577.blocks_to_write = 3;
|
||||
result = true;
|
||||
} else if(request->write_type == LFRFIDWriteTypeEM4305) {
|
||||
request->em4305.word[4] =
|
||||
(EM4x05_MODULATION_BIPHASE | EM4x05_SET_BITRATE(64) | (6 << EM4x05_MAXBLOCK_SHIFT));
|
||||
uint32_t encoded_data_reversed[2] = {0};
|
||||
for(uint8_t i = 0; i < 64; i++) {
|
||||
encoded_data_reversed[i / 32] =
|
||||
(encoded_data_reversed[i / 32] << 1) |
|
||||
(bit_lib_get_bit(protocol->encoded_data, (63 - i)) & 1);
|
||||
}
|
||||
request->em4305.word[5] = encoded_data_reversed[1];
|
||||
request->em4305.word[6] = encoded_data_reversed[0];
|
||||
request->em4305.mask = 0x70;
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -328,6 +328,20 @@ bool protocol_securakey_write_data(ProtocolSecurakey* protocol, void* data) {
|
||||
request->t5577.block[2] = bit_lib_get_bits_32(protocol->RKKTH_encoded_data, 32, 32);
|
||||
request->t5577.blocks_to_write = 3;
|
||||
result = true;
|
||||
} else if(request->write_type == LFRFIDWriteTypeEM4305) {
|
||||
request->em4305.word[4] =
|
||||
(EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(40) | // requires 330pF card
|
||||
(6 << EM4x05_MAXBLOCK_SHIFT));
|
||||
uint32_t encoded_data_reversed[2] = {0};
|
||||
for(uint8_t i = 0; i < 64; i++) {
|
||||
encoded_data_reversed[i / 32] =
|
||||
(encoded_data_reversed[i / 32] << 1) |
|
||||
(bit_lib_get_bit(protocol->RKKTH_encoded_data, (63 - i)) & 1);
|
||||
}
|
||||
request->em4305.word[5] = encoded_data_reversed[1];
|
||||
request->em4305.word[6] = encoded_data_reversed[0];
|
||||
request->em4305.mask = 0x70;
|
||||
result = true;
|
||||
}
|
||||
} else {
|
||||
if(request->write_type == LFRFIDWriteTypeT5577) {
|
||||
@@ -340,6 +354,21 @@ bool protocol_securakey_write_data(ProtocolSecurakey* protocol, void* data) {
|
||||
request->t5577.block[3] = bit_lib_get_bits_32(protocol->RKKT_encoded_data, 64, 32);
|
||||
request->t5577.blocks_to_write = 4;
|
||||
result = true;
|
||||
} else if(request->write_type == LFRFIDWriteTypeEM4305) {
|
||||
request->em4305.word[4] =
|
||||
(EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(40) | // requires 330pF card
|
||||
(7 << EM4x05_MAXBLOCK_SHIFT));
|
||||
uint32_t encoded_data_reversed[3] = {0};
|
||||
for(uint8_t i = 0; i < 96; i++) {
|
||||
encoded_data_reversed[i / 32] =
|
||||
(encoded_data_reversed[i / 32] << 1) |
|
||||
(bit_lib_get_bit(protocol->RKKT_encoded_data, (95 - i)) & 1);
|
||||
}
|
||||
request->em4305.word[5] = encoded_data_reversed[2];
|
||||
request->em4305.word[6] = encoded_data_reversed[1];
|
||||
request->em4305.word[7] = encoded_data_reversed[0];
|
||||
request->em4305.mask = 0xF0;
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -23,6 +23,7 @@ MomentumSettings momentum_settings = {
|
||||
.lockscreen_statusbar = true, // ON
|
||||
.lockscreen_prompt = true, // ON
|
||||
.lockscreen_transparent = false, // OFF
|
||||
.lockscreen_skip_animation = false, // OFF
|
||||
.battery_icon = BatteryIconBarPercent, // Bar %
|
||||
.status_icons = true, // ON
|
||||
.bar_borders = true, // ON
|
||||
@@ -96,6 +97,7 @@ static const struct {
|
||||
{setting_bool(lockscreen_statusbar)},
|
||||
{setting_bool(lockscreen_prompt)},
|
||||
{setting_bool(lockscreen_transparent)},
|
||||
{setting_bool(lockscreen_skip_animation)},
|
||||
{setting_enum(battery_icon, BatteryIconCount)},
|
||||
{setting_bool(status_icons)},
|
||||
{setting_bool(bar_borders)},
|
||||
|
||||
@@ -80,6 +80,7 @@ typedef struct {
|
||||
bool lockscreen_statusbar;
|
||||
bool lockscreen_prompt;
|
||||
bool lockscreen_transparent;
|
||||
bool lockscreen_skip_animation;
|
||||
BatteryIcon battery_icon;
|
||||
bool status_icons;
|
||||
bool bar_borders;
|
||||
|
||||
@@ -239,9 +239,15 @@ static bool subghz_protocol_keeloq_gen_data(
|
||||
(strcmp(instance->manufacture_name, "Mutanco_Mutancode") == 0) ||
|
||||
(strcmp(instance->manufacture_name, "Came_Space") == 0) ||
|
||||
(strcmp(instance->manufacture_name, "Genius_Bravo") == 0) ||
|
||||
(strcmp(instance->manufacture_name, "GSN") == 0)) {
|
||||
(strcmp(instance->manufacture_name, "GSN") == 0) ||
|
||||
(strcmp(instance->manufacture_name, "Rosh") == 0) ||
|
||||
(strcmp(instance->manufacture_name, "Rossi") == 0) ||
|
||||
(strcmp(instance->manufacture_name, "Pecinin") == 0) ||
|
||||
(strcmp(instance->manufacture_name, "Steelmate") == 0)) {
|
||||
// DTM Neo, Came_Space uses 12bit serial -> simple learning
|
||||
// FAAC_RC,XT , Mutanco_Mutancode, Genius_Bravo, GSN 12bit serial -> normal learning
|
||||
// Rosh, Rossi, Pecinin -> 12bit serial - simple learning
|
||||
// Steelmate -> 12bit serial - normal learning
|
||||
decrypt = btn << 28 | (instance->generic.serial & 0xFFF) << 16 |
|
||||
instance->generic.cnt;
|
||||
} else if(
|
||||
@@ -251,9 +257,12 @@ static bool subghz_protocol_keeloq_gen_data(
|
||||
// Nice Smilo, MHouse, JCM -> 8bit serial - simple learning
|
||||
decrypt = btn << 28 | (instance->generic.serial & 0xFF) << 16 |
|
||||
instance->generic.cnt;
|
||||
} else if(strcmp(instance->manufacture_name, "Beninca") == 0) {
|
||||
} else if(
|
||||
(strcmp(instance->manufacture_name, "Beninca") == 0) ||
|
||||
(strcmp(instance->manufacture_name, "Merlin") == 0)) {
|
||||
decrypt = btn << 28 | (0x000) << 16 | instance->generic.cnt;
|
||||
// Beninca / Allmatic -> no serial - simple XOR
|
||||
// Merlin -> no serial - simple XOR
|
||||
} else if(strcmp(instance->manufacture_name, "Centurion") == 0) {
|
||||
decrypt = btn << 28 | (0x1CE) << 16 | instance->generic.cnt;
|
||||
// Centurion -> no serial in hop, uses fixed value 0x1CE - normal learning
|
||||
|
||||
@@ -167,7 +167,7 @@ static void subghz_protocol_encoder_marantec_get_upload(SubGhzProtocolEncoderMar
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len) {
|
||||
uint8_t crc = 0x08;
|
||||
uint8_t crc = 0x01;
|
||||
size_t i, j;
|
||||
for(i = 0; i < len; i++) {
|
||||
crc ^= data[i];
|
||||
@@ -186,6 +186,18 @@ uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len) {
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_marantec_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
// Key samples
|
||||
// 1307EDF6486C5 = 000 100110000 01111110110111110110 0100 10000110 11000101
|
||||
// 1303EFAFD8683 = 000 100110000 00111110111110101111 1101 10000110 10000011
|
||||
|
||||
// From unittests
|
||||
// 1300710DF869F
|
||||
|
||||
// const serial button serial crc
|
||||
// 130 7EDF6 4 86 C5
|
||||
// 130 3EFAF D 86 83
|
||||
// 130 0710D F 86 9F
|
||||
|
||||
instance->btn = (instance->data >> 16) & 0xF;
|
||||
instance->serial = ((instance->data >> 12) & 0xFFFFFF00) | ((instance->data >> 8) & 0xFF);
|
||||
}
|
||||
@@ -369,16 +381,30 @@ void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* outp
|
||||
SubGhzProtocolDecoderMarantec* instance = context;
|
||||
subghz_protocol_marantec_remote_controller(&instance->generic);
|
||||
|
||||
uint8_t tdata[6] = {
|
||||
instance->generic.data >> 48,
|
||||
instance->generic.data >> 40,
|
||||
instance->generic.data >> 32,
|
||||
instance->generic.data >> 24,
|
||||
instance->generic.data >> 16,
|
||||
instance->generic.data >> 8};
|
||||
|
||||
uint8_t crc = subghz_protocol_marantec_crc8(tdata, sizeof(tdata));
|
||||
bool crc_ok = (crc == (instance->generic.data & 0xFF));
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key:0x%lX%08lX\r\n"
|
||||
"Sn:0x%07lX \r\n"
|
||||
"Btn:%X\r\n",
|
||||
"Key: 0x%lX%08lX\r\n"
|
||||
"Sn: 0x%07lX \r\n"
|
||||
"CRC: 0x%02X - %s\r\n"
|
||||
"Btn: %X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32),
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
instance->generic.serial,
|
||||
crc,
|
||||
crc_ok ? "Valid" : "Invalid",
|
||||
instance->generic.btn);
|
||||
}
|
||||
|
||||
@@ -107,3 +107,11 @@ SubGhzProtocolStatus
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* output);
|
||||
|
||||
/**
|
||||
* Calculate CRC8 for Marantec protocol.
|
||||
* @param data Pointer to the data buffer
|
||||
* @param len Length of the data buffer
|
||||
* @return CRC8 value
|
||||
*/
|
||||
uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len);
|
||||
|
||||
@@ -219,6 +219,11 @@ void subghz_protocol_decoder_marantec24_feed(void* context, bool level, volatile
|
||||
// Marantec24 Decoder
|
||||
// 2024 - @xMasterX (MMX)
|
||||
|
||||
// 2025 update - The protocol is not real marantec,
|
||||
// it comes from chinese remote that pretends to be replica of original marantec, actually it was a cloner
|
||||
// which had some thing written on it, which is uknown, but since its pretentding to be marantec,
|
||||
// it was decided to keep the name of the protocol as marantec24 (24 bits)
|
||||
|
||||
// Key samples
|
||||
// 101011000000010111001000 = AC05C8
|
||||
// 101011000000010111000100 = AC05C4
|
||||
@@ -268,16 +273,12 @@ void subghz_protocol_decoder_marantec24_feed(void* context, bool level, volatile
|
||||
//Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes)
|
||||
if((DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_marantec24_const.te_long) <
|
||||
subghz_protocol_marantec24_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 9) <
|
||||
subghz_protocol_marantec24_const.te_delta * 4)) {
|
||||
subghz_protocol_marantec24_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
}
|
||||
if((DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_marantec24_const.te_short) <
|
||||
subghz_protocol_marantec24_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 9) <
|
||||
subghz_protocol_marantec24_const.te_delta * 4)) {
|
||||
subghz_protocol_marantec24_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
}
|
||||
// If got 24 bits key reading is finished
|
||||
|
||||
@@ -387,6 +387,36 @@ SubGhzProtocolStatus
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_nero_radio_parse_data(SubGhzBlockGeneric* instance) {
|
||||
// Key samples from unit tests
|
||||
// 57250501049DD3
|
||||
// 57250502049D13
|
||||
//
|
||||
// Samples from remote
|
||||
// 36E4E80104A644
|
||||
// 36E4E80204A684
|
||||
// 36E4E80304A604
|
||||
// 36E4E80404A6E4
|
||||
|
||||
// possible contents
|
||||
// serial button serial/const crc??
|
||||
// 5725050 1 049D D3
|
||||
// 5725050 2 049D 13
|
||||
// 36E4E80 1 04A6 44
|
||||
// 36E4E80 2 04A6 84
|
||||
// 36E4E80 3 04A6 04
|
||||
// 36E4E80 4 04A6 E4
|
||||
|
||||
// serial is larger than uint32 can't fit into serial field
|
||||
// using data2 var since its uint64_t
|
||||
instance->btn = (instance->data >> 24) & 0xF;
|
||||
instance->data_2 = ((instance->data >> 28) << 16) | ((instance->data >> 8) & 0xFFFF);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_nero_radio_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderNeroRadio* instance = context;
|
||||
@@ -400,15 +430,23 @@ void subghz_protocol_decoder_nero_radio_get_string(void* context, FuriString* ou
|
||||
uint32_t code_found_reverse_hi = code_found_reverse >> 32;
|
||||
uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff;
|
||||
|
||||
subghz_protocol_nero_radio_parse_data(&instance->generic);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:0x%lX%08lX\r\n"
|
||||
"Yek:0x%lX%08lX\r\n",
|
||||
"Yek:0x%lX%08lX\r\n"
|
||||
"Sn: 0x%llX \r\n"
|
||||
"CRC?: 0x%02X\r\n"
|
||||
"Btn: %X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
code_found_hi,
|
||||
code_found_lo,
|
||||
code_found_reverse_hi,
|
||||
code_found_reverse_lo);
|
||||
code_found_reverse_lo,
|
||||
instance->generic.data_2,
|
||||
(uint8_t)(instance->generic.data & 0xFF),
|
||||
instance->generic.btn);
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolPhoenixV2"
|
||||
#include "../blocks/custom_btn_i.h"
|
||||
|
||||
//transmission only static mode
|
||||
#define TAG "SubGhzProtocolPhoenixV2"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_phoenix_v2_const = {
|
||||
.te_short = 427,
|
||||
@@ -64,7 +64,7 @@ const SubGhzProtocolEncoder subghz_protocol_phoenix_v2_encoder = {
|
||||
|
||||
const SubGhzProtocol subghz_protocol_phoenix_v2 = {
|
||||
.name = SUBGHZ_PROTOCOL_PHOENIX_V2_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
@@ -93,6 +93,138 @@ void subghz_protocol_encoder_phoenix_v2_free(void* context) {
|
||||
free(instance);
|
||||
}
|
||||
|
||||
// Pre define functions
|
||||
static uint16_t subghz_protocol_phoenix_v2_encrypt_counter(uint64_t full_key, uint16_t counter);
|
||||
static void subghz_protocol_phoenix_v2_check_remote_controller(SubGhzBlockGeneric* instance);
|
||||
|
||||
bool subghz_protocol_phoenix_v2_create_data(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
uint32_t serial,
|
||||
uint16_t cnt,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderPhoenix_V2* instance = context;
|
||||
instance->generic.btn = 0x1;
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.cnt = cnt;
|
||||
instance->generic.data_count_bit = 52;
|
||||
|
||||
uint64_t local_data_rev =
|
||||
(uint64_t)(((uint64_t)instance->generic.cnt << 40) |
|
||||
((uint64_t)instance->generic.btn << 32) | (uint64_t)instance->generic.serial);
|
||||
|
||||
uint16_t encrypted_counter = (uint16_t)subghz_protocol_phoenix_v2_encrypt_counter(
|
||||
local_data_rev, instance->generic.cnt);
|
||||
|
||||
instance->generic.data = subghz_protocol_blocks_reverse_key(
|
||||
(uint64_t)(((uint64_t)encrypted_counter << 40) | ((uint64_t)instance->generic.btn << 32) |
|
||||
(uint64_t)instance->generic.serial),
|
||||
instance->generic.data_count_bit + 4);
|
||||
|
||||
return SubGhzProtocolStatusOk ==
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
// Get custom button code
|
||||
static uint8_t subghz_protocol_phoenix_v2_get_btn_code(void) {
|
||||
uint8_t custom_btn_id = subghz_custom_btn_get();
|
||||
uint8_t original_btn_code = subghz_custom_btn_get_original();
|
||||
uint8_t btn = original_btn_code;
|
||||
|
||||
// Set custom button
|
||||
if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) {
|
||||
// Restore original button code
|
||||
btn = original_btn_code;
|
||||
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) {
|
||||
switch(original_btn_code) {
|
||||
case 0x1:
|
||||
btn = 0x2;
|
||||
break;
|
||||
case 0x2:
|
||||
btn = 0x1;
|
||||
break;
|
||||
case 0x4:
|
||||
btn = 0x1;
|
||||
break;
|
||||
case 0x8:
|
||||
btn = 0x1;
|
||||
break;
|
||||
case 0x3:
|
||||
btn = 0x1;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) {
|
||||
switch(original_btn_code) {
|
||||
case 0x1:
|
||||
btn = 0x4;
|
||||
break;
|
||||
case 0x2:
|
||||
btn = 0x4;
|
||||
break;
|
||||
case 0x4:
|
||||
btn = 0x2;
|
||||
break;
|
||||
case 0x8:
|
||||
btn = 0x4;
|
||||
break;
|
||||
case 0x3:
|
||||
btn = 0x4;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) {
|
||||
switch(original_btn_code) {
|
||||
case 0x1:
|
||||
btn = 0x8;
|
||||
break;
|
||||
case 0x2:
|
||||
btn = 0x8;
|
||||
break;
|
||||
case 0x4:
|
||||
btn = 0x8;
|
||||
break;
|
||||
case 0x8:
|
||||
btn = 0x2;
|
||||
break;
|
||||
case 0x3:
|
||||
btn = 0x8;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_RIGHT) {
|
||||
switch(original_btn_code) {
|
||||
case 0x1:
|
||||
btn = 0x3;
|
||||
break;
|
||||
case 0x2:
|
||||
btn = 0x3;
|
||||
break;
|
||||
case 0x4:
|
||||
btn = 0x3;
|
||||
break;
|
||||
case 0x8:
|
||||
btn = 0x3;
|
||||
break;
|
||||
case 0x3:
|
||||
btn = 0x2;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderPhoenix_V2 instance
|
||||
@@ -109,6 +241,40 @@ static bool
|
||||
} else {
|
||||
instance->encoder.size_upload = size_upload;
|
||||
}
|
||||
|
||||
uint8_t btn = instance->generic.btn;
|
||||
|
||||
// Save original button for later use
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(btn);
|
||||
}
|
||||
|
||||
// Get custom button code
|
||||
// This will override the btn variable if a custom button is set
|
||||
btn = subghz_protocol_phoenix_v2_get_btn_code();
|
||||
|
||||
// Reconstruction of the data
|
||||
if(instance->generic.cnt < 0xFFFF) {
|
||||
if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) {
|
||||
instance->generic.cnt = 0;
|
||||
} else {
|
||||
instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult();
|
||||
}
|
||||
} else if((instance->generic.cnt >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) {
|
||||
instance->generic.cnt = 0;
|
||||
}
|
||||
|
||||
uint64_t local_data_rev = subghz_protocol_blocks_reverse_key(
|
||||
instance->generic.data, instance->generic.data_count_bit + 4);
|
||||
|
||||
uint16_t encrypted_counter = (uint16_t)subghz_protocol_phoenix_v2_encrypt_counter(
|
||||
local_data_rev, instance->generic.cnt);
|
||||
|
||||
instance->generic.data = subghz_protocol_blocks_reverse_key(
|
||||
(uint64_t)(((uint64_t)encrypted_counter << 40) | ((uint64_t)btn << 32) |
|
||||
(uint64_t)instance->generic.serial),
|
||||
instance->generic.data_count_bit + 4);
|
||||
|
||||
//Send header
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_phoenix_v2_const.te_short * 60);
|
||||
@@ -151,10 +317,22 @@ SubGhzProtocolStatus
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_phoenix_v2_check_remote_controller(&instance->generic);
|
||||
|
||||
if(!subghz_protocol_encoder_phoenix_v2_get_upload(instance)) {
|
||||
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t key_data[sizeof(uint64_t)] = {0};
|
||||
for(size_t i = 0; i < sizeof(uint64_t); i++) {
|
||||
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF;
|
||||
}
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
|
||||
FURI_LOG_E(TAG, "Unable to add Key");
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
@@ -276,16 +454,103 @@ void subghz_protocol_decoder_phoenix_v2_feed(void* context, bool level, uint32_t
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t subghz_protocol_phoenix_v2_encrypt_counter(uint64_t full_key, uint16_t counter) {
|
||||
uint8_t xor_key1 = (uint8_t)(full_key >> 24); // First byte of serial
|
||||
uint8_t xor_key2 = (uint8_t)((full_key >> 16) & 0xFF); // Second byte of serial
|
||||
|
||||
uint8_t byte2 = (uint8_t)(counter >> 8); // First counter byte
|
||||
uint8_t byte1 = (uint8_t)(counter & 0xFF); // Second counter byte
|
||||
|
||||
// See decrypt function before reading these comments
|
||||
for(int i = 0; i < 16; i++) {
|
||||
// The key to reversing the process is that the MSB of the *current* byte2
|
||||
// tells us what the MSB of the *previous* byte1 was. This allows us to
|
||||
// determine if the conditional XOR was applied before?.
|
||||
uint8_t msb_of_prev_byte1 = byte2 & 0x80;
|
||||
|
||||
if(msb_of_prev_byte1 == 0) {
|
||||
// reverse the XOR.
|
||||
byte2 ^= xor_key2;
|
||||
byte1 ^= xor_key1;
|
||||
}
|
||||
|
||||
// Perform the bit shuffle in reverse
|
||||
// Store the least significant bit (LSB) of the current byte1.
|
||||
uint8_t lsb_of_current_byte1 = byte1 & 1;
|
||||
|
||||
byte2 = (byte2 << 1) | lsb_of_current_byte1;
|
||||
byte1 = (byte1 >> 1) | msb_of_prev_byte1;
|
||||
}
|
||||
|
||||
return (uint16_t)byte1 << 8 | byte2;
|
||||
}
|
||||
|
||||
static uint16_t subghz_protocol_phoenix_v2_decrypt_counter(uint64_t full_key) {
|
||||
uint16_t encrypted_value = (uint16_t)((full_key >> 40) & 0xFFFF);
|
||||
|
||||
uint8_t byte1 = (uint8_t)(encrypted_value >> 8); // First encrypted counter byte
|
||||
uint8_t byte2 = (uint8_t)(encrypted_value & 0xFF); // Second encrypted counter byte
|
||||
|
||||
uint8_t xor_key1 = (uint8_t)(full_key >> 24); // First byte of serial
|
||||
uint8_t xor_key2 = (uint8_t)((full_key >> 16) & 0xFF); // Second byte of serial
|
||||
|
||||
for(int i = 0; i < 16; i++) {
|
||||
// Store the most significant bit (MSB) of byte1.
|
||||
// The check `(msb_of_byte1 == 0)` will determine if we apply the XOR keys.
|
||||
uint8_t msb_of_byte1 = byte1 & 0x80;
|
||||
|
||||
// Store the least significant bit (LSB) of byte2.
|
||||
uint8_t lsb_of_byte2 = byte2 & 1;
|
||||
|
||||
// Perform a bit shuffle between the two bytes
|
||||
byte2 = (byte2 >> 1) | msb_of_byte1;
|
||||
byte1 = (byte1 << 1) | lsb_of_byte2;
|
||||
|
||||
// Conditionally apply the XOR keys based on the original MSB of byte1.
|
||||
if(msb_of_byte1 == 0) {
|
||||
byte1 ^= xor_key1;
|
||||
// The mask `& 0x7F` clears the MSB of byte2 after the XOR.
|
||||
byte2 = (byte2 ^ xor_key2) & 0x7F;
|
||||
}
|
||||
}
|
||||
|
||||
return (uint16_t)byte2 << 8 | byte1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_phoenix_v2_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
// 2022.08 - @Skorpionm
|
||||
// 2025.07 - @xMasterX & @RocketGod-git
|
||||
// Fully supported now, with button switch and add manually
|
||||
//
|
||||
// Key samples
|
||||
// Full key example: 0xC63E01B9615720 - after subghz_protocol_blocks_reverse_key was applied
|
||||
// Serial - B9615720
|
||||
// Button - 01
|
||||
// Encrypted -> Decrypted counters
|
||||
// C63E - 025C
|
||||
// BCC1 - 025D
|
||||
// 3341 - 025E
|
||||
// 49BE - 025F
|
||||
// 99D3 - 0260
|
||||
// E32C - 0261
|
||||
|
||||
uint64_t data_rev =
|
||||
subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit + 4);
|
||||
|
||||
instance->serial = data_rev & 0xFFFFFFFF;
|
||||
instance->cnt = (data_rev >> 40) & 0xFFFF;
|
||||
instance->cnt = subghz_protocol_phoenix_v2_decrypt_counter(data_rev);
|
||||
instance->btn = (data_rev >> 32) & 0xF;
|
||||
// encrypted cnt is (data_rev >> 40) & 0xFFFF
|
||||
|
||||
// Save original button for later use
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(instance->btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(4);
|
||||
}
|
||||
|
||||
uint32_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context) {
|
||||
@@ -320,15 +585,15 @@ void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* ou
|
||||
subghz_protocol_phoenix_v2_check_remote_controller(&instance->generic);
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"V2 Phoenix %dbit\r\n"
|
||||
"Key:%05lX%08lX\r\n"
|
||||
"Sn:0x%07lX \r\n"
|
||||
"Btn:%X Cnt: 0x%04lX\r\n",
|
||||
instance->generic.protocol_name,
|
||||
"Cnt: 0x%04lX\r\n"
|
||||
"Btn: %X\r\n",
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32) & 0xFFFFFFFF,
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt);
|
||||
instance->generic.cnt,
|
||||
instance->generic.btn);
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
&subghz_protocol_hay21,
|
||||
&subghz_protocol_revers_rb2,
|
||||
&subghz_protocol_feron,
|
||||
&subghz_protocol_roger,
|
||||
};
|
||||
|
||||
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
||||
|
||||
@@ -83,3 +83,4 @@
|
||||
#include "hay21.h"
|
||||
#include "revers_rb2.h"
|
||||
#include "feron.h"
|
||||
#include "roger.h"
|
||||
|
||||
@@ -123,6 +123,22 @@ bool subghz_protocol_came_atomo_create_data(
|
||||
uint16_t cnt,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Key generation from simple data.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderPhoenix_V2 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param serial Serial number
|
||||
* @param cnt Counter value, 16 bit
|
||||
* @param preset Modulation, SubGhzRadioPreset
|
||||
* @return true On success
|
||||
*/
|
||||
bool subghz_protocol_phoenix_v2_create_data(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
uint32_t serial,
|
||||
uint16_t cnt,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* New remote generation.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderNiceFlorS instance
|
||||
|
||||
@@ -0,0 +1,449 @@
|
||||
#include "roger.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#include "../blocks/custom_btn_i.h"
|
||||
|
||||
#define TAG "SubGhzProtocolRoger"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_roger_const = {
|
||||
.te_short = 500,
|
||||
.te_long = 1000,
|
||||
.te_delta = 270,
|
||||
.min_count_bit_for_found = 28,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderRoger {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderRoger {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
RogerDecoderStepReset = 0,
|
||||
RogerDecoderStepSaveDuration,
|
||||
RogerDecoderStepCheckDuration,
|
||||
} RogerDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_roger_decoder = {
|
||||
.alloc = subghz_protocol_decoder_roger_alloc,
|
||||
.free = subghz_protocol_decoder_roger_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_roger_feed,
|
||||
.reset = subghz_protocol_decoder_roger_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_roger_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_roger_serialize,
|
||||
.deserialize = subghz_protocol_decoder_roger_deserialize,
|
||||
.get_string = subghz_protocol_decoder_roger_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_roger_encoder = {
|
||||
.alloc = subghz_protocol_encoder_roger_alloc,
|
||||
.free = subghz_protocol_encoder_roger_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_roger_deserialize,
|
||||
.stop = subghz_protocol_encoder_roger_stop,
|
||||
.yield = subghz_protocol_encoder_roger_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_roger = {
|
||||
.name = SUBGHZ_PROTOCOL_ROGER_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_roger_decoder,
|
||||
.encoder = &subghz_protocol_roger_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_roger_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderRoger* instance = malloc(sizeof(SubGhzProtocolEncoderRoger));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_roger;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 256;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_roger_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderRoger* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
// Get custom button code
|
||||
static uint8_t subghz_protocol_roger_get_btn_code(void) {
|
||||
uint8_t custom_btn_id = subghz_custom_btn_get();
|
||||
uint8_t original_btn_code = subghz_custom_btn_get_original();
|
||||
uint8_t btn = original_btn_code;
|
||||
|
||||
// Set custom button
|
||||
if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) {
|
||||
// Restore original button code
|
||||
btn = original_btn_code;
|
||||
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) {
|
||||
switch(original_btn_code) {
|
||||
case 0x1:
|
||||
btn = 0x2;
|
||||
break;
|
||||
case 0x2:
|
||||
btn = 0x1;
|
||||
break;
|
||||
case 0x4:
|
||||
btn = 0x1;
|
||||
break;
|
||||
case 0x8:
|
||||
btn = 0x1;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) {
|
||||
switch(original_btn_code) {
|
||||
case 0x1:
|
||||
btn = 0x4;
|
||||
break;
|
||||
case 0x2:
|
||||
btn = 0x4;
|
||||
break;
|
||||
case 0x4:
|
||||
btn = 0x2;
|
||||
break;
|
||||
case 0x8:
|
||||
btn = 0x4;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) {
|
||||
switch(original_btn_code) {
|
||||
case 0x1:
|
||||
btn = 0x8;
|
||||
break;
|
||||
case 0x2:
|
||||
btn = 0x8;
|
||||
break;
|
||||
case 0x4:
|
||||
btn = 0x8;
|
||||
break;
|
||||
case 0x8:
|
||||
btn = 0x2;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderRoger instance
|
||||
*/
|
||||
static void subghz_protocol_encoder_roger_get_upload(SubGhzProtocolEncoderRoger* instance) {
|
||||
furi_assert(instance);
|
||||
size_t index = 0;
|
||||
|
||||
uint8_t btn = instance->generic.btn;
|
||||
|
||||
// Save original button for later use
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(btn);
|
||||
}
|
||||
|
||||
// Get custom button code
|
||||
// This will override the btn variable if a custom button is set
|
||||
btn = subghz_protocol_roger_get_btn_code();
|
||||
|
||||
// If End is not == button - transmit as is, no custom button allowed
|
||||
// For "End" values 23 and 20 - transmit correct ending used for their buttons
|
||||
if((instance->generic.data & 0xFF) == instance->generic.btn) {
|
||||
instance->generic.data = (uint64_t)instance->generic.serial << 12 | ((uint64_t)btn << 8) |
|
||||
btn;
|
||||
} else if(((instance->generic.data & 0xFF) == 0x23) && btn == 0x1) {
|
||||
instance->generic.data = (uint64_t)instance->generic.serial << 12 | ((uint64_t)btn << 8) |
|
||||
0x20;
|
||||
} else if(((instance->generic.data & 0xFF) == 0x20) && btn == 0x2) {
|
||||
instance->generic.data = (uint64_t)instance->generic.serial << 12 | ((uint64_t)btn << 8) |
|
||||
0x23;
|
||||
}
|
||||
|
||||
// Send key and GAP
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
// Send bit 1
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_roger_const.te_long);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_roger_const.te_short * 19);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_roger_const.te_short);
|
||||
}
|
||||
} else {
|
||||
// Send bit 0
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_roger_const.te_short);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_roger_const.te_short * 19);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_roger_const.te_long);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_roger_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
// Roger Decoder
|
||||
// 2025.07 - @xMasterX (MMX)
|
||||
|
||||
// Key samples
|
||||
// 0010001111111001 0001 00100000 // S/N: 0x23F9 Btn: 0x1 End: 0x20
|
||||
// 0010001111111001 0010 00100011 // S/N: 0x23F9 Btn: 0x2 End: 0x23
|
||||
// 0101011001010110 0001 00000001 // S/N: 0x5656 Btn: 0x1 End: 0x01
|
||||
// 0101011001010110 0010 00000010 // S/N: 0x5656 Btn: 0x2 End: 0x02
|
||||
// 0000110111111110 0001 00000001 // S/N: 0x0DFE Btn: 0x1 End: 0x01
|
||||
// 0000110111111110 0100 00000100 // S/N: 0x0DFE Btn: 0x4 End: 0x04
|
||||
// 0000110111111110 0010 00000010 // S/N: 0x0DFE Btn: 0x2 End: 0x02
|
||||
// 0000110111111110 1000 00001000 // S/N: 0x0DFE Btn: 0x8 End: 0x08
|
||||
|
||||
instance->serial = instance->data >> 12;
|
||||
instance->btn = (instance->data >> 8) & 0xF;
|
||||
|
||||
// Save original button for later use
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(instance->btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(3);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_roger_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderRoger* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_roger_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
//optional parameter parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_roger_check_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_roger_get_upload(instance);
|
||||
|
||||
uint8_t key_data[sizeof(uint64_t)] = {0};
|
||||
for(size_t i = 0; i < sizeof(uint64_t); i++) {
|
||||
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF;
|
||||
}
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
|
||||
FURI_LOG_E(TAG, "Unable to add Key");
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_roger_stop(void* context) {
|
||||
SubGhzProtocolEncoderRoger* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_roger_yield(void* context) {
|
||||
SubGhzProtocolEncoderRoger* instance = context;
|
||||
|
||||
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_roger_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderRoger* instance = malloc(sizeof(SubGhzProtocolDecoderRoger));
|
||||
instance->base.protocol = &subghz_protocol_roger;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_roger_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_roger_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
instance->decoder.parser_step = RogerDecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_roger_feed(void* context, bool level, volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case RogerDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) <
|
||||
subghz_protocol_roger_const.te_delta * 5)) {
|
||||
//Found GAP
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = RogerDecoderStepSaveDuration;
|
||||
}
|
||||
break;
|
||||
case RogerDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = RogerDecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = RogerDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case RogerDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Bit 1 is long and short timing = 1000us HIGH (te_last) and 500us LOW
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_long) <
|
||||
subghz_protocol_roger_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_roger_const.te_short) <
|
||||
subghz_protocol_roger_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = RogerDecoderStepSaveDuration;
|
||||
// Bit 0 is short and long timing = 500us HIGH (te_last) and 1000us LOW
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_short) <
|
||||
subghz_protocol_roger_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_roger_const.te_long) <
|
||||
subghz_protocol_roger_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = RogerDecoderStepSaveDuration;
|
||||
} else if(
|
||||
// End of the key
|
||||
DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) <
|
||||
subghz_protocol_roger_const.te_delta * 5) {
|
||||
//Found next GAP and add bit 1 or 0
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_long) <
|
||||
subghz_protocol_roger_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
}
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_short) <
|
||||
subghz_protocol_roger_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
}
|
||||
// If got full 28 bits key reading is finished
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_roger_const.min_count_bit_for_found) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = RogerDecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = RogerDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = RogerDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_roger_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_roger_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_roger_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_roger_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderRoger* instance = context;
|
||||
|
||||
subghz_protocol_roger_check_remote_controller(&instance->generic);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key: 0x%07lX\r\n"
|
||||
"Serial: 0x%04lX\r\n"
|
||||
"End: 0x%02lX\r\n"
|
||||
"Btn: %01X",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFF),
|
||||
instance->generic.serial,
|
||||
(uint32_t)(instance->generic.data & 0xFF),
|
||||
instance->generic.btn);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_ROGER_NAME "Roger"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderRoger SubGhzProtocolDecoderRoger;
|
||||
typedef struct SubGhzProtocolEncoderRoger SubGhzProtocolEncoderRoger;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_roger_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_roger_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_roger;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderRoger.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderRoger* pointer to a SubGhzProtocolEncoderRoger instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_roger_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderRoger.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRoger instance
|
||||
*/
|
||||
void subghz_protocol_encoder_roger_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRoger instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_roger_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRoger instance
|
||||
*/
|
||||
void subghz_protocol_encoder_roger_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderRoger instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_roger_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderRoger.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderRoger* pointer to a SubGhzProtocolDecoderRoger instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_roger_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderRoger.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
*/
|
||||
void subghz_protocol_decoder_roger_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderRoger.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
*/
|
||||
void subghz_protocol_decoder_roger_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_roger_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_roger_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderRoger.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_roger_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderRoger.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_roger_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderRoger instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output);
|
||||
@@ -14,6 +14,7 @@ static const uint32_t subghz_frequency_list[] = {
|
||||
/* 300 - 348 */
|
||||
300000000,
|
||||
302757000,
|
||||
303000000,
|
||||
303875000,
|
||||
303900000,
|
||||
304250000,
|
||||
@@ -70,6 +71,7 @@ static const uint32_t subghz_frequency_list[] = {
|
||||
779000000,
|
||||
868350000,
|
||||
868400000,
|
||||
868460000,
|
||||
868800000,
|
||||
868950000,
|
||||
906400000,
|
||||
@@ -80,11 +82,9 @@ static const uint32_t subghz_frequency_list[] = {
|
||||
};
|
||||
|
||||
static const uint32_t subghz_hopper_frequency_list[] = {
|
||||
310000000,
|
||||
315000000,
|
||||
318000000,
|
||||
418000000,
|
||||
433920000,
|
||||
434420000,
|
||||
868350000,
|
||||
0,
|
||||
};
|
||||
|
||||
@@ -1146,6 +1146,7 @@ Function,+,file_browser_free,void,FileBrowser*
|
||||
Function,+,file_browser_get_view,View*,FileBrowser*
|
||||
Function,+,file_browser_set_callback,void,"FileBrowser*, FileBrowserCallback, void*"
|
||||
Function,+,file_browser_set_item_callback,void,"FileBrowser*, FileBrowserLoadItemCallback, void*"
|
||||
Function,+,file_browser_set_select_right,void,"FileBrowser*, _Bool"
|
||||
Function,+,file_browser_start,void,"FileBrowser*, FuriString*"
|
||||
Function,+,file_browser_stop,void,FileBrowser*
|
||||
Function,+,file_browser_worker_alloc,BrowserWorker*,"FuriString*, const char*, const char*, _Bool, _Bool"
|
||||
@@ -3674,6 +3675,7 @@ Function,+,subghz_protocol_faac_slh_create_data,_Bool,"void*, FlipperFormat*, ui
|
||||
Function,+,subghz_protocol_keeloq_bft_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, uint32_t, const char*, SubGhzRadioPreset*"
|
||||
Function,+,subghz_protocol_keeloq_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, const char*, SubGhzRadioPreset*"
|
||||
Function,+,subghz_protocol_nice_flor_s_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*, _Bool"
|
||||
Function,+,subghz_protocol_phoenix_v2_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint16_t, SubGhzRadioPreset*"
|
||||
Function,+,subghz_protocol_raw_file_encoder_worker_set_callback_end,void,"SubGhzProtocolEncoderRAW*, SubGhzProtocolEncoderRAWCallbackEnd, void*"
|
||||
Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*, const char*"
|
||||
Function,+,subghz_protocol_raw_get_sample_write,size_t,SubGhzProtocolDecoderRAW*
|
||||
|
||||
|
Reference in New Issue
Block a user