mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-28 01:58:11 -07:00
Merge branch 'Eng1n33r:dev' into subbrute-deep-refactor
This commit is contained in:
@@ -109,7 +109,7 @@ const BtHidKeyboardKey bt_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = {
|
||||
{.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS},
|
||||
},
|
||||
{
|
||||
{.width = 1, .icon = &I_Pin_arrow_up7x9, .value = HID_KEYBOARD_L_SHIFT},
|
||||
{.width = 1, .icon = &I_Pin_arrow_up_7x9, .value = HID_KEYBOARD_L_SHIFT},
|
||||
{.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYPAD_COMMA},
|
||||
{.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT},
|
||||
{.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR},
|
||||
|
||||
@@ -53,7 +53,7 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) {
|
||||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up7x9);
|
||||
canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up_7x9);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Down
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
# Flipfrid
|
||||
|
||||
Basic EM4100 Fuzzer
|
||||
Basic EM4100 and HIDProx Fuzzer.
|
||||
|
||||
## Why
|
||||
|
||||
Flipfrid is a simple Rfid fuzzer using EM4100 protocol (125khz).
|
||||
Objective is to provide a simple to use fuzzer to test readers by emulating various cards.
|
||||
|
||||
EM4100 cards use a 1 byte customer id and 4 bytes card id.
|
||||
- EM4100 cards use a 1 byte customer id and 4 bytes card id.
|
||||
- HIDProx cards use a 2 byte customer id and 3 byte card id.
|
||||
|
||||
## How
|
||||
|
||||
There is 4 modes :
|
||||
- Default key loop over 16 factory/default keys and emulate each one after one ;
|
||||
- BF customer id. just an iteration from 0X00 to 0XFF on the first byte ;
|
||||
- Load Dump file : Load an existing EM4100 dump generated by Flipperzero, select an index and bruteforce from 0X00 to 0XFF;
|
||||
- Uids list: loop over a text file (one uid per line)
|
||||
1) Select the Protocol with the left and right arrows
|
||||
2) Select the Mode with the up and down arrows
|
||||
|
||||
### Info
|
||||
|
||||
There are 2 Protocols:
|
||||
- EM4100
|
||||
- HIDProx
|
||||
|
||||
There are 4 modes:
|
||||
- Default Values: Try factory/default keys and emulate one after the other.
|
||||
- BF customer id: An iteration from 0X00 to 0XFF on the first byte.
|
||||
- Load Dump file: Load an existing dump (.rfid) generated by Flipperzero, select an index and bruteforce from 0X00 to 0XFF;
|
||||
- Uids list: Iterate over an input text file (one uid per line) and emulate one after the other.
|
||||
|
||||
|
||||
|
||||
|
||||
TODO :
|
||||
- blank screen on back press
|
||||
- Add second byte test to `BF customer id`
|
||||
|
||||
@@ -64,6 +64,7 @@ FlipFridState* flipfrid_alloc() {
|
||||
flipfrid->is_attacking = false;
|
||||
flipfrid->key_index = 0;
|
||||
flipfrid->menu_index = 0;
|
||||
flipfrid->menu_proto_index = 0;
|
||||
|
||||
flipfrid->attack = FlipFridAttackDefaultValues;
|
||||
flipfrid->notify = furi_record_open(RECORD_NOTIFICATION);
|
||||
@@ -73,12 +74,14 @@ FlipFridState* flipfrid_alloc() {
|
||||
flipfrid->data[2] = 0x00;
|
||||
flipfrid->data[3] = 0x00;
|
||||
flipfrid->data[4] = 0x00;
|
||||
flipfrid->data[5] = 0x00;
|
||||
|
||||
flipfrid->payload[0] = 0x00;
|
||||
flipfrid->payload[1] = 0x00;
|
||||
flipfrid->payload[2] = 0x00;
|
||||
flipfrid->payload[3] = 0x00;
|
||||
flipfrid->payload[4] = 0x00;
|
||||
flipfrid->payload[5] = 0x00;
|
||||
|
||||
//Dialog
|
||||
flipfrid->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
|
||||
@@ -28,6 +28,11 @@ typedef enum {
|
||||
FlipFridAttackLoadFileCustomUids,
|
||||
} FlipFridAttacks;
|
||||
|
||||
typedef enum {
|
||||
EM4100,
|
||||
HIDProx,
|
||||
} FlipFridProtos;
|
||||
|
||||
typedef enum {
|
||||
NoneScene,
|
||||
SceneEntryPoint,
|
||||
@@ -56,13 +61,16 @@ typedef struct {
|
||||
FlipFridScene previous_scene;
|
||||
NotificationApp* notify;
|
||||
u_int8_t menu_index;
|
||||
u_int8_t menu_proto_index;
|
||||
|
||||
string_t data_str;
|
||||
uint8_t data[5];
|
||||
uint8_t payload[5];
|
||||
uint8_t data[6];
|
||||
uint8_t payload[6];
|
||||
uint8_t attack_step;
|
||||
FlipFridAttacks attack;
|
||||
FlipFridProtos proto;
|
||||
string_t attack_name;
|
||||
string_t proto_name;
|
||||
|
||||
DialogsApp* dialogs;
|
||||
string_t notification_msg;
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
#include "flipfrid_scene_entrypoint.h"
|
||||
|
||||
string_t menu_items[4];
|
||||
string_t menu_proto_items[2];
|
||||
|
||||
void flipfrid_scene_entrypoint_menu_callback(FlipFridState* context, uint32_t index) {
|
||||
void flipfrid_scene_entrypoint_menu_callback(
|
||||
FlipFridState* context,
|
||||
uint32_t index,
|
||||
uint32_t proto_index) {
|
||||
switch(index) {
|
||||
case FlipFridAttackDefaultValues:
|
||||
context->attack = FlipFridAttackDefaultValues;
|
||||
@@ -27,6 +31,19 @@ void flipfrid_scene_entrypoint_menu_callback(FlipFridState* context, uint32_t in
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch(proto_index) {
|
||||
case EM4100:
|
||||
context->proto = EM4100;
|
||||
string_set_str(context->proto_name, "EM4100");
|
||||
break;
|
||||
case HIDProx:
|
||||
context->proto = HIDProx;
|
||||
string_set_str(context->proto_name, "HIDProx");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void flipfrid_scene_entrypoint_on_enter(FlipFridState* context) {
|
||||
@@ -36,6 +53,7 @@ void flipfrid_scene_entrypoint_on_enter(FlipFridState* context) {
|
||||
context->payload[2] = 0x00;
|
||||
context->payload[3] = 0x00;
|
||||
context->payload[4] = 0x00;
|
||||
context->payload[5] = 0x00;
|
||||
|
||||
context->menu_index = 0;
|
||||
for(uint32_t i = 0; i < 4; i++) {
|
||||
@@ -46,6 +64,14 @@ void flipfrid_scene_entrypoint_on_enter(FlipFridState* context) {
|
||||
string_set(menu_items[1], "BF Customer ID");
|
||||
string_set(menu_items[2], "Load File");
|
||||
string_set(menu_items[3], "Load uids from file");
|
||||
|
||||
context->menu_proto_index = 0;
|
||||
for(uint32_t i = 0; i < 2; i++) {
|
||||
string_init(menu_proto_items[i]);
|
||||
}
|
||||
|
||||
string_set(menu_proto_items[0], "EM4100");
|
||||
string_set(menu_proto_items[1], "HIDProx");
|
||||
}
|
||||
|
||||
void flipfrid_scene_entrypoint_on_exit(FlipFridState* context) {
|
||||
@@ -53,6 +79,10 @@ void flipfrid_scene_entrypoint_on_exit(FlipFridState* context) {
|
||||
for(uint32_t i = 0; i < 4; i++) {
|
||||
string_clear(menu_items[i]);
|
||||
}
|
||||
|
||||
for(uint32_t i = 0; i < 2; i++) {
|
||||
string_clear(menu_proto_items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void flipfrid_scene_entrypoint_on_tick(FlipFridState* context) {
|
||||
@@ -74,10 +104,18 @@ void flipfrid_scene_entrypoint_on_event(FlipFridEvent event, FlipFridState* cont
|
||||
}
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
if(context->menu_proto_index > EM4100) {
|
||||
context->menu_proto_index--;
|
||||
}
|
||||
break;
|
||||
case InputKeyRight:
|
||||
if(context->menu_proto_index < HIDProx) {
|
||||
context->menu_proto_index++;
|
||||
}
|
||||
break;
|
||||
case InputKeyOk:
|
||||
flipfrid_scene_entrypoint_menu_callback(context, context->menu_index);
|
||||
flipfrid_scene_entrypoint_menu_callback(
|
||||
context, context->menu_index, context->menu_proto_index);
|
||||
break;
|
||||
case InputKeyBack:
|
||||
context->is_running = false;
|
||||
@@ -91,10 +129,6 @@ void flipfrid_scene_entrypoint_on_draw(Canvas* canvas, FlipFridState* context) {
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Title
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(canvas, 64, 6, AlignCenter, AlignTop, "RFID Fuzzer");
|
||||
|
||||
if(context->menu_index > FlipFridAttackDefaultValues) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(
|
||||
@@ -120,4 +154,41 @@ void flipfrid_scene_entrypoint_on_draw(Canvas* canvas, FlipFridState* context) {
|
||||
AlignTop,
|
||||
string_get_cstr(menu_items[context->menu_index + 1]));
|
||||
}
|
||||
|
||||
if(context->menu_proto_index > EM4100) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
64,
|
||||
-12,
|
||||
AlignCenter,
|
||||
AlignTop,
|
||||
string_get_cstr(menu_proto_items[context->menu_proto_index - 1]));
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(canvas, 34, 4, AlignCenter, AlignTop, "<");
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
64,
|
||||
4,
|
||||
AlignCenter,
|
||||
AlignTop,
|
||||
string_get_cstr(menu_proto_items[context->menu_proto_index]));
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(canvas, 94, 4, AlignCenter, AlignTop, ">");
|
||||
|
||||
if(context->menu_proto_index < HIDProx) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(
|
||||
canvas,
|
||||
64,
|
||||
-12,
|
||||
AlignCenter,
|
||||
AlignTop,
|
||||
string_get_cstr(menu_proto_items[context->menu_proto_index + 1]));
|
||||
}
|
||||
}
|
||||
@@ -36,11 +36,21 @@ bool flipfrid_load(FlipFridState* context, const char* file_path) {
|
||||
break;
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "Key type: %s", string_get_cstr(temp_str));
|
||||
if(strcmp(string_get_cstr(temp_str), "EM4100") != 0) {
|
||||
FURI_LOG_E(TAG, "Unsupported Key type");
|
||||
string_reset(context->notification_msg);
|
||||
string_set_str(context->notification_msg, "Unsupported Key type");
|
||||
break;
|
||||
|
||||
if(context->proto == EM4100) {
|
||||
if(strcmp(string_get_cstr(temp_str), "EM4100") != 0) {
|
||||
FURI_LOG_E(TAG, "Unsupported Key type");
|
||||
string_reset(context->notification_msg);
|
||||
string_set_str(context->notification_msg, "Unsupported Key type");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if(strcmp(string_get_cstr(temp_str), "HIDProx") != 0) {
|
||||
FURI_LOG_E(TAG, "Unsupported Key type");
|
||||
string_reset(context->notification_msg);
|
||||
string_set_str(context->notification_msg, "Unsupported Key type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,15 +63,24 @@ bool flipfrid_load(FlipFridState* context, const char* file_path) {
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "Key: %s", string_get_cstr(context->data_str));
|
||||
|
||||
// Check data size
|
||||
if(string_size(context->data_str) != 14) {
|
||||
FURI_LOG_E(TAG, "Incorrect Key length");
|
||||
string_reset(context->notification_msg);
|
||||
string_set_str(context->notification_msg, "Incorrect Key length");
|
||||
break;
|
||||
if(context->proto == EM4100) {
|
||||
if(string_size(context->data_str) != 14) {
|
||||
FURI_LOG_E(TAG, "Incorrect Key length");
|
||||
string_reset(context->notification_msg);
|
||||
string_set_str(context->notification_msg, "Incorrect Key length");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if(string_size(context->data_str) != 17) {
|
||||
FURI_LOG_E(TAG, "Incorrect Key length");
|
||||
string_reset(context->notification_msg);
|
||||
string_set_str(context->notification_msg, "Incorrect Key length");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// String to uint8_t
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
for(uint8_t i = 0; i < 6; i++) {
|
||||
char temp_str2[3];
|
||||
temp_str2[0] = string_get_cstr(context->data_str)[i * 3];
|
||||
temp_str2[1] = string_get_cstr(context->data_str)[i * 3 + 1];
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
#include <gui/elements.h>
|
||||
|
||||
uint8_t counter = 0;
|
||||
#define TIME_BETWEEN_CARDS 5
|
||||
uint8_t id_list[16][5] = {
|
||||
#define TIME_BETWEEN_CARDS 6
|
||||
uint8_t id_list[17][5] = {
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00}, // Null bytes
|
||||
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // Only FF
|
||||
{0x11, 0x11, 0x11, 0x11, 0x11}, // Only 11
|
||||
@@ -16,17 +16,39 @@ uint8_t id_list[16][5] = {
|
||||
{0x88, 0x88, 0x88, 0x88, 0x88}, // Only 88
|
||||
{0x99, 0x99, 0x99, 0x99, 0x99}, // Only 99
|
||||
{0x12, 0x34, 0x56, 0x78, 0x9A}, // Incremental UID
|
||||
{0x9A, 0x78, 0x56, 0x34, 0x12}, // Decremental UID
|
||||
{0x04, 0xd0, 0x9b, 0x0d, 0x6a}, // From arha
|
||||
{0x34, 0x00, 0x29, 0x3d, 0x9e}, // From arha
|
||||
{0x04, 0xdf, 0x00, 0x00, 0x01}, // From arha
|
||||
{0xCA, 0xCA, 0xCA, 0xCA, 0xCA}, // From arha
|
||||
};
|
||||
|
||||
uint8_t id_list_hid[14][6] = {
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // Null bytes
|
||||
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // Only FF
|
||||
{0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, // Only 11
|
||||
{0x22, 0x22, 0x22, 0x22, 0x22, 0x22}, // Only 22
|
||||
{0x33, 0x33, 0x33, 0x33, 0x33, 0x33}, // Only 33
|
||||
{0x44, 0x44, 0x44, 0x44, 0x44, 0x44}, // Only 44
|
||||
{0x55, 0x55, 0x55, 0x55, 0x55, 0x55}, // Only 55
|
||||
{0x66, 0x66, 0x66, 0x66, 0x66, 0x66}, // Only 66
|
||||
{0x77, 0x77, 0x77, 0x77, 0x77, 0x77}, // Only 77
|
||||
{0x88, 0x88, 0x88, 0x88, 0x88, 0x88}, // Only 88
|
||||
{0x99, 0x99, 0x99, 0x99, 0x99, 0x99}, // Only 99
|
||||
{0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC}, // Incremental UID
|
||||
{0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12}, // Decremental UID
|
||||
{0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA}, // From arha
|
||||
};
|
||||
|
||||
void flipfrid_scene_run_attack_on_enter(FlipFridState* context) {
|
||||
context->attack_step = 0;
|
||||
context->dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
|
||||
context->worker = lfrfid_worker_alloc(context->dict);
|
||||
context->protocol = protocol_dict_get_protocol_by_name(context->dict, "EM4100");
|
||||
if(context->proto == HIDProx) {
|
||||
context->protocol = protocol_dict_get_protocol_by_name(context->dict, "HIDProx");
|
||||
} else {
|
||||
context->protocol = protocol_dict_get_protocol_by_name(context->dict, "EM4100");
|
||||
}
|
||||
}
|
||||
|
||||
void flipfrid_scene_run_attack_on_exit(FlipFridState* context) {
|
||||
@@ -40,7 +62,7 @@ void flipfrid_scene_run_attack_on_exit(FlipFridState* context) {
|
||||
void flipfrid_scene_run_attack_on_tick(FlipFridState* context) {
|
||||
if(context->is_attacking) {
|
||||
if(1 == counter) {
|
||||
protocol_dict_set_data(context->dict, context->protocol, context->payload, 5);
|
||||
protocol_dict_set_data(context->dict, context->protocol, context->payload, 6);
|
||||
lfrfid_worker_free(context->worker);
|
||||
context->worker = lfrfid_worker_alloc(context->dict);
|
||||
lfrfid_worker_start_thread(context->worker);
|
||||
@@ -50,87 +72,180 @@ void flipfrid_scene_run_attack_on_tick(FlipFridState* context) {
|
||||
lfrfid_worker_stop_thread(context->worker);
|
||||
switch(context->attack) {
|
||||
case FlipFridAttackDefaultValues:
|
||||
context->payload[0] = id_list[context->attack_step][0];
|
||||
context->payload[1] = id_list[context->attack_step][1];
|
||||
context->payload[2] = id_list[context->attack_step][2];
|
||||
context->payload[3] = id_list[context->attack_step][3];
|
||||
context->payload[4] = id_list[context->attack_step][4];
|
||||
if(context->proto == EM4100) {
|
||||
context->payload[0] = id_list[context->attack_step][0];
|
||||
context->payload[1] = id_list[context->attack_step][1];
|
||||
context->payload[2] = id_list[context->attack_step][2];
|
||||
context->payload[3] = id_list[context->attack_step][3];
|
||||
context->payload[4] = id_list[context->attack_step][4];
|
||||
|
||||
if(context->attack_step == 15) {
|
||||
context->attack_step = 0;
|
||||
counter = 0;
|
||||
context->is_attacking = false;
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
notification_message(context->notify, &sequence_single_vibro);
|
||||
|
||||
} else {
|
||||
context->attack_step++;
|
||||
}
|
||||
break;
|
||||
|
||||
case FlipFridAttackBfCustomerId:
|
||||
context->payload[0] = context->attack_step;
|
||||
context->payload[1] = 0x00;
|
||||
context->payload[2] = 0x00;
|
||||
context->payload[3] = 0x00;
|
||||
context->payload[4] = 0x00;
|
||||
|
||||
if(context->attack_step == 255) {
|
||||
context->attack_step = 0;
|
||||
counter = 0;
|
||||
context->is_attacking = false;
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
notification_message(context->notify, &sequence_single_vibro);
|
||||
} else {
|
||||
context->attack_step++;
|
||||
}
|
||||
break;
|
||||
case FlipFridAttackLoadFile:
|
||||
context->payload[0] = context->data[0];
|
||||
context->payload[1] = context->data[1];
|
||||
context->payload[2] = context->data[2];
|
||||
context->payload[3] = context->data[3];
|
||||
context->payload[4] = context->data[4];
|
||||
|
||||
context->payload[context->key_index] = context->attack_step;
|
||||
|
||||
if(context->attack_step == 255) {
|
||||
context->attack_step = 0;
|
||||
counter = 0;
|
||||
context->is_attacking = false;
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
notification_message(context->notify, &sequence_single_vibro);
|
||||
if(context->attack_step == 15) {
|
||||
context->attack_step = 0;
|
||||
counter = 0;
|
||||
context->is_attacking = false;
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
notification_message(context->notify, &sequence_single_vibro);
|
||||
} else {
|
||||
context->attack_step++;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
context->attack_step++;
|
||||
context->payload[0] = id_list_hid[context->attack_step][0];
|
||||
context->payload[1] = id_list_hid[context->attack_step][1];
|
||||
context->payload[2] = id_list_hid[context->attack_step][2];
|
||||
context->payload[3] = id_list_hid[context->attack_step][3];
|
||||
context->payload[4] = id_list_hid[context->attack_step][4];
|
||||
context->payload[5] = id_list_hid[context->attack_step][5];
|
||||
|
||||
if(context->attack_step == 15) {
|
||||
context->attack_step = 0;
|
||||
counter = 0;
|
||||
context->is_attacking = false;
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
notification_message(context->notify, &sequence_single_vibro);
|
||||
|
||||
} else {
|
||||
context->attack_step++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case FlipFridAttackLoadFileCustomUids:
|
||||
while(true) {
|
||||
string_reset(context->data_str);
|
||||
if(!stream_read_line(context->uids_stream, context->data_str)) {
|
||||
|
||||
case FlipFridAttackBfCustomerId:
|
||||
if(context->proto == EM4100) {
|
||||
context->payload[0] = context->attack_step;
|
||||
context->payload[1] = 0x00;
|
||||
context->payload[2] = 0x00;
|
||||
context->payload[3] = 0x00;
|
||||
context->payload[4] = 0x00;
|
||||
|
||||
if(context->attack_step == 255) {
|
||||
context->attack_step = 0;
|
||||
counter = 0;
|
||||
context->is_attacking = false;
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
notification_message(context->notify, &sequence_single_vibro);
|
||||
} else {
|
||||
context->attack_step++;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
context->payload[0] = context->attack_step;
|
||||
context->payload[1] = 0x00;
|
||||
context->payload[2] = 0x00;
|
||||
context->payload[3] = 0x00;
|
||||
context->payload[4] = 0x00;
|
||||
context->payload[5] = 0x00;
|
||||
|
||||
if(context->attack_step == 255) {
|
||||
context->attack_step = 0;
|
||||
counter = 0;
|
||||
context->is_attacking = false;
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
notification_message(context->notify, &sequence_single_vibro);
|
||||
} else {
|
||||
context->attack_step++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case FlipFridAttackLoadFile:
|
||||
if(context->proto == EM4100) {
|
||||
context->payload[0] = context->data[0];
|
||||
context->payload[1] = context->data[1];
|
||||
context->payload[2] = context->data[2];
|
||||
context->payload[3] = context->data[3];
|
||||
context->payload[4] = context->data[4];
|
||||
|
||||
context->payload[context->key_index] = context->attack_step;
|
||||
|
||||
if(context->attack_step == 255) {
|
||||
context->attack_step = 0;
|
||||
counter = 0;
|
||||
context->is_attacking = false;
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
notification_message(context->notify, &sequence_single_vibro);
|
||||
break;
|
||||
};
|
||||
if(string_get_char(context->data_str, 0) == '#') continue;
|
||||
if(string_size(context->data_str) != 11) continue;
|
||||
} else {
|
||||
context->attack_step++;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
context->payload[0] = context->data[0];
|
||||
context->payload[1] = context->data[1];
|
||||
context->payload[2] = context->data[2];
|
||||
context->payload[3] = context->data[3];
|
||||
context->payload[4] = context->data[4];
|
||||
context->payload[5] = context->data[5];
|
||||
|
||||
context->payload[context->key_index] = context->attack_step;
|
||||
|
||||
if(context->attack_step == 255) {
|
||||
context->attack_step = 0;
|
||||
counter = 0;
|
||||
context->is_attacking = false;
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
notification_message(context->notify, &sequence_single_vibro);
|
||||
break;
|
||||
} else {
|
||||
context->attack_step++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
FURI_LOG_D(TAG, string_get_cstr(context->data_str));
|
||||
|
||||
// string is valid, parse it in context->payload
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
char temp_str[3];
|
||||
temp_str[0] = string_get_cstr(context->data_str)[i * 2];
|
||||
temp_str[1] = string_get_cstr(context->data_str)[i * 2 + 1];
|
||||
temp_str[2] = '\0';
|
||||
context->payload[i] = (uint8_t)strtol(temp_str, NULL, 16);
|
||||
case FlipFridAttackLoadFileCustomUids:
|
||||
if(context->proto == EM4100) {
|
||||
while(true) {
|
||||
string_reset(context->data_str);
|
||||
if(!stream_read_line(context->uids_stream, context->data_str)) {
|
||||
context->attack_step = 0;
|
||||
counter = 0;
|
||||
context->is_attacking = false;
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
notification_message(context->notify, &sequence_single_vibro);
|
||||
break;
|
||||
};
|
||||
if(string_get_char(context->data_str, 0) == '#') continue;
|
||||
if(string_size(context->data_str) != 11) continue;
|
||||
break;
|
||||
}
|
||||
FURI_LOG_D(TAG, string_get_cstr(context->data_str));
|
||||
|
||||
// string is valid, parse it in context->payload
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
char temp_str[3];
|
||||
temp_str[0] = string_get_cstr(context->data_str)[i * 2];
|
||||
temp_str[1] = string_get_cstr(context->data_str)[i * 2 + 1];
|
||||
temp_str[2] = '\0';
|
||||
context->payload[i] = (uint8_t)strtol(temp_str, NULL, 16);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
while(true) {
|
||||
string_reset(context->data_str);
|
||||
if(!stream_read_line(context->uids_stream, context->data_str)) {
|
||||
context->attack_step = 0;
|
||||
counter = 0;
|
||||
context->is_attacking = false;
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
notification_message(context->notify, &sequence_single_vibro);
|
||||
break;
|
||||
};
|
||||
if(string_get_char(context->data_str, 0) == '#') continue;
|
||||
if(string_size(context->data_str) != 13) continue;
|
||||
break;
|
||||
}
|
||||
FURI_LOG_D(TAG, string_get_cstr(context->data_str));
|
||||
|
||||
// string is valid, parse it in context->payload
|
||||
for(uint8_t i = 0; i < 6; i++) {
|
||||
char temp_str[3];
|
||||
temp_str[0] = string_get_cstr(context->data_str)[i * 2];
|
||||
temp_str[1] = string_get_cstr(context->data_str)[i * 2 + 1];
|
||||
temp_str[2] = '\0';
|
||||
context->payload[i] = (uint8_t)strtol(temp_str, NULL, 16);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,16 +305,30 @@ void flipfrid_scene_run_attack_on_draw(Canvas* canvas, FlipFridState* context) {
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, 8, AlignCenter, AlignTop, string_get_cstr(context->attack_name));
|
||||
|
||||
char uid[16];
|
||||
snprintf(
|
||||
uid,
|
||||
sizeof(uid),
|
||||
"%02X:%02X:%02X:%02X:%02X",
|
||||
context->payload[0],
|
||||
context->payload[1],
|
||||
context->payload[2],
|
||||
context->payload[3],
|
||||
context->payload[4]);
|
||||
char uid[18];
|
||||
if(context->proto == HIDProx) {
|
||||
snprintf(
|
||||
uid,
|
||||
sizeof(uid),
|
||||
"%02X:%02X:%02X:%02X:%02X:%02X",
|
||||
context->payload[0],
|
||||
context->payload[1],
|
||||
context->payload[2],
|
||||
context->payload[3],
|
||||
context->payload[4],
|
||||
context->payload[5]);
|
||||
} else {
|
||||
snprintf(
|
||||
uid,
|
||||
sizeof(uid),
|
||||
"%02X:%02X:%02X:%02X:%02X",
|
||||
context->payload[0],
|
||||
context->payload[1],
|
||||
context->payload[2],
|
||||
context->payload[3],
|
||||
context->payload[4]);
|
||||
}
|
||||
|
||||
canvas_draw_str_aligned(canvas, 64, 24, AlignCenter, AlignTop, uid);
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
@@ -47,7 +47,8 @@ static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
canvas_draw_frame(canvas, 0, 0, 128, 64);
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
if(!plugin_state->addr_err && !plugin_state->ducky_err && !plugin_state->is_thread_running) {
|
||||
if(!plugin_state->addr_err && !plugin_state->ducky_err && !plugin_state->is_thread_running &&
|
||||
!plugin_state->is_ducky_running) {
|
||||
snprintf(target_text, sizeof(target_text), target_fmt_text, target_address_str);
|
||||
canvas_draw_str_aligned(canvas, 7, 10, AlignLeft, AlignBottom, target_text);
|
||||
canvas_draw_str_aligned(canvas, 22, 20, AlignLeft, AlignBottom, "<- select address ->");
|
||||
@@ -66,7 +67,10 @@ static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
canvas, 3, 10, AlignLeft, AlignBottom, "Error: No mousejacker folder");
|
||||
canvas_draw_str_aligned(canvas, 3, 20, AlignLeft, AlignBottom, "or duckyscript file");
|
||||
canvas_draw_str_aligned(canvas, 3, 30, AlignLeft, AlignBottom, "loading error");
|
||||
} else if(plugin_state->is_thread_running) {
|
||||
} else if(plugin_state->is_thread_running && !plugin_state->is_ducky_running) {
|
||||
canvas_draw_str_aligned(canvas, 3, 10, AlignLeft, AlignBottom, "Loading...");
|
||||
canvas_draw_str_aligned(canvas, 3, 20, AlignLeft, AlignBottom, "Please wait!");
|
||||
} else if(plugin_state->is_thread_running && plugin_state->is_ducky_running) {
|
||||
canvas_draw_str_aligned(canvas, 3, 10, AlignLeft, AlignBottom, "Running duckyscript");
|
||||
canvas_draw_str_aligned(canvas, 3, 20, AlignLeft, AlignBottom, "Please wait!");
|
||||
canvas_draw_str_aligned(
|
||||
@@ -97,7 +101,7 @@ static void hexlify(uint8_t* in, uint8_t size, char* out) {
|
||||
snprintf(out + strlen(out), sizeof(out + strlen(out)), "%02X", in[i]);
|
||||
}
|
||||
|
||||
static bool open_ducky_script(Stream* stream) {
|
||||
static bool open_ducky_script(Stream* stream, PluginState* plugin_state) {
|
||||
DialogsApp* dialogs = furi_record_open("dialogs");
|
||||
bool result = false;
|
||||
string_t path;
|
||||
@@ -120,6 +124,9 @@ static bool open_ducky_script(Stream* stream) {
|
||||
}
|
||||
}
|
||||
string_clear(path);
|
||||
|
||||
plugin_state->is_ducky_running = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -160,10 +167,11 @@ static bool process_ducky_file(
|
||||
uint8_t* file_buf;
|
||||
bool loaded = false;
|
||||
FURI_LOG_D(TAG, "opening ducky script");
|
||||
if(open_ducky_script(file_stream)) {
|
||||
if(open_ducky_script(file_stream, plugin_state)) {
|
||||
file_size = stream_size(file_stream);
|
||||
if(file_size == (size_t)0) {
|
||||
FURI_LOG_D(TAG, "load failed. file_size: %d", file_size);
|
||||
plugin_state->is_ducky_running = false;
|
||||
return loaded;
|
||||
}
|
||||
file_buf = malloc(file_size);
|
||||
@@ -180,6 +188,7 @@ static bool process_ducky_file(
|
||||
}
|
||||
free(file_buf);
|
||||
}
|
||||
plugin_state->is_ducky_running = false;
|
||||
return loaded;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ typedef struct {
|
||||
bool ducky_err;
|
||||
bool addr_err;
|
||||
bool is_thread_running;
|
||||
bool is_ducky_running;
|
||||
bool close_thread_please;
|
||||
Storage* storage;
|
||||
FuriThread* mjthread;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
App(
|
||||
appid="picopass",
|
||||
name="PicoPass Reader",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="picopass_app",
|
||||
requires=[
|
||||
"storage",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
App(
|
||||
appid="spectrum_analyzer",
|
||||
name="Spectrum Analyzer",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="spectrum_analyzer_app",
|
||||
cdefines=["APP_SPECTRUM_ANALYZER"],
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
order=12,
|
||||
fap_icon="spectrum_10px.png",
|
||||
fap_category="Tools",
|
||||
)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 135 B |
@@ -0,0 +1,513 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
#include "spectrum_analyzer.h"
|
||||
|
||||
#include <lib/drivers/cc1101_regs.h>
|
||||
#include "spectrum_analyzer_worker.h"
|
||||
|
||||
typedef struct {
|
||||
uint16_t center_freq;
|
||||
uint8_t width;
|
||||
uint8_t band;
|
||||
uint8_t vscroll;
|
||||
|
||||
uint32_t channel0_frequency;
|
||||
uint32_t spacing;
|
||||
|
||||
bool mode_change;
|
||||
|
||||
float max_rssi;
|
||||
uint8_t max_rssi_dec;
|
||||
uint8_t max_rssi_channel;
|
||||
uint8_t channel_ss[NUM_CHANNELS];
|
||||
} SpectrumAnalyzerModel;
|
||||
|
||||
typedef struct {
|
||||
SpectrumAnalyzerModel* model;
|
||||
FuriMutex* model_mutex;
|
||||
|
||||
FuriMessageQueue* event_queue;
|
||||
|
||||
ViewPort* view_port;
|
||||
Gui* gui;
|
||||
|
||||
SpectrumAnalyzerWorker* worker;
|
||||
} SpectrumAnalyzer;
|
||||
|
||||
void spectrum_analyzer_draw_scale(Canvas* canvas, const SpectrumAnalyzerModel* model) {
|
||||
// Draw line
|
||||
canvas_draw_line(
|
||||
canvas, FREQ_START_X, FREQ_BOTTOM_Y, FREQ_START_X + FREQ_LENGTH_X, FREQ_BOTTOM_Y);
|
||||
// Draw minor scale
|
||||
for(int i = FREQ_START_X; i < FREQ_START_X + FREQ_LENGTH_X; i += 5) {
|
||||
canvas_draw_line(canvas, i, FREQ_BOTTOM_Y, i, FREQ_BOTTOM_Y + 2);
|
||||
}
|
||||
// Draw major scale
|
||||
for(int i = FREQ_START_X; i < FREQ_START_X + FREQ_LENGTH_X; i += 25) {
|
||||
canvas_draw_line(canvas, i, FREQ_BOTTOM_Y, i, FREQ_BOTTOM_Y + 4);
|
||||
}
|
||||
|
||||
// Draw scale tags
|
||||
uint16_t tag_left;
|
||||
uint16_t tag_center;
|
||||
uint16_t tag_right;
|
||||
char temp_str[18];
|
||||
|
||||
tag_center = model->center_freq;
|
||||
|
||||
switch(model->width) {
|
||||
case NARROW:
|
||||
tag_left = model->center_freq - 2;
|
||||
tag_right = model->center_freq + 2;
|
||||
break;
|
||||
case ULTRANARROW:
|
||||
tag_left = model->center_freq - 1;
|
||||
tag_right = model->center_freq + 1;
|
||||
break;
|
||||
case ULTRAWIDE:
|
||||
tag_left = model->center_freq - 40;
|
||||
tag_right = model->center_freq + 40;
|
||||
break;
|
||||
default:
|
||||
tag_left = model->center_freq - 10;
|
||||
tag_right = model->center_freq + 10;
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
snprintf(temp_str, 18, "%u", tag_left);
|
||||
canvas_draw_str_aligned(canvas, FREQ_START_X, 63, AlignCenter, AlignBottom, temp_str);
|
||||
snprintf(temp_str, 18, "%u", tag_center);
|
||||
canvas_draw_str_aligned(canvas, 128 / 2, 63, AlignCenter, AlignBottom, temp_str);
|
||||
snprintf(temp_str, 18, "%u", tag_right);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, FREQ_START_X + FREQ_LENGTH_X - 1, 63, AlignCenter, AlignBottom, temp_str);
|
||||
}
|
||||
|
||||
static void spectrum_analyzer_render_callback(Canvas* const canvas, void* ctx) {
|
||||
SpectrumAnalyzer* spectrum_analyzer = ctx;
|
||||
//furi_check(furi_mutex_acquire(spectrum_analyzer->model_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
SpectrumAnalyzerModel* model = spectrum_analyzer->model;
|
||||
|
||||
spectrum_analyzer_draw_scale(canvas, model);
|
||||
|
||||
for(uint8_t column = 0; column < 128; column++) {
|
||||
uint8_t ss = model->channel_ss[column + 2];
|
||||
// Compress height to max of 64 values (255>>2)
|
||||
uint8_t s = MAX((ss - model->vscroll) >> 2, 0);
|
||||
uint8_t y = FREQ_BOTTOM_Y - s; // bar height
|
||||
|
||||
// Draw each bar
|
||||
canvas_draw_line(canvas, column, FREQ_BOTTOM_Y, column, y);
|
||||
}
|
||||
|
||||
if(model->mode_change) {
|
||||
char temp_mode_str[12];
|
||||
switch(model->width) {
|
||||
case NARROW:
|
||||
strncpy(temp_mode_str, "NARROW", 12);
|
||||
break;
|
||||
case ULTRANARROW:
|
||||
strncpy(temp_mode_str, "ULTRANARROW", 12);
|
||||
break;
|
||||
case ULTRAWIDE:
|
||||
strncpy(temp_mode_str, "ULTRAWIDE", 12);
|
||||
break;
|
||||
default:
|
||||
strncpy(temp_mode_str, "WIDE", 12);
|
||||
break;
|
||||
}
|
||||
|
||||
// Current mode label
|
||||
char tmp_str[21];
|
||||
snprintf(tmp_str, 21, "Mode: %s", temp_mode_str);
|
||||
canvas_draw_str_aligned(canvas, 127, 4, AlignRight, AlignTop, tmp_str);
|
||||
}
|
||||
// Draw cross and label
|
||||
if(model->max_rssi > PEAK_THRESHOLD) {
|
||||
// Compress height to max of 64 values (255>>2)
|
||||
uint8_t max_y = MAX((model->max_rssi_dec - model->vscroll) >> 2, 0);
|
||||
max_y = (FREQ_BOTTOM_Y - max_y);
|
||||
|
||||
// Cross
|
||||
int16_t x1, x2, y1, y2;
|
||||
x1 = model->max_rssi_channel - 2 - 2;
|
||||
if(x1 < 0) x1 = 0;
|
||||
y1 = max_y - 2;
|
||||
if(y1 < 0) y1 = 0;
|
||||
x2 = model->max_rssi_channel - 2 + 2;
|
||||
if(x2 > 127) x2 = 127;
|
||||
y2 = max_y + 2;
|
||||
if(y2 > 63) y2 = 63; // SHOULD NOT HAPPEN CHECK!
|
||||
canvas_draw_line(canvas, x1, y1, x2, y2);
|
||||
|
||||
x1 = model->max_rssi_channel - 2 + 2;
|
||||
if(x1 > 127) x1 = 127;
|
||||
y1 = max_y - 2;
|
||||
if(y1 < 0) y1 = 0;
|
||||
x2 = model->max_rssi_channel - 2 - 2;
|
||||
if(x2 < 0) x2 = 0;
|
||||
y2 = max_y + 2;
|
||||
if(y2 > 63) y2 = 63; // SHOULD NOT HAPPEN CHECK!
|
||||
canvas_draw_line(canvas, (uint8_t)x1, (uint8_t)y1, (uint8_t)x2, (uint8_t)y2);
|
||||
|
||||
// Label
|
||||
char temp_str[36];
|
||||
snprintf(
|
||||
temp_str,
|
||||
36,
|
||||
"Peak: %3.2f Mhz %3.1f dbm",
|
||||
((double)(model->channel0_frequency + (model->max_rssi_channel * model->spacing)) /
|
||||
1000000),
|
||||
(double)model->max_rssi);
|
||||
canvas_draw_str_aligned(canvas, 127, 0, AlignRight, AlignTop, temp_str);
|
||||
}
|
||||
|
||||
//furi_mutex_release(spectrum_analyzer->model_mutex);
|
||||
|
||||
// FURI_LOG_D("Spectrum", "model->vscroll %u", model->vscroll);
|
||||
}
|
||||
|
||||
static void spectrum_analyzer_input_callback(InputEvent* input_event, void* ctx) {
|
||||
SpectrumAnalyzer* spectrum_analyzer = ctx;
|
||||
// Only handle short presses
|
||||
if(input_event->type == InputTypeShort) {
|
||||
furi_message_queue_put(spectrum_analyzer->event_queue, input_event, FuriWaitForever);
|
||||
}
|
||||
}
|
||||
|
||||
static void spectrum_analyzer_worker_callback(
|
||||
void* channel_ss,
|
||||
float max_rssi,
|
||||
uint8_t max_rssi_dec,
|
||||
uint8_t max_rssi_channel,
|
||||
void* context) {
|
||||
SpectrumAnalyzer* spectrum_analyzer = context;
|
||||
furi_check(
|
||||
furi_mutex_acquire(spectrum_analyzer->model_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
SpectrumAnalyzerModel* model = (SpectrumAnalyzerModel*)spectrum_analyzer->model;
|
||||
memcpy(model->channel_ss, (uint8_t*)channel_ss, sizeof(uint8_t) * NUM_CHANNELS);
|
||||
model->max_rssi = max_rssi;
|
||||
model->max_rssi_dec = max_rssi_dec;
|
||||
model->max_rssi_channel = max_rssi_channel;
|
||||
|
||||
furi_mutex_release(spectrum_analyzer->model_mutex);
|
||||
view_port_update(spectrum_analyzer->view_port);
|
||||
}
|
||||
|
||||
void spectrum_analyzer_calculate_frequencies(SpectrumAnalyzerModel* model) {
|
||||
// REDO ALL THIS. CALCULATE ONLY WITH SPACING!
|
||||
|
||||
uint8_t new_band;
|
||||
uint32_t min_hz;
|
||||
uint32_t max_hz;
|
||||
uint8_t margin;
|
||||
uint8_t step;
|
||||
uint16_t upper_limit;
|
||||
uint16_t lower_limit;
|
||||
uint16_t next_up;
|
||||
uint16_t next_down;
|
||||
uint8_t next_band_up;
|
||||
uint8_t next_band_down;
|
||||
|
||||
switch(model->width) {
|
||||
case NARROW:
|
||||
margin = NARROW_MARGIN;
|
||||
step = NARROW_STEP;
|
||||
model->spacing = NARROW_SPACING;
|
||||
break;
|
||||
case ULTRANARROW:
|
||||
margin = ULTRANARROW_MARGIN;
|
||||
step = ULTRANARROW_STEP;
|
||||
model->spacing = ULTRANARROW_SPACING;
|
||||
break;
|
||||
case ULTRAWIDE:
|
||||
margin = ULTRAWIDE_MARGIN;
|
||||
step = ULTRAWIDE_STEP;
|
||||
model->spacing = ULTRAWIDE_SPACING;
|
||||
/* nearest 20 MHz step */
|
||||
model->center_freq = ((model->center_freq + 10) / 20) * 20;
|
||||
break;
|
||||
default:
|
||||
margin = WIDE_MARGIN;
|
||||
step = WIDE_STEP;
|
||||
model->spacing = WIDE_SPACING;
|
||||
/* nearest 5 MHz step */
|
||||
model->center_freq = ((model->center_freq + 2) / 5) * 5;
|
||||
break;
|
||||
}
|
||||
|
||||
/* handle cases near edges of bands */
|
||||
if(model->center_freq > EDGE_900) {
|
||||
new_band = BAND_900;
|
||||
upper_limit = UPPER(MAX_900, margin, step);
|
||||
lower_limit = LOWER(MIN_900, margin, step);
|
||||
next_up = LOWER(MIN_300, margin, step);
|
||||
next_down = UPPER(MAX_400, margin, step);
|
||||
next_band_up = BAND_300;
|
||||
next_band_down = BAND_400;
|
||||
} else if(model->center_freq > EDGE_400) {
|
||||
new_band = BAND_400;
|
||||
upper_limit = UPPER(MAX_400, margin, step);
|
||||
lower_limit = LOWER(MIN_400, margin, step);
|
||||
next_up = LOWER(MIN_900, margin, step);
|
||||
next_down = UPPER(MAX_300, margin, step);
|
||||
next_band_up = BAND_900;
|
||||
next_band_down = BAND_300;
|
||||
} else {
|
||||
new_band = BAND_300;
|
||||
upper_limit = UPPER(MAX_300, margin, step);
|
||||
lower_limit = LOWER(MIN_300, margin, step);
|
||||
next_up = LOWER(MIN_400, margin, step);
|
||||
next_down = UPPER(MAX_900, margin, step);
|
||||
next_band_up = BAND_400;
|
||||
next_band_down = BAND_900;
|
||||
}
|
||||
|
||||
if(model->center_freq > upper_limit) {
|
||||
model->center_freq = upper_limit;
|
||||
if(new_band == model->band) {
|
||||
new_band = next_band_up;
|
||||
model->center_freq = next_up;
|
||||
}
|
||||
} else if(model->center_freq < lower_limit) {
|
||||
model->center_freq = lower_limit;
|
||||
if(new_band == model->band) {
|
||||
new_band = next_band_down;
|
||||
model->center_freq = next_down;
|
||||
}
|
||||
}
|
||||
|
||||
model->band = new_band;
|
||||
/* doing everything in Hz from here on */
|
||||
switch(model->band) {
|
||||
case BAND_400:
|
||||
min_hz = MIN_400 * 1000000;
|
||||
max_hz = MAX_400 * 1000000;
|
||||
break;
|
||||
case BAND_300:
|
||||
min_hz = MIN_300 * 1000000;
|
||||
max_hz = MAX_300 * 1000000;
|
||||
break;
|
||||
default:
|
||||
min_hz = MIN_900 * 1000000;
|
||||
max_hz = MAX_900 * 1000000;
|
||||
break;
|
||||
}
|
||||
|
||||
model->channel0_frequency =
|
||||
model->center_freq * 1000000 - (model->spacing * ((NUM_CHANNELS / 2) + 1));
|
||||
|
||||
// /* calibrate upper channels */
|
||||
// hz = model->center_freq * 1000000;
|
||||
// max_chan = NUM_CHANNELS / 2;
|
||||
// while (hz <= max_hz && max_chan < NUM_CHANNELS) {
|
||||
// instance->chan_table[max_chan].frequency = hz;
|
||||
// FURI_LOG_T("Spectrum", "calibrate_freq ch[%u]: %lu", max_chan, hz);
|
||||
// hz += model->spacing;
|
||||
// max_chan++;
|
||||
// }
|
||||
|
||||
// /* calibrate lower channels */
|
||||
// hz = instance->freq * 1000000 - model->spacing;
|
||||
// min_chan = NUM_CHANNELS / 2;
|
||||
// while (hz >= min_hz && min_chan > 0) {
|
||||
// min_chan--;
|
||||
// instance->chan_table[min_chan].frequency = hz;
|
||||
// FURI_LOG_T("Spectrum", "calibrate_freq ch[%u]: %lu", min_chan, hz);
|
||||
// hz -= model->spacing;
|
||||
// }
|
||||
|
||||
model->max_rssi = -200.0;
|
||||
model->max_rssi_dec = 0;
|
||||
|
||||
FURI_LOG_D("Spectrum", "setup_frequencies - max_hz: %u - min_hz: %u", max_hz, min_hz);
|
||||
FURI_LOG_D("Spectrum", "center_freq: %u", model->center_freq);
|
||||
FURI_LOG_D(
|
||||
"Spectrum",
|
||||
"ch[0]: %lu - ch[%u]: %lu",
|
||||
model->channel0_frequency,
|
||||
NUM_CHANNELS - 1,
|
||||
model->channel0_frequency + ((NUM_CHANNELS - 1) * model->spacing));
|
||||
}
|
||||
|
||||
SpectrumAnalyzer* spectrum_analyzer_alloc() {
|
||||
SpectrumAnalyzer* instance = malloc(sizeof(SpectrumAnalyzer));
|
||||
instance->model = malloc(sizeof(SpectrumAnalyzerModel));
|
||||
|
||||
SpectrumAnalyzerModel* model = instance->model;
|
||||
|
||||
for(uint8_t ch = 0; ch < NUM_CHANNELS - 1; ch++) {
|
||||
model->channel_ss[ch] = 0;
|
||||
}
|
||||
model->max_rssi_dec = 0;
|
||||
model->max_rssi_channel = 0;
|
||||
model->max_rssi = PEAK_THRESHOLD - 1; // Should initializar to < PEAK_THRESHOLD
|
||||
|
||||
model->center_freq = DEFAULT_FREQ;
|
||||
model->width = WIDE;
|
||||
model->band = BAND_400;
|
||||
|
||||
model->vscroll = DEFAULT_VSCROLL;
|
||||
|
||||
instance->model_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
instance->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||
|
||||
instance->worker = spectrum_analyzer_worker_alloc();
|
||||
|
||||
spectrum_analyzer_worker_set_callback(
|
||||
instance->worker, spectrum_analyzer_worker_callback, instance);
|
||||
|
||||
// Set system callbacks
|
||||
instance->view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(instance->view_port, spectrum_analyzer_render_callback, instance);
|
||||
view_port_input_callback_set(instance->view_port, spectrum_analyzer_input_callback, instance);
|
||||
|
||||
// Open GUI and register view_port
|
||||
instance->gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void spectrum_analyzer_free(SpectrumAnalyzer* instance) {
|
||||
// view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(instance->gui, instance->view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
view_port_free(instance->view_port);
|
||||
|
||||
spectrum_analyzer_worker_free(instance->worker);
|
||||
|
||||
furi_message_queue_free(instance->event_queue);
|
||||
|
||||
furi_mutex_free(instance->model_mutex);
|
||||
|
||||
free(instance->model);
|
||||
free(instance);
|
||||
|
||||
furi_hal_subghz_idle();
|
||||
furi_hal_subghz_sleep();
|
||||
}
|
||||
|
||||
int32_t spectrum_analyzer_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
SpectrumAnalyzer* spectrum_analyzer = spectrum_analyzer_alloc();
|
||||
InputEvent input;
|
||||
|
||||
furi_hal_power_suppress_charge_enter();
|
||||
|
||||
FURI_LOG_D("Spectrum", "Main Loop - Starting worker");
|
||||
furi_delay_ms(50);
|
||||
|
||||
spectrum_analyzer_worker_start(spectrum_analyzer->worker);
|
||||
|
||||
FURI_LOG_D("Spectrum", "Main Loop - Wait on queue");
|
||||
furi_delay_ms(50);
|
||||
|
||||
while(furi_message_queue_get(spectrum_analyzer->event_queue, &input, FuriWaitForever) ==
|
||||
FuriStatusOk) {
|
||||
furi_check(
|
||||
furi_mutex_acquire(spectrum_analyzer->model_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
FURI_LOG_D("Spectrum", "Main Loop - Input: %u", input.key);
|
||||
|
||||
SpectrumAnalyzerModel* model = spectrum_analyzer->model;
|
||||
|
||||
uint8_t vstep = VERTICAL_SHORT_STEP;
|
||||
uint8_t hstep;
|
||||
|
||||
bool exit_loop = false;
|
||||
|
||||
switch(model->width) {
|
||||
case NARROW:
|
||||
hstep = NARROW_STEP;
|
||||
break;
|
||||
case ULTRANARROW:
|
||||
hstep = ULTRANARROW_STEP;
|
||||
break;
|
||||
case ULTRAWIDE:
|
||||
hstep = ULTRAWIDE_STEP;
|
||||
break;
|
||||
default:
|
||||
hstep = WIDE_STEP;
|
||||
break;
|
||||
}
|
||||
|
||||
switch(input.key) {
|
||||
case InputKeyUp:
|
||||
model->vscroll = MAX(model->vscroll - vstep, MIN_VSCROLL);
|
||||
FURI_LOG_D("Spectrum", "Vscroll: %u", model->vscroll);
|
||||
break;
|
||||
case InputKeyDown:
|
||||
model->vscroll = MIN(model->vscroll + vstep, MAX_VSCROLL);
|
||||
FURI_LOG_D("Spectrum", "Vscroll: %u", model->vscroll);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
model->center_freq += hstep;
|
||||
FURI_LOG_D("Spectrum", "center_freq: %lu", model->center_freq);
|
||||
spectrum_analyzer_calculate_frequencies(model);
|
||||
spectrum_analyzer_worker_set_frequencies(
|
||||
spectrum_analyzer->worker, model->channel0_frequency, model->spacing, model->width);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
model->center_freq -= hstep;
|
||||
spectrum_analyzer_calculate_frequencies(model);
|
||||
spectrum_analyzer_worker_set_frequencies(
|
||||
spectrum_analyzer->worker, model->channel0_frequency, model->spacing, model->width);
|
||||
FURI_LOG_D("Spectrum", "center_freq: %lu", model->center_freq);
|
||||
break;
|
||||
case InputKeyOk: {
|
||||
switch(model->width) {
|
||||
case WIDE:
|
||||
model->width = NARROW;
|
||||
break;
|
||||
case NARROW:
|
||||
model->width = ULTRANARROW;
|
||||
break;
|
||||
case ULTRANARROW:
|
||||
model->width = ULTRAWIDE;
|
||||
break;
|
||||
case ULTRAWIDE:
|
||||
model->width = WIDE;
|
||||
break;
|
||||
default:
|
||||
model->width = WIDE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
model->mode_change = true;
|
||||
view_port_update(spectrum_analyzer->view_port);
|
||||
|
||||
furi_delay_ms(1000);
|
||||
|
||||
model->mode_change = false;
|
||||
spectrum_analyzer_calculate_frequencies(model);
|
||||
spectrum_analyzer_worker_set_frequencies(
|
||||
spectrum_analyzer->worker, model->channel0_frequency, model->spacing, model->width);
|
||||
FURI_LOG_D("Spectrum", "Width: %u", model->width);
|
||||
break;
|
||||
case InputKeyBack:
|
||||
exit_loop = true;
|
||||
break;
|
||||
}
|
||||
|
||||
furi_mutex_release(spectrum_analyzer->model_mutex);
|
||||
view_port_update(spectrum_analyzer->view_port);
|
||||
if(exit_loop == true) break;
|
||||
}
|
||||
|
||||
spectrum_analyzer_worker_stop(spectrum_analyzer->worker);
|
||||
|
||||
furi_hal_power_suppress_charge_exit();
|
||||
|
||||
spectrum_analyzer_free(spectrum_analyzer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
#define NUM_CHANNELS 132
|
||||
#define NUM_CHUNKS 6
|
||||
#define CHUNK_SIZE (NUM_CHANNELS / NUM_CHUNKS)
|
||||
|
||||
// Screen coordinates
|
||||
#define FREQ_BOTTOM_Y 50
|
||||
#define FREQ_START_X 14
|
||||
// How many channels displayed on the scale (On screen still 218)
|
||||
#define FREQ_LENGTH_X 102
|
||||
// dBm threshold to show peak value
|
||||
#define PEAK_THRESHOLD -85
|
||||
|
||||
/*
|
||||
* ultrawide mode: 80 MHz on screen, 784 kHz per channel
|
||||
* wide mode (default): 20 MHz on screen, 196 kHz per channel
|
||||
* narrow mode: 4 MHz on screen, 39 kHz per channel
|
||||
* ultranarrow mode: 2 MHz on screen, 19 kHz per channel
|
||||
*/
|
||||
#define WIDE 0
|
||||
#define NARROW 1
|
||||
#define ULTRAWIDE 2
|
||||
#define ULTRANARROW 3
|
||||
|
||||
/* channel spacing in Hz */
|
||||
#define WIDE_SPACING 196078
|
||||
#define NARROW_SPACING 39215
|
||||
#define ULTRAWIDE_SPACING 784313
|
||||
#define ULTRANARROW_SPACING 19607
|
||||
|
||||
/* vertical scrolling */
|
||||
#define VERTICAL_SHORT_STEP 16
|
||||
#define MAX_VSCROLL 120
|
||||
#define MIN_VSCROLL 0
|
||||
#define DEFAULT_VSCROLL 48
|
||||
|
||||
/* frequencies in MHz */
|
||||
#define DEFAULT_FREQ 440
|
||||
#define WIDE_STEP 5
|
||||
#define NARROW_STEP 1
|
||||
#define ULTRAWIDE_STEP 20
|
||||
#define ULTRANARROW_STEP 1
|
||||
#define WIDE_MARGIN 13
|
||||
#define NARROW_MARGIN 3
|
||||
#define ULTRAWIDE_MARGIN 42
|
||||
#define ULTRANARROW_MARGIN 1
|
||||
|
||||
/* frequency bands supported by device */
|
||||
#define BAND_300 0
|
||||
#define BAND_400 1
|
||||
#define BAND_900 2
|
||||
|
||||
/* band limits in MHz */
|
||||
#define MIN_300 281
|
||||
#define CEN_300 315
|
||||
#define MAX_300 361
|
||||
#define MIN_400 378
|
||||
#define CEN_400 435
|
||||
#define MAX_400 481
|
||||
#define MIN_900 749
|
||||
#define CEN_900 855
|
||||
#define MAX_900 962
|
||||
|
||||
/* band transition points in MHz */
|
||||
#define EDGE_400 369
|
||||
#define EDGE_900 615
|
||||
|
||||
/* VCO transition points in Hz */
|
||||
#define MID_300 318000000
|
||||
#define MID_400 424000000
|
||||
#define MID_900 848000000
|
||||
|
||||
#define UPPER(a, b, c) ((((a) - (b) + ((c) / 2)) / (c)) * (c))
|
||||
#define LOWER(a, b, c) ((((a) + (b)) / (c)) * (c))
|
||||
@@ -0,0 +1,198 @@
|
||||
#include "spectrum_analyzer.h"
|
||||
#include "spectrum_analyzer_worker.h"
|
||||
|
||||
#include <furi_hal.h>
|
||||
#include <furi.h>
|
||||
|
||||
#include <lib/drivers/cc1101_regs.h>
|
||||
|
||||
struct SpectrumAnalyzerWorker {
|
||||
FuriThread* thread;
|
||||
bool should_work;
|
||||
|
||||
SpectrumAnalyzerWorkerCallback callback;
|
||||
void* callback_context;
|
||||
|
||||
uint32_t channel0_frequency;
|
||||
uint32_t spacing;
|
||||
uint8_t width;
|
||||
float max_rssi;
|
||||
uint8_t max_rssi_dec;
|
||||
uint8_t max_rssi_channel;
|
||||
|
||||
uint8_t channel_ss[NUM_CHANNELS];
|
||||
};
|
||||
|
||||
/* set the channel bandwidth */
|
||||
void spectrum_analyzer_worker_set_filter(SpectrumAnalyzerWorker* instance) {
|
||||
uint8_t filter_config[2][2] = {
|
||||
{CC1101_MDMCFG4, 0},
|
||||
{0, 0},
|
||||
};
|
||||
|
||||
// FURI_LOG_D("SpectrumWorker", "spectrum_analyzer_worker_set_filter: width = %u", instance->width);
|
||||
|
||||
/* channel spacing should fit within 80% of channel filter bandwidth */
|
||||
switch(instance->width) {
|
||||
case NARROW:
|
||||
filter_config[0][1] = 0xFC; /* 39.2 kHz / .8 = 49 kHz --> 58 kHz */
|
||||
break;
|
||||
case ULTRAWIDE:
|
||||
filter_config[0][1] = 0x0C; /* 784 kHz / .8 = 980 kHz --> 812 kHz */
|
||||
break;
|
||||
default:
|
||||
filter_config[0][1] = 0x6C; /* 196 kHz / .8 = 245 kHz --> 270 kHz */
|
||||
break;
|
||||
}
|
||||
furi_hal_subghz_load_registers((uint8_t*)filter_config);
|
||||
}
|
||||
|
||||
static int32_t spectrum_analyzer_worker_thread(void* context) {
|
||||
furi_assert(context);
|
||||
SpectrumAnalyzerWorker* instance = context;
|
||||
|
||||
FURI_LOG_D("SpectrumWorker", "spectrum_analyzer_worker_thread: Start");
|
||||
|
||||
// Start CC1101
|
||||
furi_hal_subghz_reset();
|
||||
furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async);
|
||||
furi_hal_subghz_set_frequency(433920000);
|
||||
furi_hal_subghz_flush_rx();
|
||||
furi_hal_subghz_rx();
|
||||
|
||||
static const uint8_t radio_config[][2] = {
|
||||
{CC1101_FSCTRL1, 0x12},
|
||||
{CC1101_FSCTRL0, 0x00},
|
||||
|
||||
{CC1101_AGCCTRL2, 0xC0},
|
||||
|
||||
{CC1101_MDMCFG4, 0x6C},
|
||||
{CC1101_TEST2, 0x88},
|
||||
{CC1101_TEST1, 0x31},
|
||||
{CC1101_TEST0, 0x09},
|
||||
/* End */
|
||||
{0, 0},
|
||||
};
|
||||
|
||||
while(instance->should_work) {
|
||||
furi_delay_ms(50);
|
||||
|
||||
// FURI_LOG_T("SpectrumWorker", "spectrum_analyzer_worker_thread: Worker Loop");
|
||||
furi_hal_subghz_idle();
|
||||
furi_hal_subghz_load_registers((uint8_t*)radio_config);
|
||||
|
||||
// TODO: Check filter!
|
||||
// spectrum_analyzer_worker_set_filter(instance);
|
||||
|
||||
instance->max_rssi_dec = 0;
|
||||
|
||||
// Visit each channel non-consecutively
|
||||
for(uint8_t ch_offset = 0, chunk = 0; ch_offset < CHUNK_SIZE;
|
||||
++chunk >= NUM_CHUNKS && ++ch_offset && (chunk = 0)) {
|
||||
uint8_t ch = chunk * CHUNK_SIZE + ch_offset;
|
||||
furi_hal_subghz_set_frequency(instance->channel0_frequency + (ch * instance->spacing));
|
||||
|
||||
furi_hal_subghz_rx();
|
||||
furi_delay_ms(3);
|
||||
|
||||
// dec dBm
|
||||
//max_ss = 127 -> -10.5
|
||||
//max_ss = 0 -> -74.0
|
||||
//max_ss = 255 -> -74.5
|
||||
//max_ss = 128 -> -138.0
|
||||
instance->channel_ss[ch] = (furi_hal_subghz_get_rssi() + 138) * 2;
|
||||
|
||||
if(instance->channel_ss[ch] > instance->max_rssi_dec) {
|
||||
instance->max_rssi_dec = instance->channel_ss[ch];
|
||||
instance->max_rssi = (instance->channel_ss[ch] / 2) - 138;
|
||||
instance->max_rssi_channel = ch;
|
||||
}
|
||||
|
||||
furi_hal_subghz_idle();
|
||||
}
|
||||
|
||||
// FURI_LOG_T("SpectrumWorker", "channel_ss[0]: %u", instance->channel_ss[0]);
|
||||
|
||||
// Report results back to main thread
|
||||
if(instance->callback) {
|
||||
instance->callback(
|
||||
(void*)&(instance->channel_ss),
|
||||
instance->max_rssi,
|
||||
instance->max_rssi_dec,
|
||||
instance->max_rssi_channel,
|
||||
instance->callback_context);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SpectrumAnalyzerWorker* spectrum_analyzer_worker_alloc() {
|
||||
FURI_LOG_D("Spectrum", "spectrum_analyzer_worker_alloc: Start");
|
||||
|
||||
SpectrumAnalyzerWorker* instance = malloc(sizeof(SpectrumAnalyzerWorker));
|
||||
|
||||
instance->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(instance->thread, "SpectrumWorker");
|
||||
furi_thread_set_stack_size(instance->thread, 2048);
|
||||
furi_thread_set_context(instance->thread, instance);
|
||||
furi_thread_set_callback(instance->thread, spectrum_analyzer_worker_thread);
|
||||
|
||||
FURI_LOG_D("Spectrum", "spectrum_analyzer_worker_alloc: End");
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void spectrum_analyzer_worker_free(SpectrumAnalyzerWorker* instance) {
|
||||
FURI_LOG_D("Spectrum", "spectrum_analyzer_worker_free");
|
||||
furi_assert(instance);
|
||||
furi_thread_free(instance->thread);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void spectrum_analyzer_worker_set_callback(
|
||||
SpectrumAnalyzerWorker* instance,
|
||||
SpectrumAnalyzerWorkerCallback callback,
|
||||
void* context) {
|
||||
furi_assert(instance);
|
||||
instance->callback = callback;
|
||||
instance->callback_context = context;
|
||||
}
|
||||
|
||||
void spectrum_analyzer_worker_set_frequencies(
|
||||
SpectrumAnalyzerWorker* instance,
|
||||
uint32_t channel0_frequency,
|
||||
uint32_t spacing,
|
||||
uint8_t width) {
|
||||
furi_assert(instance);
|
||||
|
||||
FURI_LOG_D(
|
||||
"SpectrumWorker",
|
||||
"spectrum_analyzer_worker_set_frequencies - channel0_frequency= %u - spacing = %u - width = %u",
|
||||
channel0_frequency,
|
||||
spacing,
|
||||
width);
|
||||
|
||||
instance->channel0_frequency = channel0_frequency;
|
||||
instance->spacing = spacing;
|
||||
instance->width = width;
|
||||
}
|
||||
|
||||
void spectrum_analyzer_worker_start(SpectrumAnalyzerWorker* instance) {
|
||||
FURI_LOG_D("Spectrum", "spectrum_analyzer_worker_start");
|
||||
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->should_work == false);
|
||||
|
||||
instance->should_work = true;
|
||||
furi_thread_start(instance->thread);
|
||||
}
|
||||
|
||||
void spectrum_analyzer_worker_stop(SpectrumAnalyzerWorker* instance) {
|
||||
FURI_LOG_D("Spectrum", "spectrum_analyzer_worker_stop");
|
||||
furi_assert(instance);
|
||||
furi_assert(instance->should_work == true);
|
||||
|
||||
instance->should_work = false;
|
||||
furi_thread_join(instance->thread);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef void (*SpectrumAnalyzerWorkerCallback)(
|
||||
void* chan_table,
|
||||
float max_rssi,
|
||||
uint8_t max_rssi_dec,
|
||||
uint8_t max_rssi_channel,
|
||||
void* context);
|
||||
|
||||
typedef struct SpectrumAnalyzerWorker SpectrumAnalyzerWorker;
|
||||
|
||||
SpectrumAnalyzerWorker* spectrum_analyzer_worker_alloc();
|
||||
|
||||
void spectrum_analyzer_worker_free(SpectrumAnalyzerWorker* instance);
|
||||
|
||||
void spectrum_analyzer_worker_set_callback(
|
||||
SpectrumAnalyzerWorker* instance,
|
||||
SpectrumAnalyzerWorkerCallback callback,
|
||||
void* context);
|
||||
|
||||
void spectrum_analyzer_worker_set_filter(SpectrumAnalyzerWorker* instance);
|
||||
|
||||
void spectrum_analyzer_worker_set_frequencies(
|
||||
SpectrumAnalyzerWorker* instance,
|
||||
uint32_t channel0_frequency,
|
||||
uint32_t spacing,
|
||||
uint8_t width);
|
||||
|
||||
void spectrum_analyzer_worker_start(SpectrumAnalyzerWorker* instance);
|
||||
|
||||
void spectrum_analyzer_worker_stop(SpectrumAnalyzerWorker* instance);
|
||||
Reference in New Issue
Block a user