mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-06-13 19:43:34 -07:00
Merge remote-tracking branch 'ofw/dev' into mntm-dev
This commit is contained in:
Vendored
+1
@@ -12,6 +12,7 @@
|
||||
"SConstruct": "python",
|
||||
"*.fam": "python"
|
||||
},
|
||||
"clangd.checkUpdates": false,
|
||||
"clangd.path": "${workspaceFolder}/toolchain/current/bin/clangd@FBT_PLATFORM_EXECUTABLE_EXT@",
|
||||
"clangd.arguments": [
|
||||
"--query-driver=**/arm-none-eabi-*",
|
||||
|
||||
+9
-2
@@ -9,7 +9,12 @@
|
||||
### Added:
|
||||
- Apps:
|
||||
- Games: Quadrastic (by @ivanbarsukov)
|
||||
- OFW: RFID: EM4305 support (by @Astrrra)
|
||||
- RFID:
|
||||
- OFW: EM4305 support (by @Astrrra)
|
||||
- OFW: Noralsy Format/Brand protocol (by @zinongli)
|
||||
- OFW: BadKB: Mouse control (by @jetrp1)
|
||||
- OFW: Infrared: Universal IR signal selection (by @portasynthinca3)
|
||||
- OFW: NFC: Disney Infinity KDF plugin (by @bettse)
|
||||
- Desktop:
|
||||
- UL: Option to prevent Auto Lock when connected to USB/RPC (by @Dmitry422)
|
||||
- OFW: Add the Showtime animation (by @Astrrra)
|
||||
@@ -39,12 +44,14 @@
|
||||
### Fixed:
|
||||
- Asset Packs: Fix level-up animations not being themed (by @Willy-JL)
|
||||
- About: Fix missing Prev. button when invoked from Device Info keybind (by @Willy-JL)
|
||||
- OFW: uFBT: Bumped action version in example github workflow for project template (by @hedger)
|
||||
- OFW: NFC: ST25TB poller mode check (by @RebornedBrain)
|
||||
- Furi:
|
||||
- OFW: EventLoop unsubscribe fix (by @gsurkov & @portasynthinca3)
|
||||
- OFW: Various bug fixes and improvements (by @skotopes)
|
||||
- OFW: Ensure that `furi_record_create()` is passed a non-NULL data pointer (by @dcoles)
|
||||
- OFW: CLI: Fixed repeat in subghz tx_from_file command (by @Jnesselr)
|
||||
- OFW: VSCode: Disabled auto-update for clangd since correct version is in the toolchain (by @hedger)
|
||||
- OFW: uFBT: Bumped action version in example github workflow for project template (by @hedger)
|
||||
|
||||
### Removed:
|
||||
- JS: Removed old `widget` module, replaced by new `gui/widget` view
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <m-dict.h>
|
||||
#include <m-array.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#include "infrared_signal.h"
|
||||
@@ -12,22 +13,56 @@
|
||||
#define INFRARED_LIBRARY_HEADER "IR library file"
|
||||
#define INFRARED_LIBRARY_VERSION (1)
|
||||
|
||||
ARRAY_DEF(SignalPositionArray, size_t, M_DEFAULT_OPLIST);
|
||||
|
||||
typedef struct {
|
||||
uint32_t index;
|
||||
uint32_t count;
|
||||
size_t index;
|
||||
SignalPositionArray_t signals;
|
||||
} InfraredBruteForceRecord;
|
||||
|
||||
static inline void ir_bf_record_init(InfraredBruteForceRecord* record) {
|
||||
record->index = 0;
|
||||
SignalPositionArray_init(record->signals);
|
||||
}
|
||||
#define IR_BF_RECORD_INIT(r) (ir_bf_record_init(&(r)))
|
||||
|
||||
static inline void
|
||||
ir_bf_record_init_set(InfraredBruteForceRecord* dest, const InfraredBruteForceRecord* src) {
|
||||
dest->index = src->index;
|
||||
SignalPositionArray_init_set(dest->signals, src->signals);
|
||||
}
|
||||
#define IR_BF_RECORD_INIT_SET(d, s) (ir_bf_record_init_set(&(d), &(s)))
|
||||
|
||||
static inline void
|
||||
ir_bf_record_set(InfraredBruteForceRecord* dest, const InfraredBruteForceRecord* src) {
|
||||
dest->index = src->index;
|
||||
SignalPositionArray_set(dest->signals, src->signals);
|
||||
}
|
||||
#define IR_BF_RECORD_SET(d, s) (ir_bf_record_set(&(d), &(s)))
|
||||
|
||||
static inline void ir_bf_record_clear(InfraredBruteForceRecord* record) {
|
||||
SignalPositionArray_clear(record->signals);
|
||||
}
|
||||
#define IR_BF_RECORD_CLEAR(r) (ir_bf_record_clear(&(r)))
|
||||
|
||||
#define IR_BF_RECORD_OPLIST \
|
||||
(INIT(IR_BF_RECORD_INIT), \
|
||||
INIT_SET(IR_BF_RECORD_INIT_SET), \
|
||||
SET(IR_BF_RECORD_SET), \
|
||||
CLEAR(IR_BF_RECORD_CLEAR))
|
||||
|
||||
DICT_DEF2(
|
||||
InfraredBruteForceRecordDict,
|
||||
FuriString*,
|
||||
FURI_STRING_OPLIST,
|
||||
InfraredBruteForceRecord,
|
||||
M_POD_OPLIST);
|
||||
IR_BF_RECORD_OPLIST);
|
||||
|
||||
struct InfraredBruteForce {
|
||||
FlipperFormat* ff;
|
||||
const char* db_filename;
|
||||
FuriString* current_record_name;
|
||||
InfraredBruteForceRecord current_record;
|
||||
InfraredSignal* current_signal;
|
||||
InfraredBruteForceRecordDict_t records;
|
||||
bool is_started;
|
||||
@@ -45,6 +80,7 @@ InfraredBruteForce* infrared_brute_force_alloc(void) {
|
||||
}
|
||||
|
||||
void infrared_brute_force_free(InfraredBruteForce* brute_force) {
|
||||
furi_check(brute_force);
|
||||
furi_assert(!brute_force->is_started);
|
||||
InfraredBruteForceRecordDict_clear(brute_force->records);
|
||||
furi_string_free(brute_force->current_record_name);
|
||||
@@ -52,6 +88,7 @@ void infrared_brute_force_free(InfraredBruteForce* brute_force) {
|
||||
}
|
||||
|
||||
void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const char* db_filename) {
|
||||
furi_check(brute_force);
|
||||
furi_assert(!brute_force->is_started);
|
||||
brute_force->db_filename = db_filename;
|
||||
}
|
||||
@@ -59,6 +96,7 @@ void infrared_brute_force_set_db_filename(InfraredBruteForce* brute_force, const
|
||||
InfraredErrorCode infrared_brute_force_calculate_messages(
|
||||
InfraredBruteForce* brute_force,
|
||||
bool auto_detect_buttons) {
|
||||
furi_check(brute_force);
|
||||
furi_assert(!brute_force->is_started);
|
||||
furi_assert(brute_force->db_filename);
|
||||
InfraredErrorCode error = InfraredErrorCodeNone;
|
||||
@@ -99,12 +137,13 @@ InfraredErrorCode infrared_brute_force_calculate_messages(
|
||||
break;
|
||||
}
|
||||
|
||||
bool signals_valid = false;
|
||||
size_t signal_start = flipper_format_tell(ff);
|
||||
bool signal_valid = false;
|
||||
uint32_t auto_detect_button_index = 0;
|
||||
while(infrared_signal_read_name(ff, signal_name) == InfraredErrorCodeNone) {
|
||||
error = infrared_signal_read_body(signal, ff);
|
||||
signals_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal);
|
||||
if(!signals_valid) break;
|
||||
signal_valid = (!INFRARED_ERROR_PRESENT(error)) && infrared_signal_is_valid(signal);
|
||||
if(!signal_valid) break;
|
||||
|
||||
InfraredBruteForceRecord* record =
|
||||
InfraredBruteForceRecordDict_get(brute_force->records, signal_name);
|
||||
@@ -113,11 +152,10 @@ InfraredErrorCode infrared_brute_force_calculate_messages(
|
||||
brute_force, auto_detect_button_index++, furi_string_get_cstr(signal_name));
|
||||
record = InfraredBruteForceRecordDict_get(brute_force->records, signal_name);
|
||||
}
|
||||
if(record) { //-V547
|
||||
++(record->count);
|
||||
}
|
||||
furi_assert(record);
|
||||
SignalPositionArray_push_back(record->signals, signal_start);
|
||||
}
|
||||
if(!signals_valid) break;
|
||||
if(!signal_valid) break;
|
||||
} while(false);
|
||||
|
||||
infrared_signal_free(signal);
|
||||
@@ -132,6 +170,7 @@ bool infrared_brute_force_start(
|
||||
InfraredBruteForce* brute_force,
|
||||
uint32_t index,
|
||||
uint32_t* record_count) {
|
||||
furi_check(brute_force);
|
||||
furi_assert(!brute_force->is_started);
|
||||
bool success = false;
|
||||
*record_count = 0;
|
||||
@@ -142,9 +181,10 @@ bool infrared_brute_force_start(
|
||||
InfraredBruteForceRecordDict_next(it)) {
|
||||
const InfraredBruteForceRecordDict_itref_t* record = InfraredBruteForceRecordDict_cref(it);
|
||||
if(record->value.index == index) {
|
||||
*record_count = record->value.count;
|
||||
*record_count = SignalPositionArray_size(record->value.signals);
|
||||
if(*record_count) {
|
||||
furi_string_set(brute_force->current_record_name, record->key);
|
||||
brute_force->current_record = record->value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -163,10 +203,12 @@ bool infrared_brute_force_start(
|
||||
}
|
||||
|
||||
bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force) {
|
||||
furi_check(brute_force);
|
||||
return brute_force->is_started;
|
||||
}
|
||||
|
||||
void infrared_brute_force_stop(InfraredBruteForce* brute_force) {
|
||||
furi_check(brute_force);
|
||||
furi_assert(brute_force->is_started);
|
||||
furi_string_reset(brute_force->current_record_name);
|
||||
infrared_signal_free(brute_force->current_signal);
|
||||
@@ -177,25 +219,32 @@ void infrared_brute_force_stop(InfraredBruteForce* brute_force) {
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
bool infrared_brute_force_send_next(InfraredBruteForce* brute_force) {
|
||||
bool infrared_brute_force_send(InfraredBruteForce* brute_force, uint32_t signal_index) {
|
||||
furi_check(brute_force);
|
||||
furi_assert(brute_force->is_started);
|
||||
|
||||
const bool success = infrared_signal_search_by_name_and_read(
|
||||
brute_force->current_signal,
|
||||
brute_force->ff,
|
||||
furi_string_get_cstr(brute_force->current_record_name)) ==
|
||||
InfraredErrorCodeNone;
|
||||
if(success) {
|
||||
infrared_signal_transmit(brute_force->current_signal);
|
||||
}
|
||||
return success;
|
||||
if(signal_index >= SignalPositionArray_size(brute_force->current_record.signals)) return false;
|
||||
|
||||
size_t signal_start =
|
||||
*SignalPositionArray_cget(brute_force->current_record.signals, signal_index);
|
||||
if(!flipper_format_seek(brute_force->ff, signal_start, FlipperFormatOffsetFromStart))
|
||||
return false;
|
||||
|
||||
if(INFRARED_ERROR_PRESENT(
|
||||
infrared_signal_read_body(brute_force->current_signal, brute_force->ff)))
|
||||
return false;
|
||||
|
||||
infrared_signal_transmit(brute_force->current_signal);
|
||||
return true;
|
||||
}
|
||||
|
||||
void infrared_brute_force_add_record(
|
||||
InfraredBruteForce* brute_force,
|
||||
uint32_t index,
|
||||
const char* name) {
|
||||
InfraredBruteForceRecord value = {.index = index, .count = 0};
|
||||
InfraredBruteForceRecord value;
|
||||
ir_bf_record_init(&value);
|
||||
value.index = index;
|
||||
FuriString* key;
|
||||
key = furi_string_alloc_set(name);
|
||||
InfraredBruteForceRecordDict_set_at(brute_force->records, key, value);
|
||||
|
||||
@@ -82,18 +82,16 @@ bool infrared_brute_force_is_started(const InfraredBruteForce* brute_force);
|
||||
void infrared_brute_force_stop(InfraredBruteForce* brute_force);
|
||||
|
||||
/**
|
||||
* @brief Send the next signal from the chosen category.
|
||||
*
|
||||
* This function is called repeatedly until no more signals are left
|
||||
* in the chosen signal category.
|
||||
*
|
||||
* @warning Transmission must be started first by calling infrared_brute_force_start()
|
||||
* before calling this function.
|
||||
*
|
||||
* @param[in,out] brute_force pointer to the instance to be used.
|
||||
* @returns true if the next signal existed and could be transmitted, false otherwise.
|
||||
* @brief Send an arbitrary signal from the chosen category.
|
||||
*
|
||||
* @param[in] brute_force pointer to the instance
|
||||
* @param signal_index the index of the signal within the category, must be
|
||||
* between 0 and `record_count` as told by
|
||||
* `infrared_brute_force_start`
|
||||
*
|
||||
* @returns true on success, false otherwise
|
||||
*/
|
||||
bool infrared_brute_force_send_next(InfraredBruteForce* brute_force);
|
||||
bool infrared_brute_force_send(InfraredBruteForce* brute_force, uint32_t signal_index);
|
||||
|
||||
/**
|
||||
* @brief Add a signal category to an InfraredBruteForce instance's dictionary.
|
||||
|
||||
@@ -475,25 +475,24 @@ static void
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t record_count;
|
||||
uint32_t signal_count, current_signal = 0;
|
||||
bool running = infrared_brute_force_start(
|
||||
brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, &record_count);
|
||||
brute_force, INFRARED_BRUTE_FORCE_DUMMY_INDEX, &signal_count);
|
||||
|
||||
if(record_count <= 0) {
|
||||
if(signal_count <= 0) {
|
||||
printf("Invalid signal name.\r\n");
|
||||
break;
|
||||
}
|
||||
|
||||
printf("Sending %lu signal(s)...\r\n", record_count);
|
||||
printf("Sending %lu signal(s)...\r\n", signal_count);
|
||||
printf("Press Ctrl-C to stop.\r\n");
|
||||
|
||||
int records_sent = 0;
|
||||
while(running) {
|
||||
running = infrared_brute_force_send_next(brute_force);
|
||||
running = infrared_brute_force_send(brute_force, current_signal);
|
||||
|
||||
if(cli_cmd_interrupt_received(cli)) break;
|
||||
|
||||
printf("\r%d%% complete.", (int)((float)records_sent++ / (float)record_count * 100));
|
||||
printf("\r%d%% complete.", (int)((float)current_signal++ / (float)signal_count * 100));
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
enum InfraredCustomEventType {
|
||||
typedef enum {
|
||||
// Reserve first 100 events for button types and indexes, starting from 0
|
||||
InfraredCustomEventTypeReserved = 100,
|
||||
InfraredCustomEventTypeMenuSelected,
|
||||
@@ -13,7 +13,7 @@ enum InfraredCustomEventType {
|
||||
InfraredCustomEventTypeTextEditDone,
|
||||
InfraredCustomEventTypePopupClosed,
|
||||
InfraredCustomEventTypeButtonSelected,
|
||||
InfraredCustomEventTypeBackPressed,
|
||||
InfraredCustomEventTypePopupInput,
|
||||
InfraredCustomEventTypeTaskFinished,
|
||||
|
||||
InfraredCustomEventTypeRpcLoadFile,
|
||||
@@ -27,7 +27,7 @@ enum InfraredCustomEventType {
|
||||
|
||||
InfraredCustomEventTypeGpioTxPinChanged,
|
||||
InfraredCustomEventTypeGpioOtgChanged,
|
||||
};
|
||||
} InfraredCustomEventType;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef union {
|
||||
|
||||
@@ -2,15 +2,28 @@
|
||||
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef union {
|
||||
uint32_t packed_value;
|
||||
struct {
|
||||
bool is_paused;
|
||||
uint8_t padding;
|
||||
uint16_t signal_index;
|
||||
};
|
||||
} InfraredSceneState;
|
||||
#pragma pack(pop)
|
||||
|
||||
void infrared_scene_universal_common_item_callback(void* context, uint32_t index) {
|
||||
InfraredApp* infrared = context;
|
||||
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeButtonSelected, index);
|
||||
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
|
||||
}
|
||||
|
||||
static void infrared_scene_universal_common_progress_back_callback(void* context) {
|
||||
static void infrared_scene_universal_common_progress_input_callback(
|
||||
void* context,
|
||||
InfraredProgressViewInput input) {
|
||||
InfraredApp* infrared = context;
|
||||
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypeBackPressed, -1);
|
||||
uint32_t event = infrared_custom_event_pack(InfraredCustomEventTypePopupInput, input);
|
||||
view_dispatcher_send_custom_event(infrared->view_dispatcher, event);
|
||||
}
|
||||
|
||||
@@ -19,8 +32,8 @@ static void
|
||||
ViewStack* view_stack = infrared->view_stack;
|
||||
InfraredProgressView* progress = infrared->progress;
|
||||
infrared_progress_view_set_progress_total(progress, record_count);
|
||||
infrared_progress_view_set_back_callback(
|
||||
progress, infrared_scene_universal_common_progress_back_callback, infrared);
|
||||
infrared_progress_view_set_input_callback(
|
||||
progress, infrared_scene_universal_common_progress_input_callback, infrared);
|
||||
view_stack_add_view(view_stack, infrared_progress_view_get_view(progress));
|
||||
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
|
||||
}
|
||||
@@ -52,29 +65,111 @@ void infrared_scene_universal_common_on_enter(void* context) {
|
||||
infrared_blocking_task_start(infrared, infrared_scene_universal_common_task_callback);
|
||||
}
|
||||
|
||||
static void infrared_scene_universal_common_handle_popup_input(
|
||||
InfraredApp* infrared,
|
||||
InfraredProgressViewInput input) {
|
||||
InfraredBruteForce* brute_force = infrared->brute_force;
|
||||
SceneManager* scene_manager = infrared->scene_manager;
|
||||
uint32_t scene_id = scene_manager_get_current_scene(infrared->scene_manager);
|
||||
switch(input) {
|
||||
case InfraredProgressViewInputStop: {
|
||||
infrared_brute_force_stop(brute_force);
|
||||
infrared_scene_universal_common_hide_popup(infrared);
|
||||
break;
|
||||
}
|
||||
|
||||
case InfraredProgressViewInputPause: {
|
||||
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
|
||||
infrared_progress_view_set_paused(infrared->progress, true);
|
||||
InfraredSceneState scene_state = {
|
||||
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
|
||||
scene_state.is_paused = true;
|
||||
if(scene_state.signal_index)
|
||||
scene_state.signal_index--; // when running, the state stores the next index
|
||||
scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value);
|
||||
break;
|
||||
}
|
||||
|
||||
case InfraredProgressViewInputResume: {
|
||||
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
|
||||
infrared_progress_view_set_paused(infrared->progress, false);
|
||||
InfraredSceneState scene_state = {
|
||||
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
|
||||
scene_state.is_paused = false;
|
||||
scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value);
|
||||
break;
|
||||
}
|
||||
|
||||
case InfraredProgressViewInputNextSignal: {
|
||||
InfraredSceneState scene_state = {
|
||||
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
|
||||
scene_state.signal_index++;
|
||||
if(infrared_progress_view_set_progress(infrared->progress, scene_state.signal_index + 1))
|
||||
scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value);
|
||||
break;
|
||||
}
|
||||
|
||||
case InfraredProgressViewInputPreviousSignal: {
|
||||
InfraredSceneState scene_state = {
|
||||
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
|
||||
if(scene_state.signal_index) {
|
||||
scene_state.signal_index--;
|
||||
if(infrared_progress_view_set_progress(
|
||||
infrared->progress, scene_state.signal_index + 1))
|
||||
scene_manager_set_scene_state(scene_manager, scene_id, scene_state.packed_value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case InfraredProgressViewInputSendSingle: {
|
||||
InfraredSceneState scene_state = {
|
||||
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
|
||||
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStartSend);
|
||||
infrared_brute_force_send(infrared->brute_force, scene_state.signal_index);
|
||||
infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
|
||||
bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) {
|
||||
InfraredApp* infrared = context;
|
||||
SceneManager* scene_manager = infrared->scene_manager;
|
||||
InfraredBruteForce* brute_force = infrared->brute_force;
|
||||
uint32_t scene_id = scene_manager_get_current_scene(infrared->scene_manager);
|
||||
bool consumed = false;
|
||||
|
||||
if(infrared_brute_force_is_started(brute_force)) {
|
||||
if(event.type == SceneManagerEventTypeTick) {
|
||||
bool success = infrared_brute_force_send_next(brute_force);
|
||||
if(success) {
|
||||
success = infrared_progress_view_increase_progress(infrared->progress);
|
||||
InfraredSceneState scene_state = {
|
||||
.packed_value = scene_manager_get_scene_state(scene_manager, scene_id)};
|
||||
|
||||
if(!scene_state.is_paused) {
|
||||
bool success = infrared_brute_force_send(brute_force, scene_state.signal_index);
|
||||
if(success) {
|
||||
success = infrared_progress_view_set_progress(
|
||||
infrared->progress, scene_state.signal_index + 1);
|
||||
scene_state.signal_index++;
|
||||
scene_manager_set_scene_state(
|
||||
scene_manager, scene_id, scene_state.packed_value);
|
||||
}
|
||||
if(!success) {
|
||||
infrared_brute_force_stop(brute_force);
|
||||
infrared_scene_universal_common_hide_popup(infrared);
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
if(!success) {
|
||||
infrared_brute_force_stop(brute_force);
|
||||
infrared_scene_universal_common_hide_popup(infrared);
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) {
|
||||
infrared_brute_force_stop(brute_force);
|
||||
infrared_scene_universal_common_hide_popup(infrared);
|
||||
uint16_t event_type;
|
||||
int16_t event_value;
|
||||
infrared_custom_event_unpack(event.event, &event_type, &event_value);
|
||||
if(event_type == InfraredCustomEventTypePopupInput) {
|
||||
infrared_scene_universal_common_handle_popup_input(infrared, event_value);
|
||||
consumed = true;
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
} else {
|
||||
if(event.type == SceneManagerEventTypeBack) {
|
||||
@@ -88,6 +183,7 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e
|
||||
if(event_type == InfraredCustomEventTypeButtonSelected) {
|
||||
uint32_t record_count;
|
||||
if(infrared_brute_force_start(brute_force, event_value, &record_count)) {
|
||||
scene_manager_set_scene_state(infrared->scene_manager, scene_id, 0);
|
||||
dolphin_deed(DolphinDeedIrSend);
|
||||
infrared_scene_universal_common_show_popup(infrared, record_count);
|
||||
} else {
|
||||
|
||||
@@ -14,54 +14,80 @@
|
||||
|
||||
struct InfraredProgressView {
|
||||
View* view;
|
||||
InfraredProgressViewBackCallback back_callback;
|
||||
InfraredProgressViewInputCallback input_callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
size_t progress;
|
||||
size_t progress_total;
|
||||
bool is_paused;
|
||||
} InfraredProgressViewModel;
|
||||
|
||||
bool infrared_progress_view_increase_progress(InfraredProgressView* progress) {
|
||||
furi_assert(progress);
|
||||
bool result = false;
|
||||
|
||||
InfraredProgressViewModel* model = view_get_model(progress->view);
|
||||
if(model->progress < model->progress_total) {
|
||||
++model->progress;
|
||||
result = model->progress < model->progress_total;
|
||||
}
|
||||
view_commit_model(progress->view, true);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void infrared_progress_view_draw_callback(Canvas* canvas, void* _model) {
|
||||
InfraredProgressViewModel* model = (InfraredProgressViewModel*)_model;
|
||||
|
||||
uint8_t x = 0;
|
||||
uint8_t y = 36;
|
||||
uint8_t y = 25;
|
||||
uint8_t width = 63;
|
||||
uint8_t height = 59;
|
||||
uint8_t height = 81;
|
||||
|
||||
elements_bold_rounded_frame(canvas, x, y, width, height);
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
elements_multiline_text_aligned(
|
||||
canvas, x + 34, y + 9, AlignCenter, AlignCenter, "Sending ...");
|
||||
canvas,
|
||||
x + 32,
|
||||
y + 9,
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
model->is_paused ? "Paused" : "Sending...");
|
||||
|
||||
float progress_value = (float)model->progress / model->progress_total;
|
||||
elements_progress_bar(canvas, x + 4, y + 19, width - 7, progress_value);
|
||||
|
||||
char number_string[10] = {0};
|
||||
snprintf(
|
||||
number_string, sizeof(number_string), "%d/%d", model->progress, model->progress_total);
|
||||
char progress_string[16] = {0};
|
||||
if(model->is_paused) {
|
||||
snprintf(
|
||||
progress_string,
|
||||
sizeof(progress_string),
|
||||
"%zu/%zu",
|
||||
model->progress,
|
||||
model->progress_total);
|
||||
} else {
|
||||
uint8_t percent_value = 100 * model->progress / model->progress_total;
|
||||
snprintf(progress_string, sizeof(progress_string), "%d%%", percent_value);
|
||||
}
|
||||
elements_multiline_text_aligned(
|
||||
canvas, x + 33, y + 37, AlignCenter, AlignCenter, number_string);
|
||||
canvas, x + 33, y + 37, AlignCenter, AlignCenter, progress_string);
|
||||
|
||||
canvas_draw_icon(canvas, x + 14, y + height - 14, &I_Pin_back_arrow_10x8);
|
||||
canvas_draw_str(canvas, x + 30, y + height - 6, "= stop");
|
||||
uint8_t buttons_x = x + (model->is_paused ? 10 : 14);
|
||||
uint8_t buttons_y = y + (model->is_paused ? 46 : 50);
|
||||
|
||||
canvas_draw_icon(canvas, buttons_x + 0, buttons_y + 0, &I_Pin_back_arrow_10x8);
|
||||
canvas_draw_str(canvas, buttons_x + 14, buttons_y + 8, model->is_paused ? "resume" : "stop");
|
||||
|
||||
canvas_draw_icon(canvas, buttons_x + 1, buttons_y + 10, &I_Ok_btn_9x9);
|
||||
canvas_draw_str(canvas, buttons_x + 14, buttons_y + 17, model->is_paused ? "send 1" : "pause");
|
||||
|
||||
if(model->is_paused) {
|
||||
canvas_draw_icon(canvas, buttons_x + 2, buttons_y + 21, &I_ButtonLeftSmall_3x5);
|
||||
canvas_draw_icon(canvas, buttons_x + 7, buttons_y + 21, &I_ButtonRightSmall_3x5);
|
||||
canvas_draw_str(canvas, buttons_x + 14, buttons_y + 26, "select");
|
||||
}
|
||||
}
|
||||
|
||||
bool infrared_progress_view_set_progress(InfraredProgressView* instance, uint16_t progress) {
|
||||
bool result;
|
||||
with_view_model(
|
||||
instance->view,
|
||||
InfraredProgressViewModel * model,
|
||||
{
|
||||
result = progress <= model->progress_total;
|
||||
if(result) model->progress = progress;
|
||||
},
|
||||
true);
|
||||
return result;
|
||||
}
|
||||
|
||||
void infrared_progress_view_set_progress_total(
|
||||
@@ -74,14 +100,40 @@ void infrared_progress_view_set_progress_total(
|
||||
view_commit_model(progress->view, false);
|
||||
}
|
||||
|
||||
void infrared_progress_view_set_paused(InfraredProgressView* instance, bool is_paused) {
|
||||
with_view_model(
|
||||
instance->view, InfraredProgressViewModel * model, { model->is_paused = is_paused; }, true);
|
||||
}
|
||||
|
||||
bool infrared_progress_view_input_callback(InputEvent* event, void* context) {
|
||||
InfraredProgressView* instance = context;
|
||||
if(event->type != InputTypeShort && event->type != InputTypeRepeat) return false;
|
||||
if(!instance->input_callback) return false;
|
||||
|
||||
if((event->type == InputTypeShort) && (event->key == InputKeyBack)) {
|
||||
if(instance->back_callback) {
|
||||
instance->back_callback(instance->context);
|
||||
}
|
||||
}
|
||||
with_view_model(
|
||||
instance->view,
|
||||
InfraredProgressViewModel * model,
|
||||
{
|
||||
if(model->is_paused) {
|
||||
if(event->key == InputKeyLeft)
|
||||
instance->input_callback(
|
||||
instance->context, InfraredProgressViewInputPreviousSignal);
|
||||
else if(event->key == InputKeyRight)
|
||||
instance->input_callback(
|
||||
instance->context, InfraredProgressViewInputNextSignal);
|
||||
else if(event->key == InputKeyOk)
|
||||
instance->input_callback(
|
||||
instance->context, InfraredProgressViewInputSendSingle);
|
||||
else if(event->key == InputKeyBack)
|
||||
instance->input_callback(instance->context, InfraredProgressViewInputResume);
|
||||
} else {
|
||||
if(event->key == InputKeyOk)
|
||||
instance->input_callback(instance->context, InfraredProgressViewInputPause);
|
||||
else if(event->key == InputKeyBack)
|
||||
instance->input_callback(instance->context, InfraredProgressViewInputStop);
|
||||
}
|
||||
},
|
||||
false);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -106,12 +158,12 @@ void infrared_progress_view_free(InfraredProgressView* progress) {
|
||||
free(progress);
|
||||
}
|
||||
|
||||
void infrared_progress_view_set_back_callback(
|
||||
void infrared_progress_view_set_input_callback(
|
||||
InfraredProgressView* instance,
|
||||
InfraredProgressViewBackCallback callback,
|
||||
InfraredProgressViewInputCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
instance->back_callback = callback;
|
||||
instance->input_callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,17 @@ extern "C" {
|
||||
/** Anonymous instance */
|
||||
typedef struct InfraredProgressView InfraredProgressView;
|
||||
|
||||
/** Callback for back button handling */
|
||||
typedef void (*InfraredProgressViewBackCallback)(void*);
|
||||
typedef enum {
|
||||
InfraredProgressViewInputStop,
|
||||
InfraredProgressViewInputPause,
|
||||
InfraredProgressViewInputResume,
|
||||
InfraredProgressViewInputPreviousSignal,
|
||||
InfraredProgressViewInputNextSignal,
|
||||
InfraredProgressViewInputSendSingle,
|
||||
} InfraredProgressViewInput;
|
||||
|
||||
/** Callback for input handling */
|
||||
typedef void (*InfraredProgressViewInputCallback)(void* context, InfraredProgressViewInput event);
|
||||
|
||||
/** Allocate and initialize Infrared view
|
||||
*
|
||||
@@ -35,13 +44,12 @@ void infrared_progress_view_free(InfraredProgressView* instance);
|
||||
*/
|
||||
View* infrared_progress_view_get_view(InfraredProgressView* instance);
|
||||
|
||||
/** Increase progress on progress view module
|
||||
/** Set progress of progress view module
|
||||
*
|
||||
* @param instance view module
|
||||
* @retval true - value is incremented and maximum is reached,
|
||||
* false - value is incremented and maximum is not reached
|
||||
* @param progress progress value
|
||||
*/
|
||||
bool infrared_progress_view_increase_progress(InfraredProgressView* instance);
|
||||
bool infrared_progress_view_set_progress(InfraredProgressView* instance, uint16_t progress);
|
||||
|
||||
/** Set maximum progress value
|
||||
*
|
||||
@@ -52,15 +60,22 @@ void infrared_progress_view_set_progress_total(
|
||||
InfraredProgressView* instance,
|
||||
uint16_t progress_max);
|
||||
|
||||
/** Set back button callback
|
||||
/** Selects the variant of the View
|
||||
*
|
||||
* @param instance view instance
|
||||
* @param is_paused the "paused" variant is displayed if true; the "sending" one if false
|
||||
*/
|
||||
void infrared_progress_view_set_paused(InfraredProgressView* instance, bool is_paused);
|
||||
|
||||
/** Set input callback
|
||||
*
|
||||
* @param instance - view module
|
||||
* @param callback - callback to call for back button
|
||||
* @param callback - callback to call for input
|
||||
* @param context - context to pass to callback
|
||||
*/
|
||||
void infrared_progress_view_set_back_callback(
|
||||
void infrared_progress_view_set_input_callback(
|
||||
InfraredProgressView* instance,
|
||||
InfraredProgressViewBackCallback callback,
|
||||
InfraredProgressViewInputCallback callback,
|
||||
void* context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -321,6 +321,16 @@ App(
|
||||
sources=["plugins/supported_cards/trt.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="disney_infinity_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="disney_infinity_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
fap_libs=["mbedtls"],
|
||||
sources=["plugins/supported_cards/disney_infinity.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="sonicare_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
#include <mbedtls/sha1.h>
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
|
||||
#include <flipper_application/flipper_application.h>
|
||||
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <bit_lib/bit_lib.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define TAG "DisneyInfinity"
|
||||
#define UID_LEN 7
|
||||
|
||||
// Derived from https://nfc.toys/#new-interoperability-for-infinity
|
||||
static uint8_t seed[38] = {0x0A, 0x14, 0xFD, 0x05, 0x07, 0xFF, 0x4B, 0xCD, 0x02, 0x6B,
|
||||
0xA8, 0x3F, 0x0A, 0x3B, 0x89, 0xA9, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x28, 0x63, 0x29, 0x20, 0x44, 0x69, 0x73,
|
||||
0x6E, 0x65, 0x79, 0x20, 0x32, 0x30, 0x31, 0x33};
|
||||
|
||||
void di_key(const uint8_t* uid, MfClassicKey* key) {
|
||||
uint8_t hash[20];
|
||||
memcpy(seed + 16, uid, UID_LEN);
|
||||
mbedtls_sha1(seed, sizeof(seed), hash);
|
||||
key->data[0] = hash[3];
|
||||
key->data[1] = hash[2];
|
||||
key->data[2] = hash[1];
|
||||
key->data[3] = hash[0];
|
||||
key->data[4] = hash[7];
|
||||
key->data[5] = hash[6];
|
||||
}
|
||||
|
||||
static bool disney_infinity_read(Nfc* nfc, NfcDevice* device) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(device);
|
||||
size_t* uid_len = 0;
|
||||
bool is_read = false;
|
||||
MfClassicData* data = mf_classic_alloc();
|
||||
|
||||
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||
const uint8_t* uid_bytes = mf_classic_get_uid(data, uid_len);
|
||||
MfClassicDeviceKeys keys = {};
|
||||
|
||||
do {
|
||||
MfClassicType type = MfClassicTypeMini;
|
||||
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
data->type = type;
|
||||
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||
di_key(uid_bytes, &keys.key_a[i]);
|
||||
di_key(uid_bytes, &keys.key_b[i]);
|
||||
FURI_BIT_SET(keys.key_a_mask, i);
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_W(TAG, "Failed to read data: %d", error);
|
||||
break;
|
||||
}
|
||||
|
||||
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
is_read = mf_classic_is_card_read(data);
|
||||
} while(false);
|
||||
|
||||
mf_classic_free(data);
|
||||
|
||||
return is_read;
|
||||
}
|
||||
|
||||
static bool disney_infinity_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
size_t* uid_len = 0;
|
||||
bool parsed = false;
|
||||
FuriString* name = furi_string_alloc();
|
||||
const uint8_t verify_sector = 0;
|
||||
MfClassicKey key = {};
|
||||
|
||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||
const uint8_t* uid_bytes = mf_classic_get_uid(data, uid_len);
|
||||
|
||||
do {
|
||||
// verify key
|
||||
MfClassicSectorTrailer* sec_tr =
|
||||
mf_classic_get_sector_trailer_by_sector(data, verify_sector);
|
||||
|
||||
di_key(uid_bytes, &key);
|
||||
if(memcmp(key.data, sec_tr->key_a.data, 6) != 0) break;
|
||||
|
||||
// At some point I'd like to add name lookup like Skylanders
|
||||
furi_string_printf(parsed_data, "\e#Disney Infinity\n");
|
||||
|
||||
parsed = true;
|
||||
|
||||
} while(false);
|
||||
|
||||
furi_string_free(name);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/* Actual implementation of app<>plugin interface */
|
||||
static const NfcSupportedCardsPlugin disney_infinity_plugin = {
|
||||
.protocol = NfcProtocolMfClassic,
|
||||
.verify = NULL, // Need UID to verify key(s)
|
||||
.read = disney_infinity_read,
|
||||
.parse = disney_infinity_parse,
|
||||
};
|
||||
|
||||
/* Plugin descriptor to comply with basic plugin specification */
|
||||
static const FlipperAppPluginDescriptor disney_infinity_plugin_descriptor = {
|
||||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||
.entry_point = &disney_infinity_plugin,
|
||||
};
|
||||
|
||||
/* Plugin entry point - must return a pointer to const descriptor */
|
||||
const FlipperAppPluginDescriptor* disney_infinity_plugin_ep(void) {
|
||||
return &disney_infinity_plugin_descriptor;
|
||||
}
|
||||
@@ -617,7 +617,7 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context)
|
||||
if(furi_string_size(args)) {
|
||||
char* args_cstr = (char*)furi_string_get_cstr(args);
|
||||
StrintParseError parse_err = StrintParseNoError;
|
||||
parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10);
|
||||
parse_err |= strint_to_uint32(args_cstr, &args_cstr, &repeat, 10);
|
||||
parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10);
|
||||
if(parse_err) {
|
||||
cli_print_usage(
|
||||
|
||||
@@ -230,6 +230,11 @@ bool scene_manager_search_and_switch_to_another_scene(
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t scene_manager_get_current_scene(SceneManager* scene_manager) {
|
||||
furi_check(scene_manager);
|
||||
return *SceneManagerIdStack_back(scene_manager->scene_id_stack);
|
||||
}
|
||||
|
||||
void scene_manager_stop(SceneManager* scene_manager) {
|
||||
furi_check(scene_manager);
|
||||
|
||||
|
||||
@@ -170,6 +170,14 @@ bool scene_manager_search_and_switch_to_another_scene(
|
||||
SceneManager* scene_manager,
|
||||
uint32_t scene_id);
|
||||
|
||||
/** Get id of current scene
|
||||
*
|
||||
* @param scene_manager SceneManager instance
|
||||
*
|
||||
* @return Scene ID
|
||||
*/
|
||||
uint32_t scene_manager_get_current_scene(SceneManager* scene_manager);
|
||||
|
||||
/** Exit from current scene
|
||||
*
|
||||
* @param scene_manager SceneManager instance
|
||||
|
||||
@@ -189,7 +189,19 @@ Example:
|
||||
ID 1234:abcd Flipper Devices:Flipper Zero
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> VID and PID are hex codes and are mandatory.
|
||||
> Manufacturer and Product are text strings and are optional.
|
||||
VID and PID are hex codes and are mandatory. Manufacturer and Product are text strings and are optional.
|
||||
|
||||
## Mouse Commands
|
||||
|
||||
Mouse movement and click commands. Mouse click commands support HOLD functionality.
|
||||
|
||||
| Command | Parameters | Notes |
|
||||
| ------------- | -------------------------------| -------------------------------- |
|
||||
| LEFTCLICK | None | |
|
||||
| LEFT_CLICK | None | functionally same as LEFTCLICK |
|
||||
| RIGHTCLICK | None | |
|
||||
| RIGHT_CLICK | None | functionally same as RIGHTCLICK |
|
||||
| MOUSEMOVE | x y: int move mount/direction | |
|
||||
| MOUSE_MOVE | x y: int move mount/direction | functionally same as MOUSEMOVE |
|
||||
| MOUSESCROLL | delta: int scroll distance | |
|
||||
| MOUSE_SCROLL | delta: int scroll distance | functionally same as MOUSESCROLL |
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
#include "flipper_format_stream.h"
|
||||
#include "flipper_format_stream_i.h"
|
||||
|
||||
// permits direct casting between `FlipperFormatOffset` and `StreamOffset`
|
||||
static_assert((size_t)FlipperFormatOffsetFromCurrent == (size_t)StreamOffsetFromCurrent);
|
||||
static_assert((size_t)FlipperFormatOffsetFromStart == (size_t)StreamOffsetFromStart);
|
||||
static_assert((size_t)FlipperFormatOffsetFromEnd == (size_t)StreamOffsetFromEnd);
|
||||
|
||||
/********************************** Private **********************************/
|
||||
struct FlipperFormat {
|
||||
Stream* stream;
|
||||
@@ -127,6 +132,17 @@ bool flipper_format_rewind(FlipperFormat* flipper_format) {
|
||||
return stream_rewind(flipper_format->stream);
|
||||
}
|
||||
|
||||
size_t flipper_format_tell(FlipperFormat* flipper_format) {
|
||||
furi_check(flipper_format);
|
||||
return stream_tell(flipper_format->stream);
|
||||
}
|
||||
|
||||
bool flipper_format_seek(FlipperFormat* flipper_format, int32_t offset, FlipperFormatOffset anchor) {
|
||||
furi_check(flipper_format);
|
||||
// direct usage of `anchor` made valid by `static_assert`s at the top of this file
|
||||
return stream_seek(flipper_format->stream, offset, (StreamOffset)anchor);
|
||||
}
|
||||
|
||||
bool flipper_format_seek_to_end(FlipperFormat* flipper_format) {
|
||||
furi_check(flipper_format);
|
||||
return stream_seek(flipper_format->stream, 0, StreamOffsetFromEnd);
|
||||
|
||||
@@ -94,6 +94,12 @@ extern "C" {
|
||||
|
||||
typedef struct FlipperFormat FlipperFormat;
|
||||
|
||||
typedef enum {
|
||||
FlipperFormatOffsetFromCurrent,
|
||||
FlipperFormatOffsetFromStart,
|
||||
FlipperFormatOffsetFromEnd,
|
||||
} FlipperFormatOffset;
|
||||
|
||||
/** Allocate FlipperFormat as string.
|
||||
*
|
||||
* @return FlipperFormat* pointer to a FlipperFormat instance
|
||||
@@ -216,6 +222,24 @@ void flipper_format_set_strict_mode(FlipperFormat* flipper_format, bool strict_m
|
||||
*/
|
||||
bool flipper_format_rewind(FlipperFormat* flipper_format);
|
||||
|
||||
/** Get the RW pointer position
|
||||
*
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
*
|
||||
* @return RW pointer position
|
||||
*/
|
||||
size_t flipper_format_tell(FlipperFormat* flipper_format);
|
||||
|
||||
/** Set the RW pointer position to an arbitrary value
|
||||
*
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param offset Offset relative to the anchor point
|
||||
* @param anchor Anchor point (e.g. start of file)
|
||||
*
|
||||
* @return True on success
|
||||
*/
|
||||
bool flipper_format_seek(FlipperFormat* flipper_format, int32_t offset, FlipperFormatOffset anchor);
|
||||
|
||||
/** Move the RW pointer at the end. Can be useful if you want to add some data
|
||||
* after reading.
|
||||
*
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "protocol_nexwatch.h"
|
||||
#include "protocol_securakey.h"
|
||||
#include "protocol_gproxii.h"
|
||||
#include "protocol_noralsy.h"
|
||||
#include "protocol_insta_fob.h"
|
||||
|
||||
const ProtocolBase* lfrfid_protocols[] = {
|
||||
@@ -46,5 +47,6 @@ const ProtocolBase* lfrfid_protocols[] = {
|
||||
[LFRFIDProtocolNexwatch] = &protocol_nexwatch,
|
||||
[LFRFIDProtocolSecurakey] = &protocol_securakey,
|
||||
[LFRFIDProtocolGProxII] = &protocol_gproxii,
|
||||
[LFRFIDProtocolNoralsy] = &protocol_noralsy,
|
||||
[LFRFIDProtocolInstaFob] = &protocol_insta_fob,
|
||||
};
|
||||
|
||||
@@ -32,6 +32,7 @@ typedef enum {
|
||||
LFRFIDProtocolNexwatch,
|
||||
LFRFIDProtocolSecurakey,
|
||||
LFRFIDProtocolGProxII,
|
||||
LFRFIDProtocolNoralsy,
|
||||
LFRFIDProtocolInstaFob,
|
||||
|
||||
LFRFIDProtocolMax,
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
#include <furi.h>
|
||||
#include <toolbox/protocols/protocol.h>
|
||||
#include <toolbox/manchester_decoder.h>
|
||||
#include <bit_lib/bit_lib.h>
|
||||
#include "lfrfid_protocols.h"
|
||||
|
||||
#define NORALSY_CLOCK_PER_BIT (32)
|
||||
|
||||
#define NORALSY_ENCODED_BIT_SIZE (96)
|
||||
#define NORALSY_ENCODED_BYTE_SIZE ((NORALSY_ENCODED_BIT_SIZE) / 8)
|
||||
#define NORALSY_PREAMBLE_BIT_SIZE (32)
|
||||
#define NORALSY_PREAMBLE_BYTE_SIZE ((NORALSY_PREAMBLE_BIT_SIZE) / 8)
|
||||
#define NORALSY_ENCODED_BYTE_FULL_SIZE ((NORALSY_ENCODED_BIT_SIZE) / 8)
|
||||
#define NORALSY_DECODED_DATA_SIZE ((NORALSY_ENCODED_BIT_SIZE) / 8)
|
||||
|
||||
#define NORALSY_READ_SHORT_TIME (128)
|
||||
#define NORALSY_READ_LONG_TIME (256)
|
||||
#define NORALSY_READ_JITTER_TIME (60)
|
||||
|
||||
#define NORALSY_READ_SHORT_TIME_LOW (NORALSY_READ_SHORT_TIME - NORALSY_READ_JITTER_TIME)
|
||||
#define NORALSY_READ_SHORT_TIME_HIGH (NORALSY_READ_SHORT_TIME + NORALSY_READ_JITTER_TIME)
|
||||
#define NORALSY_READ_LONG_TIME_LOW (NORALSY_READ_LONG_TIME - NORALSY_READ_JITTER_TIME)
|
||||
#define NORALSY_READ_LONG_TIME_HIGH (NORALSY_READ_LONG_TIME + NORALSY_READ_JITTER_TIME)
|
||||
|
||||
#define TAG "NORALSY"
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[NORALSY_ENCODED_BYTE_SIZE];
|
||||
uint8_t encoded_data[NORALSY_ENCODED_BYTE_SIZE];
|
||||
|
||||
uint8_t encoded_data_index;
|
||||
bool encoded_polarity;
|
||||
|
||||
ManchesterState decoder_manchester_state;
|
||||
} ProtocolNoralsy;
|
||||
|
||||
ProtocolNoralsy* protocol_noralsy_alloc(void) {
|
||||
ProtocolNoralsy* protocol = malloc(sizeof(ProtocolNoralsy));
|
||||
return (void*)protocol;
|
||||
}
|
||||
|
||||
void protocol_noralsy_free(ProtocolNoralsy* protocol) {
|
||||
free(protocol);
|
||||
}
|
||||
|
||||
static uint8_t noralsy_chksum(uint8_t* bits, uint8_t len) {
|
||||
uint8_t sum = 0;
|
||||
for(uint8_t i = 0; i < len; i += 4)
|
||||
sum ^= bit_lib_get_bits(bits, i, 4);
|
||||
return sum & 0x0F;
|
||||
}
|
||||
|
||||
uint8_t* protocol_noralsy_get_data(ProtocolNoralsy* protocol) {
|
||||
return protocol->data;
|
||||
}
|
||||
|
||||
static void protocol_noralsy_decode(ProtocolNoralsy* protocol) {
|
||||
bit_lib_copy_bits(protocol->data, 0, NORALSY_ENCODED_BIT_SIZE, protocol->encoded_data, 0);
|
||||
}
|
||||
|
||||
static bool protocol_noralsy_can_be_decoded(ProtocolNoralsy* protocol) {
|
||||
// check 12 bits preamble
|
||||
// If necessary, use 0xBB0214FF for 32 bit preamble check
|
||||
// However, it is not confirmed the 13-16 bit are static.
|
||||
if(bit_lib_get_bits_16(protocol->encoded_data, 0, 12) != 0b101110110000) return false;
|
||||
uint8_t calc1 = noralsy_chksum(&protocol->encoded_data[4], 40);
|
||||
uint8_t calc2 = noralsy_chksum(&protocol->encoded_data[0], 76);
|
||||
uint8_t chk1 = bit_lib_get_bits(protocol->encoded_data, 72, 4);
|
||||
uint8_t chk2 = bit_lib_get_bits(protocol->encoded_data, 76, 4);
|
||||
if(calc1 != chk1 || calc2 != chk2) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void protocol_noralsy_decoder_start(ProtocolNoralsy* protocol) {
|
||||
memset(protocol->encoded_data, 0, NORALSY_ENCODED_BYTE_FULL_SIZE);
|
||||
manchester_advance(
|
||||
protocol->decoder_manchester_state,
|
||||
ManchesterEventReset,
|
||||
&protocol->decoder_manchester_state,
|
||||
NULL);
|
||||
}
|
||||
|
||||
bool protocol_noralsy_decoder_feed(ProtocolNoralsy* protocol, bool level, uint32_t duration) {
|
||||
bool result = false;
|
||||
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
|
||||
if(duration > NORALSY_READ_SHORT_TIME_LOW && duration < NORALSY_READ_SHORT_TIME_HIGH) {
|
||||
if(!level) {
|
||||
event = ManchesterEventShortHigh;
|
||||
} else {
|
||||
event = ManchesterEventShortLow;
|
||||
}
|
||||
} else if(duration > NORALSY_READ_LONG_TIME_LOW && duration < NORALSY_READ_LONG_TIME_HIGH) {
|
||||
if(!level) {
|
||||
event = ManchesterEventLongHigh;
|
||||
} else {
|
||||
event = ManchesterEventLongLow;
|
||||
}
|
||||
}
|
||||
|
||||
if(event != ManchesterEventReset) {
|
||||
bool data;
|
||||
bool data_ok = manchester_advance(
|
||||
protocol->decoder_manchester_state, event, &protocol->decoder_manchester_state, &data);
|
||||
|
||||
if(data_ok) {
|
||||
bit_lib_push_bit(protocol->encoded_data, NORALSY_ENCODED_BYTE_FULL_SIZE, data);
|
||||
|
||||
if(protocol_noralsy_can_be_decoded(protocol)) {
|
||||
protocol_noralsy_decode(protocol);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool protocol_noralsy_encoder_start(ProtocolNoralsy* protocol) {
|
||||
bit_lib_copy_bits(protocol->encoded_data, 0, NORALSY_ENCODED_BIT_SIZE, protocol->data, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LevelDuration protocol_noralsy_encoder_yield(ProtocolNoralsy* protocol) {
|
||||
bool level = bit_lib_get_bit(protocol->encoded_data, protocol->encoded_data_index);
|
||||
uint32_t duration = NORALSY_CLOCK_PER_BIT / 2;
|
||||
|
||||
if(protocol->encoded_polarity) {
|
||||
protocol->encoded_polarity = false;
|
||||
} else {
|
||||
level = !level;
|
||||
|
||||
protocol->encoded_polarity = true;
|
||||
bit_lib_increment_index(protocol->encoded_data_index, NORALSY_ENCODED_BIT_SIZE);
|
||||
}
|
||||
|
||||
return level_duration_make(level, duration);
|
||||
}
|
||||
|
||||
bool protocol_noralsy_write_data(ProtocolNoralsy* protocol, void* data) {
|
||||
LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data;
|
||||
bool result = false;
|
||||
|
||||
// Correct protocol data by redecoding
|
||||
protocol_noralsy_encoder_start(protocol);
|
||||
protocol_noralsy_decode(protocol);
|
||||
|
||||
protocol_noralsy_encoder_start(protocol);
|
||||
|
||||
if(request->write_type == LFRFIDWriteTypeT5577) {
|
||||
request->t5577.block[0] =
|
||||
(LFRFID_T5577_MODULATION_MANCHESTER | LFRFID_T5577_BITRATE_RF_32 |
|
||||
(3 << LFRFID_T5577_MAXBLOCK_SHIFT) | LFRFID_T5577_ST_TERMINATOR);
|
||||
// In fact, base on the current two dump samples from Iceman server,
|
||||
// Noralsy are usually T5577s with config = 0x00088C6A
|
||||
// But the `C` and `A` are not explainable by the ATA5577C datasheet
|
||||
// and they don't affect reading whatsoever.
|
||||
// So we are mimicing Proxmark's solution here. Leave those nibbles as zero.
|
||||
request->t5577.block[1] = bit_lib_get_bits_32(protocol->encoded_data, 0, 32);
|
||||
request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32);
|
||||
request->t5577.block[3] = bit_lib_get_bits_32(protocol->encoded_data, 64, 32);
|
||||
request->t5577.blocks_to_write = 4;
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void protocol_noralsy_render_data_internal(ProtocolNoralsy* protocol, FuriString* result) {
|
||||
UNUSED(protocol);
|
||||
uint32_t raw2 = bit_lib_get_bits_32(protocol->data, 32, 32);
|
||||
uint32_t raw3 = bit_lib_get_bits_32(protocol->data, 64, 32);
|
||||
uint32_t cardid = ((raw2 & 0xFFF00000) >> 20) << 16;
|
||||
cardid |= (raw2 & 0xFF) << 8;
|
||||
cardid |= ((raw3 & 0xFF000000) >> 24);
|
||||
|
||||
uint8_t year = (raw2 & 0x000ff000) >> 12;
|
||||
bool tag_is_gen_z = (year > 0x60);
|
||||
furi_string_printf(
|
||||
result,
|
||||
"Card ID: %07lx\n"
|
||||
"Year: %s%02x",
|
||||
cardid,
|
||||
tag_is_gen_z ? "19" : "20",
|
||||
year);
|
||||
}
|
||||
|
||||
void protocol_noralsy_render_data(ProtocolNoralsy* protocol, FuriString* result) {
|
||||
protocol_noralsy_render_data_internal(protocol, result);
|
||||
}
|
||||
|
||||
void protocol_noralsy_render_brief_data(ProtocolNoralsy* protocol, FuriString* result) {
|
||||
protocol_noralsy_render_data_internal(protocol, result);
|
||||
}
|
||||
|
||||
const ProtocolBase protocol_noralsy = {
|
||||
.name = "Noralsy",
|
||||
.manufacturer = "Noralsy",
|
||||
.data_size = NORALSY_DECODED_DATA_SIZE,
|
||||
.features = LFRFIDFeatureASK,
|
||||
.validate_count = 3,
|
||||
.alloc = (ProtocolAlloc)protocol_noralsy_alloc,
|
||||
.free = (ProtocolFree)protocol_noralsy_free,
|
||||
.get_data = (ProtocolGetData)protocol_noralsy_get_data,
|
||||
.decoder =
|
||||
{
|
||||
.start = (ProtocolDecoderStart)protocol_noralsy_decoder_start,
|
||||
.feed = (ProtocolDecoderFeed)protocol_noralsy_decoder_feed,
|
||||
},
|
||||
.encoder =
|
||||
{
|
||||
.start = (ProtocolEncoderStart)protocol_noralsy_encoder_start,
|
||||
.yield = (ProtocolEncoderYield)protocol_noralsy_encoder_yield,
|
||||
},
|
||||
.render_data = (ProtocolRenderData)protocol_noralsy_render_data,
|
||||
.render_brief_data = (ProtocolRenderData)protocol_noralsy_render_brief_data,
|
||||
.write_data = (ProtocolWriteData)protocol_noralsy_write_data,
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include <toolbox/protocols/protocol.h>
|
||||
|
||||
extern const ProtocolBase protocol_noralsy;
|
||||
@@ -319,6 +319,7 @@ SubGhzProtocolStatus
|
||||
res = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
res = SubGhzProtocolStatusOk;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "ms-python.black-formatter"
|
||||
},
|
||||
"clangd.checkUpdates": false,
|
||||
"clangd.path": "@UFBT_TOOLCHAIN_CLANGD@",
|
||||
"clangd.arguments": [
|
||||
"--query-driver=**/arm-none-eabi-*",
|
||||
|
||||
@@ -1038,6 +1038,7 @@ Function,+,flipper_format_read_int32,_Bool,"FlipperFormat*, const char*, int32_t
|
||||
Function,+,flipper_format_read_string,_Bool,"FlipperFormat*, const char*, FuriString*"
|
||||
Function,+,flipper_format_read_uint32,_Bool,"FlipperFormat*, const char*, uint32_t*, const uint16_t"
|
||||
Function,+,flipper_format_rewind,_Bool,FlipperFormat*
|
||||
Function,+,flipper_format_seek,_Bool,"FlipperFormat*, int32_t, FlipperFormatOffset"
|
||||
Function,+,flipper_format_seek_to_end,_Bool,FlipperFormat*
|
||||
Function,+,flipper_format_set_strict_mode,void,"FlipperFormat*, _Bool"
|
||||
Function,+,flipper_format_stream_delete_key_and_write,_Bool,"Stream*, FlipperStreamWriteData*, _Bool"
|
||||
@@ -1046,6 +1047,7 @@ Function,+,flipper_format_stream_read_value_line,_Bool,"Stream*, const char*, Fl
|
||||
Function,+,flipper_format_stream_write_comment_cstr,_Bool,"Stream*, const char*"
|
||||
Function,+,flipper_format_stream_write_value_line,_Bool,"Stream*, FlipperStreamWriteData*"
|
||||
Function,+,flipper_format_string_alloc,FlipperFormat*,
|
||||
Function,+,flipper_format_tell,size_t,FlipperFormat*
|
||||
Function,+,flipper_format_update_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t"
|
||||
Function,+,flipper_format_update_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t"
|
||||
Function,+,flipper_format_update_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t"
|
||||
@@ -2469,6 +2471,7 @@ Function,-,scalbnl,long double,"long double, int"
|
||||
Function,-,scanf,int,"const char*, ..."
|
||||
Function,+,scene_manager_alloc,SceneManager*,"const SceneManagerHandlers*, void*"
|
||||
Function,+,scene_manager_free,void,SceneManager*
|
||||
Function,+,scene_manager_get_current_scene,uint32_t,SceneManager*
|
||||
Function,+,scene_manager_get_scene_state,uint32_t,"const SceneManager*, uint32_t"
|
||||
Function,+,scene_manager_handle_back_event,_Bool,SceneManager*
|
||||
Function,+,scene_manager_handle_custom_event,_Bool,"SceneManager*, uint32_t"
|
||||
|
||||
|
@@ -1200,6 +1200,7 @@ Function,+,flipper_format_read_int32,_Bool,"FlipperFormat*, const char*, int32_t
|
||||
Function,+,flipper_format_read_string,_Bool,"FlipperFormat*, const char*, FuriString*"
|
||||
Function,+,flipper_format_read_uint32,_Bool,"FlipperFormat*, const char*, uint32_t*, const uint16_t"
|
||||
Function,+,flipper_format_rewind,_Bool,FlipperFormat*
|
||||
Function,+,flipper_format_seek,_Bool,"FlipperFormat*, int32_t, FlipperFormatOffset"
|
||||
Function,+,flipper_format_seek_to_end,_Bool,FlipperFormat*
|
||||
Function,+,flipper_format_set_strict_mode,void,"FlipperFormat*, _Bool"
|
||||
Function,+,flipper_format_stream_delete_key_and_write,_Bool,"Stream*, FlipperStreamWriteData*, _Bool"
|
||||
@@ -1208,6 +1209,7 @@ Function,+,flipper_format_stream_read_value_line,_Bool,"Stream*, const char*, Fl
|
||||
Function,+,flipper_format_stream_write_comment_cstr,_Bool,"Stream*, const char*"
|
||||
Function,+,flipper_format_stream_write_value_line,_Bool,"Stream*, FlipperStreamWriteData*"
|
||||
Function,+,flipper_format_string_alloc,FlipperFormat*,
|
||||
Function,+,flipper_format_tell,size_t,FlipperFormat*
|
||||
Function,+,flipper_format_update_bool,_Bool,"FlipperFormat*, const char*, const _Bool*, const uint16_t"
|
||||
Function,+,flipper_format_update_float,_Bool,"FlipperFormat*, const char*, const float*, const uint16_t"
|
||||
Function,+,flipper_format_update_hex,_Bool,"FlipperFormat*, const char*, const uint8_t*, const uint16_t"
|
||||
@@ -3203,6 +3205,7 @@ Function,-,scalbnl,long double,"long double, int"
|
||||
Function,-,scanf,int,"const char*, ..."
|
||||
Function,+,scene_manager_alloc,SceneManager*,"const SceneManagerHandlers*, void*"
|
||||
Function,+,scene_manager_free,void,SceneManager*
|
||||
Function,+,scene_manager_get_current_scene,uint32_t,SceneManager*
|
||||
Function,+,scene_manager_get_scene_state,uint32_t,"const SceneManager*, uint32_t"
|
||||
Function,+,scene_manager_handle_back_event,_Bool,SceneManager*
|
||||
Function,+,scene_manager_handle_custom_event,_Bool,"SceneManager*, uint32_t"
|
||||
|
||||
|
Reference in New Issue
Block a user