diff --git a/applications/applications.c b/applications/applications.c index b9e8bbd47..4deae885b 100644 --- a/applications/applications.c +++ b/applications/applications.c @@ -55,6 +55,7 @@ extern void crypto_on_system_start(); extern void ibutton_on_system_start(); extern void infrared_on_system_start(); extern void lfrfid_on_system_start(); +extern void music_player_on_system_start(); extern void nfc_on_system_start(); extern void storage_on_system_start(); extern void subghz_on_system_start(); @@ -280,6 +281,10 @@ const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = { infrared_on_system_start, #endif +#ifdef APP_MUSIC_PLAYER + music_player_on_system_start, +#endif + #ifdef APP_NFC nfc_on_system_start, #endif @@ -332,7 +337,7 @@ const FlipperApplication FLIPPER_PLUGINS[] = { #ifdef APP_MUSIC_PLAYER {.app = music_player_app, .name = "Music Player", - .stack_size = 1024, + .stack_size = 2048, .icon = &A_Plugins_14, .flags = FlipperApplicationFlagDefault}, #endif diff --git a/applications/archive/helpers/archive_browser.c b/applications/archive/helpers/archive_browser.c index b1e1e3efb..cbe57c06e 100644 --- a/applications/archive/helpers/archive_browser.c +++ b/applications/archive/helpers/archive_browser.c @@ -80,6 +80,7 @@ void archive_set_item_count(ArchiveBrowserView* browser, uint32_t count) { model->item_idx = CLAMP(model->item_idx, model->item_cnt - 1, 0); return false; }); + archive_update_offset(browser); } void archive_file_array_rm_selected(ArchiveBrowserView* browser) { @@ -396,8 +397,6 @@ void archive_enter_dir(ArchiveBrowserView* browser, string_t name) { return; } - archive_dir_count_items(browser, string_get_cstr(name)); - if(string_cmp(browser->path, name) != 0) { with_view_model( browser->view, (ArchiveBrowserViewModel * model) { @@ -410,6 +409,7 @@ void archive_enter_dir(ArchiveBrowserView* browser, string_t name) { string_set(browser->path, name); } + archive_dir_count_items(browser, string_get_cstr(name)); archive_switch_dir(browser, string_get_cstr(browser->path)); } diff --git a/applications/archive/scenes/archive_scene_browser.c b/applications/archive/scenes/archive_scene_browser.c index bcfd63ba5..0091bc993 100644 --- a/applications/archive/scenes/archive_scene_browser.c +++ b/applications/archive/scenes/archive_scene_browser.c @@ -95,6 +95,7 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { if(known_app) { archive_run_in_app(browser, selected); } + archive_show_file_menu(browser, false); consumed = true; break; case ArchiveBrowserEventFileMenuPin: diff --git a/applications/music_player/music_player.c b/applications/music_player/music_player.c index 4f8813d68..73f4bd2c8 100644 --- a/applications/music_player/music_player.c +++ b/applications/music_player/music_player.c @@ -2,132 +2,93 @@ #include #include -#include +#include +#include "music_player_worker.h" -// TODO float note freq -typedef enum { - // Delay - N = 0, - // Octave 4 - B4 = 494, - // Octave 5 - C5 = 523, - D5 = 587, - E5 = 659, - F_5 = 740, - G5 = 784, - A5 = 880, - B5 = 988, - // Octave 6 - C6 = 1046, - D6 = 1175, - E6 = 1319, -} MelodyEventNote; +#define TAG "MusicPlayer" -typedef enum { - L1 = 1, - L2 = 2, - L4 = 4, - L8 = 8, - L16 = 16, - L32 = 32, - L64 = 64, - L128 = 128, -} MelodyEventLength; +#define MUSIC_PLAYER_APP_PATH_FOLDER "/any/music_player" +#define MUSIC_PLAYER_APP_EXTENSION "*" + +#define MUSIC_PLAYER_SEMITONE_HISTORY_SIZE 4 typedef struct { - MelodyEventNote note; - MelodyEventLength length; -} MelodyEventRecord; + uint8_t semitone_history[MUSIC_PLAYER_SEMITONE_HISTORY_SIZE]; + uint8_t duration_history[MUSIC_PLAYER_SEMITONE_HISTORY_SIZE]; + + uint8_t volume; + uint8_t semitone; + uint8_t dots; + uint8_t duration; + float position; +} MusicPlayerModel; typedef struct { - const MelodyEventRecord* record; - int8_t loop_count; -} SongPattern; + MusicPlayerModel* model; + osMutexId_t* model_mutex; -const MelodyEventRecord melody_start[] = { - {E6, L8}, {N, L8}, {E5, L8}, {B5, L8}, {N, L4}, {E5, L8}, {A5, L8}, {G5, L8}, {A5, L8}, - {E5, L8}, {B5, L8}, {N, L8}, {G5, L8}, {A5, L8}, {D6, L8}, {N, L4}, {D5, L8}, {B5, L8}, - {N, L4}, {D5, L8}, {A5, L8}, {G5, L8}, {A5, L8}, {D5, L8}, {F_5, L8}, {N, L8}, {G5, L8}, - {A5, L8}, {D6, L8}, {N, L4}, {F_5, L8}, {B5, L8}, {N, L4}, {F_5, L8}, {D6, L8}, {C6, L8}, - {B5, L8}, {F_5, L8}, {A5, L8}, {N, L8}, {G5, L8}, {F_5, L8}, {E5, L8}, {N, L8}, {C5, L8}, - {E5, L8}, {B5, L8}, {B4, L8}, {C5, L8}, {D5, L8}, {D6, L8}, {C6, L8}, {B5, L8}, {F_5, L8}, - {A5, L8}, {N, L8}, {G5, L8}, {A5, L8}, {E6, L8}}; + osMessageQueueId_t input_queue; -const MelodyEventRecord melody_loop[] = { - {N, L4}, {E5, L8}, {B5, L8}, {N, L4}, {E5, L8}, {A5, L8}, {G5, L8}, {A5, L8}, {E5, L8}, - {B5, L8}, {N, L8}, {G5, L8}, {A5, L8}, {D6, L8}, {N, L4}, {D5, L8}, {B5, L8}, {N, L4}, - {D5, L8}, {A5, L8}, {G5, L8}, {A5, L8}, {D5, L8}, {F_5, L8}, {N, L8}, {G5, L8}, {A5, L8}, - {D6, L8}, {N, L4}, {F_5, L8}, {B5, L8}, {N, L4}, {F_5, L8}, {D6, L8}, {C6, L8}, {B5, L8}, - {F_5, L8}, {A5, L8}, {N, L8}, {G5, L8}, {F_5, L8}, {E5, L8}, {N, L8}, {C5, L8}, {E5, L8}, - {B5, L8}, {B4, L8}, {C5, L8}, {D5, L8}, {D6, L8}, {C6, L8}, {B5, L8}, {F_5, L8}, {A5, L8}, - {N, L8}, {G5, L8}, {A5, L8}, {E6, L8}}; + ViewPort* view_port; + Gui* gui; -const MelodyEventRecord melody_chords_1bar[] = { - {E6, L8}, {N, L8}, {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, - {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, - {B4, L128}, {E5, L128}, {B5, L8}, {N, L4}, {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, - {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, - {B4, L128}, {E5, L128}, {B4, L128}, {E5, L128}, {A5, L8}}; + MusicPlayerWorker* worker; +} MusicPlayer; -const SongPattern song[] = {{melody_start, 1}, {melody_loop, -1}}; +static const float MUSIC_PLAYER_VOLUMES[] = {0, .25, .5, .75, 1}; -typedef enum { - EventTypeTick, - EventTypeKey, - EventTypeNote, - // add your events type -} MusicDemoEventType; +static const char* semitone_to_note(int8_t semitone) { + switch(semitone) { + case 0: + return "C"; + case 1: + return "C#"; + case 2: + return "D"; + case 3: + return "D#"; + case 4: + return "E"; + case 5: + return "F"; + case 6: + return "F#"; + case 7: + return "G"; + case 8: + return "G#"; + case 9: + return "A"; + case 10: + return "A#"; + case 11: + return "B"; + default: + return "--"; + } +} -typedef struct { - union { - InputEvent input; - const MelodyEventRecord* note_record; - } value; - MusicDemoEventType type; -} MusicDemoEvent; - -typedef struct { - ValueMutex* state_mutex; - osMessageQueueId_t event_queue; - -} MusicDemoContext; - -#define note_stack_size 4 -typedef struct { - // describe state here - const MelodyEventRecord* note_record; - const MelodyEventRecord* note_stack[note_stack_size]; - uint8_t volume_id; - uint8_t volume_id_max; -} State; - -const float volumes[] = {0, .25, .5, .75, 1}; - -bool is_white_note(const MelodyEventRecord* note_record, uint8_t id) { - if(note_record == NULL) return false; - - switch(note_record->note) { - case C5: - case C6: +static bool is_white_note(uint8_t semitone, uint8_t id) { + switch(semitone) { + case 0: if(id == 0) return true; break; - case D5: - case D6: + case 2: if(id == 1) return true; break; - case E5: - case E6: + case 4: if(id == 2) return true; break; - case G5: + case 5: + if(id == 3) return true; + break; + case 7: if(id == 4) return true; break; - case A5: + case 9: if(id == 5) return true; break; - case B4: - case B5: + case 11: if(id == 6) return true; break; default: @@ -137,13 +98,23 @@ bool is_white_note(const MelodyEventRecord* note_record, uint8_t id) { return false; } -bool is_black_note(const MelodyEventRecord* note_record, uint8_t id) { - if(note_record == NULL) return false; - - switch(note_record->note) { - case F_5: +static bool is_black_note(uint8_t semitone, uint8_t id) { + switch(semitone) { + case 1: + if(id == 0) return true; + break; + case 3: + if(id == 1) return true; + break; + case 6: if(id == 3) return true; break; + case 8: + if(id == 4) return true; + break; + case 10: + if(id == 5) return true; + break; default: break; } @@ -151,87 +122,9 @@ bool is_black_note(const MelodyEventRecord* note_record, uint8_t id) { return false; } -const char* get_note_name(const MelodyEventRecord* note_record) { - if(note_record == NULL) return ""; - - switch(note_record->note) { - case N: - return "---"; - break; - case B4: - return "B4-"; - break; - case C5: - return "C5-"; - break; - case D5: - return "D5-"; - break; - case E5: - return "E5-"; - break; - case F_5: - return "F#5"; - break; - case G5: - return "G5-"; - break; - case A5: - return "A5-"; - break; - case B5: - return "B5-"; - break; - case C6: - return "C6-"; - break; - case D6: - return "D6-"; - break; - case E6: - return "E6-"; - break; - default: - return "UNK"; - break; - } -} -const char* get_note_len_name(const MelodyEventRecord* note_record) { - if(note_record == NULL) return ""; - - switch(note_record->length) { - case L1: - return "1-"; - break; - case L2: - return "2-"; - break; - case L4: - return "4-"; - break; - case L8: - return "8-"; - break; - case L16: - return "16"; - break; - case L32: - return "32"; - break; - case L64: - return "64"; - break; - case L128: - return "1+"; - break; - default: - return "--"; - break; - } -} - static void render_callback(Canvas* canvas, void* ctx) { - State* state = (State*)acquire_mutex((ValueMutex*)ctx, 25); + MusicPlayer* music_player = ctx; + furi_check(osMutexAcquire(music_player->model_mutex, osWaitForever) == osOK); canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); @@ -250,7 +143,7 @@ static void render_callback(Canvas* canvas, void* ctx) { // white keys for(size_t i = 0; i < 7; i++) { - if(is_white_note(state->note_record, i)) { + if(is_white_note(music_player->model->semitone, i)) { canvas_draw_box(canvas, x_pos + white_w * i, y_pos, white_w + 1, white_h); } else { canvas_draw_frame(canvas, x_pos + white_w * i, y_pos, white_w + 1, white_h); @@ -264,7 +157,7 @@ static void render_callback(Canvas* canvas, void* ctx) { canvas_draw_box( canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h); canvas_set_color(canvas, ColorBlack); - if(is_black_note(state->note_record, i)) { + if(is_black_note(music_player->model->semitone, i)) { canvas_draw_box( canvas, x_pos + white_w * i + black_x, y_pos + black_y, black_w + 1, black_h); } else { @@ -277,7 +170,8 @@ static void render_callback(Canvas* canvas, void* ctx) { // volume view_port x_pos = 124; y_pos = 0; - const uint8_t volume_h = (64 / (state->volume_id_max - 1)) * state->volume_id; + const uint8_t volume_h = + (64 / (COUNT_OF(MUSIC_PLAYER_VOLUMES) - 1)) * music_player->model->volume; canvas_draw_frame(canvas, x_pos, y_pos, 4, 64); canvas_draw_box(canvas, x_pos, y_pos + (64 - volume_h), 4, volume_h); @@ -289,171 +183,175 @@ static void render_callback(Canvas* canvas, void* ctx) { canvas_draw_frame(canvas, x_pos, y_pos, 49, 64); canvas_draw_line(canvas, x_pos + 28, 0, x_pos + 28, 64); - for(uint8_t i = 0; i < note_stack_size; i++) { + char duration_text[16]; + for(uint8_t i = 0; i < MUSIC_PLAYER_SEMITONE_HISTORY_SIZE; i++) { + if(music_player->model->duration_history[i] == 0xFF) { + snprintf(duration_text, 15, "--"); + } else { + snprintf(duration_text, 15, "%d", music_player->model->duration_history[i]); + } + if(i == 0) { canvas_draw_box(canvas, x_pos, y_pos + 48, 49, 16); canvas_set_color(canvas, ColorWhite); } else { canvas_set_color(canvas, ColorBlack); } - canvas_draw_str(canvas, x_pos + 4, 64 - 16 * i - 3, get_note_name(state->note_stack[i])); canvas_draw_str( - canvas, x_pos + 31, 64 - 16 * i - 3, get_note_len_name(state->note_stack[i])); + canvas, + x_pos + 4, + 64 - 16 * i - 3, + semitone_to_note(music_player->model->semitone_history[i])); + canvas_draw_str(canvas, x_pos + 31, 64 - 16 * i - 3, duration_text); canvas_draw_line(canvas, x_pos, 64 - 16 * i, x_pos + 48, 64 - 16 * i); } - release_mutex((ValueMutex*)ctx, state); + osMutexRelease(music_player->model_mutex); } static void input_callback(InputEvent* input_event, void* ctx) { - osMessageQueueId_t event_queue = ctx; - - MusicDemoEvent event; - event.type = EventTypeKey; - event.value.input = *input_event; - osMessageQueuePut(event_queue, &event, 0, 0); + MusicPlayer* music_player = ctx; + if(input_event->type == InputTypeShort) { + osMessageQueuePut(music_player->input_queue, input_event, 0, 0); + } } -void process_note( - const MelodyEventRecord* note_record, - float bar_length_ms, - MusicDemoContext* context) { - MusicDemoEvent event; - // send note event - event.type = EventTypeNote; - event.value.note_record = note_record; - osMessageQueuePut(context->event_queue, &event, 0, 0); +static void music_player_worker_callback( + uint8_t semitone, + uint8_t dots, + uint8_t duration, + float position, + void* context) { + MusicPlayer* music_player = context; + furi_check(osMutexAcquire(music_player->model_mutex, osWaitForever) == osOK); - // read volume - State* state = (State*)acquire_mutex(context->state_mutex, 25); - float volume = volumes[state->volume_id]; - release_mutex(context->state_mutex, state); - - // play note - float note_delay = bar_length_ms / (float)note_record->length; - if(note_record->note != N) { - furi_hal_speaker_start(note_record->note, volume); + for(size_t i = 0; i < MUSIC_PLAYER_SEMITONE_HISTORY_SIZE - 1; i++) { + size_t r = MUSIC_PLAYER_SEMITONE_HISTORY_SIZE - 1 - i; + music_player->model->duration_history[r] = music_player->model->duration_history[r - 1]; + music_player->model->semitone_history[r] = music_player->model->semitone_history[r - 1]; } - furi_hal_delay_ms(note_delay); - furi_hal_speaker_stop(); + + semitone = (semitone == 0xFF) ? 0xFF : semitone % 12; + + music_player->model->semitone = semitone; + music_player->model->dots = dots; + music_player->model->duration = duration; + music_player->model->position = position; + + music_player->model->semitone_history[0] = semitone; + music_player->model->duration_history[0] = duration; + + osMutexRelease(music_player->model_mutex); + view_port_update(music_player->view_port); } -void music_player_thread(void* p) { - MusicDemoContext* context = (MusicDemoContext*)p; +MusicPlayer* music_player_alloc() { + MusicPlayer* instance = malloc(sizeof(MusicPlayer)); - const float bpm = 130.0f; - // 4/4 - const float bar_length_ms = (60.0f * 1000.0f / bpm) * 4; - const uint16_t melody_start_events_count = sizeof(melody_start) / sizeof(melody_start[0]); - const uint16_t melody_loop_events_count = sizeof(melody_loop) / sizeof(melody_loop[0]); + instance->model = malloc(sizeof(MusicPlayerModel)); + memset(instance->model->duration_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE); + memset(instance->model->semitone_history, 0xff, MUSIC_PLAYER_SEMITONE_HISTORY_SIZE); + instance->model->volume = 3; - for(size_t i = 0; i < melody_start_events_count; i++) { - process_note(&melody_start[i], bar_length_ms, context); - } + instance->model_mutex = osMutexNew(NULL); - while(1) { - for(size_t i = 0; i < melody_loop_events_count; i++) { - process_note(&melody_loop[i], bar_length_ms, context); - } - } + instance->input_queue = osMessageQueueNew(8, sizeof(InputEvent), NULL); + + instance->worker = music_player_worker_alloc(); + music_player_worker_set_volume( + instance->worker, MUSIC_PLAYER_VOLUMES[instance->model->volume]); + music_player_worker_set_callback(instance->worker, music_player_worker_callback, instance); + + instance->view_port = view_port_alloc(); + view_port_draw_callback_set(instance->view_port, render_callback, instance); + view_port_input_callback_set(instance->view_port, input_callback, instance); + + // Open GUI and register view_port + instance->gui = furi_record_open("gui"); + gui_add_view_port(instance->gui, instance->view_port, GuiLayerFullscreen); + + return instance; +} + +void music_player_free(MusicPlayer* instance) { + gui_remove_view_port(instance->gui, instance->view_port); + furi_record_close("gui"); + view_port_free(instance->view_port); + + music_player_worker_free(instance->worker); + + osMessageQueueDelete(instance->input_queue); + + osMutexDelete(instance->model_mutex); + + free(instance->model); + free(instance); } int32_t music_player_app(void* p) { - osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(MusicDemoEvent), NULL); + MusicPlayer* music_player = music_player_alloc(); - State _state; - _state.note_record = NULL; - for(size_t i = 0; i < note_stack_size; i++) { - _state.note_stack[i] = NULL; - } - _state.volume_id = 1; - _state.volume_id_max = sizeof(volumes) / sizeof(volumes[0]); + string_t file_path; + string_init(file_path); - ValueMutex state_mutex; - if(!init_mutex(&state_mutex, &_state, sizeof(State))) { - printf("cannot create mutex\r\n"); - return 255; - } - - ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, render_callback, &state_mutex); - view_port_input_callback_set(view_port, input_callback, event_queue); - - // Open GUI and register view_port - Gui* gui = furi_record_open("gui"); - gui_add_view_port(gui, view_port, GuiLayerFullscreen); - - // start player thread - // TODO change to fuirac_start - osThreadAttr_t player_attr = {.name = "music_player_thread", .stack_size = 512}; - MusicDemoContext context = {.state_mutex = &state_mutex, .event_queue = event_queue}; - osThreadId_t player = osThreadNew(music_player_thread, &context, &player_attr); - - if(player == NULL) { - printf("cannot create player thread\r\n"); - return 255; - } - - MusicDemoEvent event; - while(1) { - osStatus_t event_status = osMessageQueueGet(event_queue, &event, NULL, 100); - - State* state = (State*)acquire_mutex_block(&state_mutex); - - if(event_status == osOK) { - if(event.type == EventTypeKey) { - // press events - if(event.value.input.type == InputTypeShort && - event.value.input.key == InputKeyBack) { - release_mutex(&state_mutex, state); - break; - } - - if(event.value.input.type == InputTypePress && - event.value.input.key == InputKeyUp) { - if(state->volume_id < state->volume_id_max - 1) state->volume_id++; - } - - if(event.value.input.type == InputTypePress && - event.value.input.key == InputKeyDown) { - if(state->volume_id > 0) state->volume_id--; - } - - if(event.value.input.type == InputTypePress && - event.value.input.key == InputKeyLeft) { - } - - if(event.value.input.type == InputTypePress && - event.value.input.key == InputKeyRight) { - } - - if(event.value.input.key == InputKeyOk) { - } - - } else if(event.type == EventTypeNote) { - state->note_record = event.value.note_record; - - for(size_t i = note_stack_size - 1; i > 0; i--) { - state->note_stack[i] = state->note_stack[i - 1]; - } - state->note_stack[0] = state->note_record; - } + do { + if(p) { + string_cat_str(file_path, p); } else { - // event timeout + char* file_name = malloc(256); + DialogsApp* dialogs = furi_record_open("dialogs"); + bool res = dialog_file_select_show( + dialogs, + MUSIC_PLAYER_APP_PATH_FOLDER, + MUSIC_PLAYER_APP_EXTENSION, + file_name, + 255, + NULL); + furi_record_close("dialogs"); + if(!res) { + FURI_LOG_E(TAG, "No file selected"); + break; + } + string_cat_str(file_path, MUSIC_PLAYER_APP_PATH_FOLDER); + string_cat_str(file_path, "/"); + string_cat_str(file_path, file_name); + free(file_name); } - view_port_update(view_port); - release_mutex(&state_mutex, state); - } + if(!music_player_worker_load(music_player->worker, string_get_cstr(file_path))) { + FURI_LOG_E(TAG, "Unable to load file"); + break; + } - osThreadTerminate(player); - furi_hal_speaker_stop(); - view_port_enabled_set(view_port, false); - gui_remove_view_port(gui, view_port); - furi_record_close("gui"); - view_port_free(view_port); - osMessageQueueDelete(event_queue); - delete_mutex(&state_mutex); + music_player_worker_start(music_player->worker); + + InputEvent input; + while(osMessageQueueGet(music_player->input_queue, &input, NULL, osWaitForever) == osOK) { + furi_check(osMutexAcquire(music_player->model_mutex, osWaitForever) == osOK); + + if(input.key == InputKeyBack) { + osMutexRelease(music_player->model_mutex); + break; + } else if(input.key == InputKeyUp) { + if(music_player->model->volume < COUNT_OF(MUSIC_PLAYER_VOLUMES) - 1) + music_player->model->volume++; + music_player_worker_set_volume( + music_player->worker, MUSIC_PLAYER_VOLUMES[music_player->model->volume]); + } else if(input.key == InputKeyDown) { + if(music_player->model->volume > 0) music_player->model->volume--; + music_player_worker_set_volume( + music_player->worker, MUSIC_PLAYER_VOLUMES[music_player->model->volume]); + } + + osMutexRelease(music_player->model_mutex); + view_port_update(music_player->view_port); + } + + music_player_worker_stop(music_player->worker); + } while(0); + + string_clear(file_path); + music_player_free(music_player); return 0; } diff --git a/applications/music_player/music_player_cli.c b/applications/music_player/music_player_cli.c new file mode 100644 index 000000000..3c76cb840 --- /dev/null +++ b/applications/music_player/music_player_cli.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include "music_player_worker.h" + +static void music_player_cli(Cli* cli, string_t args, void* context) { + MusicPlayerWorker* music_player_worker = music_player_worker_alloc(); + Storage* storage = furi_record_open("storage"); + + do { + if(storage_common_stat(storage, string_get_cstr(args), NULL) == FSE_OK) { + if(!music_player_worker_load(music_player_worker, string_get_cstr(args))) { + printf("Failed to open file %s\r\n", string_get_cstr(args)); + break; + } + } else { + if(!music_player_worker_load_rtttl_from_string( + music_player_worker, string_get_cstr(args))) { + printf("Argument is not a file or RTTTL\r\n"); + break; + } + } + + printf("Press CTRL+C to stop\r\n"); + music_player_worker_start(music_player_worker); + while(!cli_cmd_interrupt_received(cli)) { + osDelay(50); + } + music_player_worker_stop(music_player_worker); + } while(0); + + furi_record_close("storage"); + music_player_worker_free(music_player_worker); +} + +void music_player_on_system_start() { +#ifdef SRV_CLI + Cli* cli = furi_record_open("cli"); + + cli_add_command(cli, "music_player", CliCommandFlagDefault, music_player_cli, NULL); + + furi_record_close("cli"); +#else + UNUSED(music_player_cli); +#endif +} diff --git a/applications/music_player/music_player_worker.c b/applications/music_player/music_player_worker.c new file mode 100644 index 000000000..2c80e6f5e --- /dev/null +++ b/applications/music_player/music_player_worker.c @@ -0,0 +1,496 @@ +#include "music_player_worker.h" + +#include +#include + +#include +#include + +#include + +#define TAG "MusicPlayerWorker" + +#define MUSIC_PLAYER_FILETYPE "Flipper Music Format" +#define MUSIC_PLAYER_VERSION 0 + +#define SEMITONE_PAUSE 0xFF + +#define NOTE_C4 261.63f +#define NOTE_C4_SEMITONE (4.0f * 12.0f) +#define TWO_POW_TWELTH_ROOT 1.059463094359f + +typedef struct { + uint8_t semitone; + uint8_t duration; + uint8_t dots; +} NoteBlock; + +ARRAY_DEF(NoteBlockArray, NoteBlock, M_POD_OPLIST); + +struct MusicPlayerWorker { + FuriThread* thread; + bool should_work; + + MusicPlayerWorkerCallback callback; + void* callback_context; + + float volume; + uint32_t bpm; + uint32_t duration; + uint32_t octave; + NoteBlockArray_t notes; +}; + +static int32_t music_player_worker_thread_callback(void* context) { + furi_assert(context); + MusicPlayerWorker* instance = context; + + NoteBlockArray_it_t it; + NoteBlockArray_it(it, instance->notes); + + while(instance->should_work) { + if(NoteBlockArray_end_p(it)) { + NoteBlockArray_it(it, instance->notes); + osDelay(10); + } else { + NoteBlock* note_block = NoteBlockArray_ref(it); + + float note_from_a4 = (float)note_block->semitone - NOTE_C4_SEMITONE; + float frequency = NOTE_C4 * powf(TWO_POW_TWELTH_ROOT, note_from_a4); + float duration = + 60.0 * osKernelGetTickFreq() * 4 / instance->bpm / note_block->duration; + while(note_block->dots > 0) { + duration += duration / 2; + note_block->dots--; + } + uint32_t next_tick = furi_hal_get_tick() + duration; + float volume = instance->volume; + + if(instance->callback) { + instance->callback( + note_block->semitone, + note_block->dots, + note_block->duration, + 0.0, + instance->callback_context); + } + + furi_hal_speaker_stop(); + furi_hal_speaker_start(frequency, volume); + while(instance->should_work && furi_hal_get_tick() < next_tick) { + volume *= 0.9945679; + furi_hal_speaker_set_volume(volume); + furi_hal_delay_ms(2); + } + NoteBlockArray_next(it); + } + } + + furi_hal_speaker_stop(); + + return 0; +} + +MusicPlayerWorker* music_player_worker_alloc() { + MusicPlayerWorker* instance = malloc(sizeof(MusicPlayerWorker)); + + NoteBlockArray_init(instance->notes); + + instance->thread = furi_thread_alloc(); + furi_thread_set_name(instance->thread, "MusicPlayerWorker"); + furi_thread_set_stack_size(instance->thread, 1024); + furi_thread_set_context(instance->thread, instance); + furi_thread_set_callback(instance->thread, music_player_worker_thread_callback); + + return instance; +} + +void music_player_worker_free(MusicPlayerWorker* instance) { + furi_assert(instance); + furi_thread_free(instance->thread); + NoteBlockArray_clear(instance->notes); + free(instance); +} + +static bool is_digit(const char c) { + return isdigit(c) != 0; +} + +static bool is_letter(const char c) { + return islower(c) != 0 || isupper(c) != 0; +} + +static bool is_space(const char c) { + return c == ' ' || c == '\t'; +} + +static size_t extract_number(const char* string, uint32_t* number) { + size_t ret = 0; + while(is_digit(*string)) { + *number *= 10; + *number += (*string - '0'); + string++; + ret++; + } + return ret; +} + +static size_t extract_dots(const char* string, uint32_t* number) { + size_t ret = 0; + while(*string == '.') { + *number += 1; + string++; + ret++; + } + return ret; +} + +static size_t extract_char(const char* string, char* symbol) { + if(is_letter(*string)) { + *symbol = *string; + return 1; + } else { + return 0; + } +} + +static size_t extract_sharp(const char* string, char* symbol) { + if(*string == '#' || *string == '_') { + *symbol = '#'; + return 1; + } else { + return 0; + } +} + +static size_t skip_till(const char* string, const char symbol) { + size_t ret = 0; + while(*string != '\0' && *string != symbol) { + string++; + ret++; + } + if(*string != symbol) { + ret = 0; + } + return ret; +} + +static bool music_player_worker_add_note( + MusicPlayerWorker* instance, + uint8_t semitone, + uint8_t duration, + uint8_t dots) { + NoteBlock note_block; + + note_block.semitone = semitone; + note_block.duration = duration; + note_block.dots = dots; + + NoteBlockArray_push_back(instance->notes, note_block); + + return true; +} + +static int8_t note_to_semitone(const char note) { + switch(note) { + case 'C': + return 0; + // C# + case 'D': + return 2; + // D# + case 'E': + return 4; + case 'F': + return 5; + // F# + case 'G': + return 7; + // G# + case 'A': + return 9; + // A# + case 'B': + return 11; + default: + return 0; + } +} + +static bool music_player_worker_parse_notes(MusicPlayerWorker* instance, const char* string) { + const char* cursor = string; + bool result = true; + + while(*cursor != '\0') { + if(!is_space(*cursor)) { + uint32_t duration = 0; + char note_char = '\0'; + char sharp_char = '\0'; + uint32_t octave = 0; + uint32_t dots = 0; + + // Parsing + cursor += extract_number(cursor, &duration); + cursor += extract_char(cursor, ¬e_char); + cursor += extract_sharp(cursor, &sharp_char); + cursor += extract_number(cursor, &octave); + cursor += extract_dots(cursor, &dots); + + // Post processing + note_char = toupper(note_char); + if(!duration) { + duration = instance->duration; + } + if(!octave) { + octave = instance->octave; + } + + // Validation + bool is_valid = true; + is_valid &= (duration >= 1 && duration <= 128); + is_valid &= ((note_char >= 'A' && note_char <= 'G') || note_char == 'P'); + is_valid &= (sharp_char == '#' || sharp_char == '\0'); + is_valid &= (octave >= 0 && octave <= 16); + is_valid &= (dots >= 0 && dots <= 16); + if(!is_valid) { + FURI_LOG_E( + TAG, + "Invalid note: %u%c%c%u.%u", + duration, + note_char == '\0' ? '_' : note_char, + sharp_char == '\0' ? '_' : sharp_char, + octave, + dots); + result = false; + break; + } + + // Note to semitones + uint8_t semitone = 0; + if(note_char == 'P') { + semitone = SEMITONE_PAUSE; + } else { + semitone += octave * 12; + semitone += note_to_semitone(note_char); + semitone += sharp_char == '#' ? 1 : 0; + } + + if(music_player_worker_add_note(instance, semitone, duration, dots)) { + FURI_LOG_D( + TAG, + "Added note: %c%c%u.%u = %u %u", + note_char == '\0' ? '_' : note_char, + sharp_char == '\0' ? '_' : sharp_char, + octave, + dots, + semitone, + duration); + } else { + FURI_LOG_E( + TAG, + "Invalid note: %c%c%u.%u = %u %u", + note_char == '\0' ? '_' : note_char, + sharp_char == '\0' ? '_' : sharp_char, + octave, + dots, + semitone, + duration); + } + cursor += skip_till(cursor, ','); + } + + if(*cursor != '\0') cursor++; + } + + return result; +} + +bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path) { + furi_assert(instance); + furi_assert(file_path); + + bool ret = false; + if(strcasestr(file_path, ".fmf")) { + ret = music_player_worker_load_fmf_from_file(instance, file_path); + } else { + ret = music_player_worker_load_rtttl_from_file(instance, file_path); + } + return ret; +} + +bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const char* file_path) { + furi_assert(instance); + furi_assert(file_path); + + bool result = false; + string_t temp_str; + string_init(temp_str); + + Storage* storage = furi_record_open("storage"); + FlipperFormat* file = flipper_format_file_alloc(storage); + + do { + if(!flipper_format_file_open_existing(file, file_path)) break; + + uint32_t version = 0; + if(!flipper_format_read_header(file, temp_str, &version)) break; + if(string_cmp_str(temp_str, MUSIC_PLAYER_FILETYPE) || (version != MUSIC_PLAYER_VERSION)) { + FURI_LOG_E(TAG, "Incorrect file format or version"); + break; + } + + if(!flipper_format_read_uint32(file, "BPM", &instance->bpm, 1)) { + FURI_LOG_E(TAG, "BPM is missing"); + break; + } + if(!flipper_format_read_uint32(file, "Duration", &instance->duration, 1)) { + FURI_LOG_E(TAG, "Duration is missing"); + break; + } + if(!flipper_format_read_uint32(file, "Octave", &instance->octave, 1)) { + FURI_LOG_E(TAG, "Octave is missing"); + break; + } + + if(!flipper_format_read_string(file, "Notes", temp_str)) { + FURI_LOG_E(TAG, "Notes is missing"); + break; + } + + if(!music_player_worker_parse_notes(instance, string_get_cstr(temp_str))) { + break; + } + + result = true; + } while(false); + + furi_record_close("storage"); + flipper_format_free(file); + string_clear(temp_str); + + return result; +} + +bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const char* file_path) { + furi_assert(instance); + furi_assert(file_path); + + bool result = false; + string_t content; + string_init(content); + Storage* storage = furi_record_open("storage"); + File* file = storage_file_alloc(storage); + + do { + if(!storage_file_open(file, file_path, FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_E(TAG, "Unable to open file"); + break; + }; + + uint16_t ret = 0; + do { + uint8_t buffer[65] = {0}; + ret = storage_file_read(file, buffer, sizeof(buffer) - 1); + for(size_t i = 0; i < ret; i++) { + string_push_back(content, buffer[i]); + } + } while(ret > 0); + + string_strim(content); + if(!string_size(content)) { + FURI_LOG_E(TAG, "Empty file"); + break; + } + + if(!music_player_worker_load_rtttl_from_string(instance, string_get_cstr(content))) { + FURI_LOG_E(TAG, "Invalid file content"); + break; + } + + result = true; + } while(0); + + storage_file_free(file); + furi_record_close("storage"); + string_clear(content); + + return result; +} + +bool music_player_worker_load_rtttl_from_string(MusicPlayerWorker* instance, const char* string) { + furi_assert(instance); + + const char* cursor = string; + + // Skip name + cursor += skip_till(cursor, ':'); + if(*cursor != ':') { + return false; + } + + // Duration + cursor += skip_till(cursor, '='); + if(*cursor != '=') { + return false; + } + cursor++; + cursor += extract_number(cursor, &instance->duration); + + // Octave + cursor += skip_till(cursor, '='); + if(*cursor != '=') { + return false; + } + cursor++; + cursor += extract_number(cursor, &instance->octave); + + // BPM + cursor += skip_till(cursor, '='); + if(*cursor != '=') { + return false; + } + cursor++; + cursor += extract_number(cursor, &instance->bpm); + + // Notes + cursor += skip_till(cursor, ':'); + if(*cursor != ':') { + return false; + } + cursor++; + if(!music_player_worker_parse_notes(instance, cursor)) { + return false; + } + + return true; +} + +void music_player_worker_set_callback( + MusicPlayerWorker* instance, + MusicPlayerWorkerCallback callback, + void* context) { + furi_assert(instance); + instance->callback = callback; + instance->callback_context = context; +} + +void music_player_worker_set_volume(MusicPlayerWorker* instance, float volume) { + furi_assert(instance); + instance->volume = volume; +} + +void music_player_worker_start(MusicPlayerWorker* instance) { + furi_assert(instance); + furi_assert(instance->should_work == false); + + instance->should_work = true; + furi_thread_start(instance->thread); +} + +void music_player_worker_stop(MusicPlayerWorker* instance) { + furi_assert(instance); + furi_assert(instance->should_work == true); + + instance->should_work = false; + furi_thread_join(instance->thread); +} diff --git a/applications/music_player/music_player_worker.h b/applications/music_player/music_player_worker.h new file mode 100644 index 000000000..3aa99ea37 --- /dev/null +++ b/applications/music_player/music_player_worker.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +typedef void (*MusicPlayerWorkerCallback)( + uint8_t semitone, + uint8_t dots, + uint8_t duration, + float position, + void* context); + +typedef struct MusicPlayerWorker MusicPlayerWorker; + +MusicPlayerWorker* music_player_worker_alloc(); + +void music_player_worker_free(MusicPlayerWorker* instance); + +bool music_player_worker_load(MusicPlayerWorker* instance, const char* file_path); + +bool music_player_worker_load_fmf_from_file(MusicPlayerWorker* instance, const char* file_path); + +bool music_player_worker_load_rtttl_from_file(MusicPlayerWorker* instance, const char* file_path); + +bool music_player_worker_load_rtttl_from_string(MusicPlayerWorker* instance, const char* string); + +void music_player_worker_set_callback( + MusicPlayerWorker* instance, + MusicPlayerWorkerCallback callback, + void* context); + +void music_player_worker_set_volume(MusicPlayerWorker* instance, float volume); + +void music_player_worker_start(MusicPlayerWorker* instance); + +void music_player_worker_stop(MusicPlayerWorker* instance); diff --git a/applications/subghz/helpers/subghz_frequency_analyzer_worker.c b/applications/subghz/helpers/subghz_frequency_analyzer_worker.c index b6b3a1edc..8d8ad0b65 100644 --- a/applications/subghz/helpers/subghz_frequency_analyzer_worker.c +++ b/applications/subghz/helpers/subghz_frequency_analyzer_worker.c @@ -71,6 +71,7 @@ static int32_t subghz_frequency_analyzer_worker_thread(void* context) { furi_hal_subghz_load_preset(FuriHalSubGhzPresetOok650Async); furi_hal_subghz_set_frequency(433920000); furi_hal_subghz_flush_rx(); + furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate); furi_hal_subghz_rx(); while(instance->worker_running) { diff --git a/applications/unit_tests/subghz/subghz_test.c b/applications/unit_tests/subghz/subghz_test.c index 48613868d..6dc0f964e 100644 --- a/applications/unit_tests/subghz/subghz_test.c +++ b/applications/unit_tests/subghz/subghz_test.c @@ -13,7 +13,7 @@ #define CAME_ATOMO_DIR_NAME "/ext/subghz/assets/came_atomo" #define NICE_FLOR_S_DIR_NAME "/ext/subghz/assets/nice_flor_s" #define TEST_RANDOM_DIR_NAME "/ext/unit_tests/subghz/test_random_raw.sub" -#define TEST_RANDOM_COUNT_PARSE 101 +#define TEST_RANDOM_COUNT_PARSE 59 #define TEST_TIMEOUT 10000 static SubGhzEnvironment* environment_handler; @@ -29,6 +29,7 @@ static void subghz_test_rx_callback( string_t text; string_init(text); subghz_protocol_decoder_base_get_string(decoder_base, text); + subghz_receiver_reset(receiver_handler); FURI_LOG_I(TAG, "\r\n%s", string_get_cstr(text)); string_clear(text); subghz_test_decoder_count++; diff --git a/assets/resources/Manifest b/assets/resources/Manifest index f19edd7de..08d950441 100644 --- a/assets/resources/Manifest +++ b/assets/resources/Manifest @@ -1,8 +1,9 @@ V:0 -T:1651076680 +T:1651524332 D:badusb D:dolphin D:infrared +D:music_player D:nfc D:subghz D:u2f @@ -223,6 +224,7 @@ F:f267f0654781049ca323b11bb4375519:581:dolphin/L3_Lab_research_128x54/frame_9.bm F:41106c0cbc5144f151b2b2d3daaa0527:727:dolphin/L3_Lab_research_128x54/meta.txt D:infrared/assets F:d895fda2f48c6cc4c55e8a398ff52e43:74300:infrared/assets/tv.ir +F:a157a80f5a668700403d870c23b9567d:470:music_player/Marble_Machine.fmf D:nfc/assets F:c6826a621d081d68309e4be424d3d974:4715:nfc/assets/aid.nfc F:86efbebdf41bb6bf15cc51ef88f069d5:2565:nfc/assets/country_code.nfc diff --git a/assets/resources/music_player/Marble_Machine.fmf b/assets/resources/music_player/Marble_Machine.fmf new file mode 100644 index 000000000..7403c9a0f --- /dev/null +++ b/assets/resources/music_player/Marble_Machine.fmf @@ -0,0 +1,6 @@ +Filetype: Flipper Music Format +Version: 0 +BPM: 130 +Duration: 8 +Octave: 5 +Notes: E6, P, E, B, 4P, E, A, G, A, E, B, P, G, A, D6, 4P, D, B, 4P, D, A, G, A, D, F#, P, G, A, D6, 4P, F#, B, 4P, F#, D6, C6, B, F#, A, P, G, F#, E, P, C, E, B, B4, C, D, D6, C6, B, F#, A, P, G, A, E6, 4P, E, B, 4P, E, A, G, A, E, B, P, G, A, D6, 4P, D, B, 4P, D, A, G, A, D, F#, P, G, A, D6, 4P, F#, B, 4P, F#, D6, C6, B, F#, A, P, G, F#, E, P, C, E, B, B4, C, D, D6, C6, B, F#, A, P, G, A, E6 diff --git a/firmware/targets/f7/furi_hal/furi_hal_resources.c b/firmware/targets/f7/furi_hal/furi_hal_resources.c index e548ffaf3..4209e0cf8 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_resources.c +++ b/firmware/targets/f7/furi_hal/furi_hal_resources.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -87,10 +88,19 @@ void furi_hal_resources_init_early() { SET_BIT(PWR->CR3, PWR_CR3_APC); // Hard reset USB + furi_hal_gpio_write(&gpio_usb_dm, 1); + furi_hal_gpio_write(&gpio_usb_dp, 1); furi_hal_gpio_init_simple(&gpio_usb_dm, GpioModeOutputOpenDrain); furi_hal_gpio_init_simple(&gpio_usb_dp, GpioModeOutputOpenDrain); furi_hal_gpio_write(&gpio_usb_dm, 0); furi_hal_gpio_write(&gpio_usb_dp, 0); + furi_hal_delay_us(5); // Device Driven disconnect: 2.5us + extra to compensate cables + furi_hal_gpio_write(&gpio_usb_dm, 1); + furi_hal_gpio_write(&gpio_usb_dp, 1); + furi_hal_gpio_init_simple(&gpio_usb_dm, GpioModeAnalog); + furi_hal_gpio_init_simple(&gpio_usb_dp, GpioModeAnalog); + furi_hal_gpio_write(&gpio_usb_dm, 0); + furi_hal_gpio_write(&gpio_usb_dp, 0); // External header pins furi_hal_gpio_init(&gpio_ext_pc0, GpioModeAnalog, GpioPullNo, GpioSpeedLow); diff --git a/firmware/targets/f7/furi_hal/furi_hal_speaker.c b/firmware/targets/f7/furi_hal/furi_hal_speaker.c index 001861986..03a7f094b 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_speaker.c +++ b/firmware/targets/f7/furi_hal/furi_hal_speaker.c @@ -20,15 +20,7 @@ void furi_hal_speaker_init() { &gpio_speaker, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn14TIM16); } -void furi_hal_speaker_start(float frequency, float volume) { - if(volume == 0) { - return; - } - - if(volume < 0) volume = 0; - if(volume > 1) volume = 1; - volume = volume * volume * volume; - +static inline uint32_t furi_hal_speaker_calculate_autoreload(float frequency) { uint32_t autoreload = (SystemCoreClock / FURI_HAL_SPEAKER_PRESCALER / frequency) - 1; if(autoreload < 2) { autoreload = 2; @@ -36,35 +28,65 @@ void furi_hal_speaker_start(float frequency, float volume) { autoreload = UINT16_MAX; } - LL_TIM_InitTypeDef TIM_InitStruct = {0}; - TIM_InitStruct.Prescaler = FURI_HAL_SPEAKER_PRESCALER - 1; - TIM_InitStruct.Autoreload = autoreload; - LL_TIM_Init(FURI_HAL_SPEAKER_TIMER, &TIM_InitStruct); + return autoreload; +} + +static inline uint32_t furi_hal_speaker_calculate_compare(float volume) { + if(volume < 0) volume = 0; + if(volume > 1) volume = 1; + volume = volume * volume * volume; #ifdef FURI_HAL_SPEAKER_NEW_VOLUME uint32_t compare_value = volume * FURI_HAL_SPEAKER_MAX_VOLUME; - uint32_t clip_value = volume * TIM_InitStruct.Autoreload / 2; + uint32_t clip_value = volume * LL_TIM_GetAutoReload(FURI_HAL_SPEAKER_TIMER) / 2; if(compare_value > clip_value) { compare_value = clip_value; } #else - uint32_t compare_value = volume * autoreload / 2; + uint32_t compare_value = volume * LL_TIM_GetAutoReload(FURI_HAL_SPEAKER_TIMER) / 2; #endif if(compare_value == 0) { compare_value = 1; } + return compare_value; +} + +void furi_hal_speaker_start(float frequency, float volume) { + if(volume <= 0) { + furi_hal_speaker_stop(); + return; + } + + LL_TIM_InitTypeDef TIM_InitStruct = {0}; + TIM_InitStruct.Prescaler = FURI_HAL_SPEAKER_PRESCALER - 1; + TIM_InitStruct.Autoreload = furi_hal_speaker_calculate_autoreload(frequency); + LL_TIM_Init(FURI_HAL_SPEAKER_TIMER, &TIM_InitStruct); + LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; - TIM_OC_InitStruct.CompareValue = compare_value; + TIM_OC_InitStruct.CompareValue = furi_hal_speaker_calculate_compare(volume); LL_TIM_OC_Init(FURI_HAL_SPEAKER_TIMER, FURI_HAL_SPEAKER_CHANNEL, &TIM_OC_InitStruct); LL_TIM_EnableAllOutputs(FURI_HAL_SPEAKER_TIMER); LL_TIM_EnableCounter(FURI_HAL_SPEAKER_TIMER); } +void furi_hal_speaker_set_volume(float volume) { + if(volume <= 0) { + furi_hal_speaker_stop(); + return; + } + +#if FURI_HAL_SPEAKER_CHANNEL == LL_TIM_CHANNEL_CH1 + LL_TIM_OC_SetCompareCH1(FURI_HAL_SPEAKER_TIMER, furi_hal_speaker_calculate_compare(volume)); +#else +#error Invalid channel +#endif +} + void furi_hal_speaker_stop() { LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER); LL_TIM_DisableCounter(FURI_HAL_SPEAKER_TIMER); diff --git a/firmware/targets/furi_hal_include/furi_hal_speaker.h b/firmware/targets/furi_hal_include/furi_hal_speaker.h index 35c89fb69..67de41d92 100644 --- a/firmware/targets/furi_hal_include/furi_hal_speaker.h +++ b/firmware/targets/furi_hal_include/furi_hal_speaker.h @@ -12,6 +12,8 @@ void furi_hal_speaker_init(); void furi_hal_speaker_start(float frequency, float volume); +void furi_hal_speaker_set_volume(float volume); + void furi_hal_speaker_stop(); #ifdef __cplusplus diff --git a/lib/subghz/blocks/generic.h b/lib/subghz/blocks/generic.h index aec78214c..5746fd5de 100644 --- a/lib/subghz/blocks/generic.h +++ b/lib/subghz/blocks/generic.h @@ -16,7 +16,7 @@ struct SubGhzBlockGeneric { uint32_t serial; uint8_t data_count_bit; uint8_t btn; - uint16_t cnt; + uint32_t cnt; uint32_t seed; }; diff --git a/lib/subghz/protocols/firefly.c b/lib/subghz/protocols/firefly.c index e872d5580..3a339e0a9 100644 --- a/lib/subghz/protocols/firefly.c +++ b/lib/subghz/protocols/firefly.c @@ -6,12 +6,6 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -/* - * Help - * https://phreakerclub.com/447 - * - */ - #define TAG "SubGhzProtocolFirefly" #define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c" diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 403d23ef6..f5ab875e3 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -11,7 +11,7 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#define TAG "SubGhzProtocolkeeloq" +#define TAG "SubGhzProtocolKeeloq" static const SubGhzBlockConst subghz_protocol_keeloq_const = { .te_short = 400, diff --git a/lib/subghz/protocols/nero_sketch.c b/lib/subghz/protocols/nero_sketch.c index 12a1a5476..474b742ad 100644 --- a/lib/subghz/protocols/nero_sketch.c +++ b/lib/subghz/protocols/nero_sketch.c @@ -6,12 +6,6 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -/* - * Help - * https://phreakerclub.com/447 - * - */ - #define TAG "SubGhzProtocolNeroSketch" static const SubGhzBlockConst subghz_protocol_nero_sketch_const = { diff --git a/lib/subghz/protocols/nice_flo.c b/lib/subghz/protocols/nice_flo.c index 01371b735..6f94e8fdf 100644 --- a/lib/subghz/protocols/nice_flo.c +++ b/lib/subghz/protocols/nice_flo.c @@ -5,12 +5,6 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -/* - * Help - * https://phreakerclub.com/447 - * - */ - #define TAG "SubGhzProtocolNiceFLO" static const SubGhzBlockConst subghz_protocol_nice_flo_const = { diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index 518c55472..9de52eb05 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -5,6 +5,7 @@ #include "../blocks/encoder.h" #include "../blocks/generic.h" #include "../blocks/math.h" + /* * https://phreakerclub.com/1615 * https://phreakerclub.com/forum/showthread.php?t=2360 diff --git a/lib/subghz/protocols/princeton.c b/lib/subghz/protocols/princeton.c index 4fa2b4810..42f035e6d 100644 --- a/lib/subghz/protocols/princeton.c +++ b/lib/subghz/protocols/princeton.c @@ -12,7 +12,7 @@ * */ -#define TAG "SubGhzProtocolCAME" +#define TAG "SubGhzProtocolPrinceton" static const SubGhzBlockConst subghz_protocol_princeton_const = { .te_short = 400, @@ -28,6 +28,7 @@ struct SubGhzProtocolDecoderPrinceton { SubGhzBlockGeneric generic; uint32_t te; + uint32_t last_data; }; struct SubGhzProtocolEncoderPrinceton { @@ -209,6 +210,7 @@ void subghz_protocol_decoder_princeton_reset(void* context) { furi_assert(context); SubGhzProtocolDecoderPrinceton* instance = context; instance->decoder.parser_step = PrincetonDecoderStepReset; + instance->last_data = 0; } void subghz_protocol_decoder_princeton_feed(void* context, bool level, uint32_t duration) { @@ -241,15 +243,18 @@ void subghz_protocol_decoder_princeton_feed(void* context, bool level, uint32_t instance->decoder.parser_step = PrincetonDecoderStepSaveDuration; if(instance->decoder.decode_count_bit == subghz_protocol_princeton_const.min_count_bit_for_found) { - instance->te /= (instance->decoder.decode_count_bit * 4 + 1); + if(instance->last_data == instance->decoder.decode_data) { + instance->te /= (instance->decoder.decode_count_bit * 4 + 1); - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = instance->decoder.decode_count_bit; - instance->generic.serial = instance->decoder.decode_data >> 4; - instance->generic.btn = (uint8_t)instance->decoder.decode_data & 0x00000F; + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + instance->generic.serial = instance->decoder.decode_data >> 4; + instance->generic.btn = (uint8_t)instance->decoder.decode_data & 0x00000F; - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->last_data = instance->decoder.decode_data; } instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; diff --git a/lib/subghz/protocols/princeton_for_testing.c b/lib/subghz/protocols/princeton_for_testing.c index 1c4552f46..bdf5f4292 100644 --- a/lib/subghz/protocols/princeton_for_testing.c +++ b/lib/subghz/protocols/princeton_for_testing.c @@ -2,6 +2,7 @@ #include "furi_hal.h" #include "../blocks/math.h" + /* * Help * https://phreakerclub.com/447 diff --git a/lib/subghz/protocols/registry.c b/lib/subghz/protocols/registry.c index c384eda11..a88603b64 100644 --- a/lib/subghz/protocols/registry.c +++ b/lib/subghz/protocols/registry.c @@ -7,7 +7,8 @@ const SubGhzProtocol* subghz_protocol_registry[] = { &subghz_protocol_nero_sketch, &subghz_protocol_ido, &subghz_protocol_kia, &subghz_protocol_hormann, &subghz_protocol_nero_radio, &subghz_protocol_somfy_telis, &subghz_protocol_somfy_keytis, &subghz_protocol_scher_khan, &subghz_protocol_gate_tx, - &subghz_protocol_raw, &subghz_protocol_firefly, + &subghz_protocol_raw, &subghz_protocol_firefly, &subghz_protocol_secplus_v2, + &subghz_protocol_secplus_v1, }; diff --git a/lib/subghz/protocols/registry.h b/lib/subghz/protocols/registry.h index 531caee2a..8d904fe1f 100644 --- a/lib/subghz/protocols/registry.h +++ b/lib/subghz/protocols/registry.h @@ -22,6 +22,8 @@ #include "gate_tx.h" #include "raw.h" #include "firefly.h" +#include "secplus_v2.h" +#include "secplus_v1.h" /** * Registration by name SubGhzProtocol. diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c new file mode 100644 index 000000000..1ad605b2f --- /dev/null +++ b/lib/subghz/protocols/secplus_v1.c @@ -0,0 +1,369 @@ +#include "secplus_v1.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* +* Help +* https://github.com/argilo/secplus +* https://github.com/merbanan/rtl_433/blob/master/src/devices/secplus_v1.c +*/ + +#define TAG "SubGhzProtocoSecPlus_v1" + +#define SECPLUS_V1_BIT_ERR -1 //0b0000 +#define SECPLUS_V1_BIT_0 0 //0b0001 +#define SECPLUS_V1_BIT_1 1 //0b0011 +#define SECPLUS_V1_BIT_2 2 //0b0111 + +#define SECPLUS_V1_PACKET_1_HEADER 0x00 +#define SECPLUS_V1_PACKET_2_HEADER 0x02 +#define SECPLUS_V1_PACKET_1_INDEX_BASE 0 +#define SECPLUS_V1_PACKET_2_INDEX_BASE 21 +#define SECPLUS_V1_PACKET_1_ACCEPTED (1 << 0) +#define SECPLUS_V1_PACKET_2_ACCEPTED (1 << 1) + +static const SubGhzBlockConst subghz_protocol_secplus_v1_const = { + .te_short = 500, + .te_long = 1500, + .te_delta = 100, + .min_count_bit_for_found = 21, +}; + +struct SubGhzProtocolDecoderSecPlus_v1 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint8_t packet_accepted; + uint8_t base_packet_index; + uint8_t data_array[44]; +}; + +struct SubGhzProtocolEncoderSecPlus_v1 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + SecPlus_v1DecoderStepReset = 0, + SecPlus_v1DecoderStepSearchStartBit, + SecPlus_v1DecoderStepSaveDuration, + SecPlus_v1DecoderStepDecoderData, +} SecPlus_v1DecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_secplus_v1_decoder = { + .alloc = subghz_protocol_decoder_secplus_v1_alloc, + .free = subghz_protocol_decoder_secplus_v1_free, + + .feed = subghz_protocol_decoder_secplus_v1_feed, + .reset = subghz_protocol_decoder_secplus_v1_reset, + + .get_hash_data = subghz_protocol_decoder_secplus_v1_get_hash_data, + .serialize = subghz_protocol_decoder_secplus_v1_serialize, + .deserialize = subghz_protocol_decoder_secplus_v1_deserialize, + .get_string = subghz_protocol_decoder_secplus_v1_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_secplus_v1_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_secplus_v1 = { + .name = SUBGHZ_PROTOCOL_SECPLUS_V1_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_secplus_v1_decoder, + .encoder = &subghz_protocol_secplus_v1_encoder, +}; + +void* subghz_protocol_decoder_secplus_v1_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderSecPlus_v1* instance = malloc(sizeof(SubGhzProtocolDecoderSecPlus_v1)); + instance->base.protocol = &subghz_protocol_secplus_v1; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_secplus_v1_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v1* instance = context; + free(instance); +} + +void subghz_protocol_decoder_secplus_v1_reset(void* context) { + furi_assert(context); + // SubGhzProtocolDecoderSecPlus_v1* instance = context; + // does not reset the decoder because you need to get 2 parts of the package +} + +/** + * Security+ 1.0 half-message decoding + * @param instance SubGhzProtocolDecoderSecPlus_v1* + */ + +static void subghz_protocol_secplus_v1_decode(SubGhzProtocolDecoderSecPlus_v1* instance) { + uint32_t rolling = 0; + uint32_t fixed = 0; + uint32_t acc = 0; + uint8_t digit = 0; + + //decode packet 1 + for(uint8_t i = 1; i < 21; i += 2) { + digit = instance->data_array[i]; + rolling = (rolling * 3) + digit; + acc += digit; + + digit = (60 + instance->data_array[i + 1] - acc) % 3; + fixed = (fixed * 3) + digit; + acc += digit; + } + + acc = 0; + //decode packet 2 + for(uint8_t i = 22; i < 42; i += 2) { + digit = instance->data_array[i]; + rolling = (rolling * 3) + digit; + acc += digit; + + digit = (60 + instance->data_array[i + 1] - acc) % 3; + fixed = (fixed * 3) + digit; + acc += digit; + } + + rolling = subghz_protocol_blocks_reverse_key(rolling, 32); + instance->generic.data = (uint64_t)fixed << 32 | rolling; + + instance->generic.data_count_bit = + subghz_protocol_secplus_v1_const.min_count_bit_for_found * 2; +} + +void subghz_protocol_decoder_secplus_v1_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v1* instance = context; + + switch(instance->decoder.parser_step) { + case SecPlus_v1DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_secplus_v1_const.te_short * 120) < + subghz_protocol_secplus_v1_const.te_delta * 120)) { + //Found header Security+ 1.0 + instance->decoder.parser_step = SecPlus_v1DecoderStepSearchStartBit; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->packet_accepted = 0; + memset(instance->data_array, 0, sizeof(instance->data_array)); + } + break; + case SecPlus_v1DecoderStepSearchStartBit: + if(level) { + if(DURATION_DIFF(duration, subghz_protocol_secplus_v1_const.te_short) < + subghz_protocol_secplus_v1_const.te_delta) { + instance->base_packet_index = SECPLUS_V1_PACKET_1_INDEX_BASE; + instance + ->data_array[instance->decoder.decode_count_bit + instance->base_packet_index] = + SECPLUS_V1_BIT_0; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = SecPlus_v1DecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, subghz_protocol_secplus_v1_const.te_long) < + subghz_protocol_secplus_v1_const.te_delta) { + instance->base_packet_index = SECPLUS_V1_PACKET_2_INDEX_BASE; + instance + ->data_array[instance->decoder.decode_count_bit + instance->base_packet_index] = + SECPLUS_V1_BIT_2; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = SecPlus_v1DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = SecPlus_v1DecoderStepReset; + } + } else { + instance->decoder.parser_step = SecPlus_v1DecoderStepReset; + } + break; + case SecPlus_v1DecoderStepSaveDuration: + if(!level) { //save interval + if(DURATION_DIFF(duration, subghz_protocol_secplus_v1_const.te_short * 120) < + subghz_protocol_secplus_v1_const.te_delta * 120) { + if(instance->decoder.decode_count_bit == + subghz_protocol_secplus_v1_const.min_count_bit_for_found) { + if(instance->base_packet_index == SECPLUS_V1_PACKET_1_INDEX_BASE) + instance->packet_accepted |= SECPLUS_V1_PACKET_1_ACCEPTED; + if(instance->base_packet_index == SECPLUS_V1_PACKET_2_INDEX_BASE) + instance->packet_accepted |= SECPLUS_V1_PACKET_2_ACCEPTED; + + if(instance->packet_accepted == + (SECPLUS_V1_PACKET_1_ACCEPTED | SECPLUS_V1_PACKET_2_ACCEPTED)) { + subghz_protocol_secplus_v1_decode(instance); + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.parser_step = SecPlus_v1DecoderStepReset; + } + } + instance->decoder.parser_step = SecPlus_v1DecoderStepSearchStartBit; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else { + instance->decoder.te_last = duration; + instance->decoder.parser_step = SecPlus_v1DecoderStepDecoderData; + } + } else { + instance->decoder.parser_step = SecPlus_v1DecoderStepReset; + } + break; + case SecPlus_v1DecoderStepDecoderData: + if(level && (instance->decoder.decode_count_bit <= + subghz_protocol_secplus_v1_const.min_count_bit_for_found)) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_secplus_v1_const.te_short * 3) < + subghz_protocol_secplus_v1_const.te_delta * 3) && + (DURATION_DIFF(duration, subghz_protocol_secplus_v1_const.te_short) < + subghz_protocol_secplus_v1_const.te_delta)) { + instance + ->data_array[instance->decoder.decode_count_bit + instance->base_packet_index] = + SECPLUS_V1_BIT_0; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = SecPlus_v1DecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_secplus_v1_const.te_short * 2) < + subghz_protocol_secplus_v1_const.te_delta * 2) && + (DURATION_DIFF(duration, subghz_protocol_secplus_v1_const.te_short * 2) < + subghz_protocol_secplus_v1_const.te_delta * 2)) { + instance + ->data_array[instance->decoder.decode_count_bit + instance->base_packet_index] = + SECPLUS_V1_BIT_1; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = SecPlus_v1DecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_secplus_v1_const.te_short) < + subghz_protocol_secplus_v1_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_secplus_v1_const.te_short * 3) < + subghz_protocol_secplus_v1_const.te_delta * 3)) { + instance + ->data_array[instance->decoder.decode_count_bit + instance->base_packet_index] = + SECPLUS_V1_BIT_2; + instance->decoder.decode_count_bit++; + instance->decoder.parser_step = SecPlus_v1DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = SecPlus_v1DecoderStepReset; + } + } else { + instance->decoder.parser_step = SecPlus_v1DecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_secplus_v1_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v1* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_secplus_v1_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v1* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); +} + +bool subghz_protocol_decoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v1* instance = context; + return subghz_block_generic_deserialize(&instance->generic, flipper_format); +} + +void subghz_protocol_decoder_secplus_v1_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v1* instance = context; + + uint32_t fixed = (instance->generic.data >> 32) & 0xFFFFFFFF; + instance->generic.cnt = instance->generic.data & 0xFFFFFFFF; + + instance->generic.btn = fixed % 3; + uint8_t id0 = (fixed / 3) % 3; + uint8_t id1 = (fixed / 9) % 3; + uint16_t pin = 0; + + string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%lX%08lX\r\n" + "id1:%d id0:%d", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + id1, + id0); + + if(id1 == 0) { + // (fixed // 3**3) % (3**7) 3^3=27 3^73=72187 + + instance->generic.serial = (fixed / 27) % 2187; + // pin = (fixed // 3**10) % (3**9) 3^10=59049 3^9=19683 + pin = (fixed / 59049) % 19683; + + if(0 <= pin && pin <= 9999) { + string_cat_printf(output, " pin:%d", pin); + } else if(10000 <= pin && pin <= 11029) { + string_cat_printf(output, " pin:enter"); + } + + int pin_suffix = 0; + // pin_suffix = (fixed // 3**19) % 3 3^19=1162261467 + pin_suffix = (fixed / 1162261467) % 3; + + if(pin_suffix == 1) { + string_cat_printf(output, " #\r\n"); + } else if(pin_suffix == 2) { + string_cat_printf(output, " *\r\n"); + } else { + string_cat_printf(output, "\r\n"); + } + string_cat_printf( + output, + "Sn:0x%08lX\r\n" + "Cnt:0x%03X\r\n" + "Sw_id:0x%X\r\n", + instance->generic.serial, + instance->generic.cnt, + instance->generic.btn); + } else { + //id = fixed / 27; + instance->generic.serial = fixed / 27; + if(instance->generic.btn == 1) { + string_cat_printf(output, " Btn:left\r\n"); + } else if(instance->generic.btn == 0) { + string_cat_printf(output, " Btn:middle\r\n"); + } else if(instance->generic.btn == 2) { + string_cat_printf(output, " Btn:right\r\n"); + } + + string_cat_printf( + output, + "Sn:0x%08lX\r\n" + "Cnt:0x%03X\r\n" + "Sw_id:0x%X\r\n", + instance->generic.serial, + instance->generic.cnt, + instance->generic.btn); + } +} diff --git a/lib/subghz/protocols/secplus_v1.h b/lib/subghz/protocols/secplus_v1.h new file mode 100644 index 000000000..891f751cd --- /dev/null +++ b/lib/subghz/protocols/secplus_v1.h @@ -0,0 +1,74 @@ +#pragma once +#include "base.h" + +#define SUBGHZ_PROTOCOL_SECPLUS_V1_NAME "Security+ 1.0" + +typedef struct SubGhzProtocolDecoderSecPlus_v1 SubGhzProtocolDecoderSecPlus_v1; +typedef struct SubGhzProtocolEncoderSecPlus_v1 SubGhzProtocolEncoderSecPlus_v1; + +extern const SubGhzProtocolDecoder subghz_protocol_secplus_v1_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_secplus_v1_encoder; +extern const SubGhzProtocol subghz_protocol_secplus_v1; + +/** + * Allocate SubGhzProtocolDecoderSecPlus_v1. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderSecPlus_v1* pointer to a SubGhzProtocolDecoderSecPlus_v1 instance + */ +void* subghz_protocol_decoder_secplus_v1_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderSecPlus_v1. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance + */ +void subghz_protocol_decoder_secplus_v1_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderSecPlus_v1. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance + */ +void subghz_protocol_decoder_secplus_v1_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_secplus_v1_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_secplus_v1_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderSecPlus_v1. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ +bool subghz_protocol_decoder_secplus_v1_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderSecPlus_v1. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_secplus_v1_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_secplus_v1_get_string(void* context, string_t output); diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c new file mode 100644 index 000000000..4ad1ea9ef --- /dev/null +++ b/lib/subghz/protocols/secplus_v2.c @@ -0,0 +1,475 @@ +#include "secplus_v2.h" +#include +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +/* +* Help +* https://github.com/argilo/secplus +* https://github.com/merbanan/rtl_433/blob/master/src/devices/secplus_v2.c +*/ + +#define TAG "SubGhzProtocoSecPlus_v2" + +#define SECPLUS_V2_HEADER 0x3C0000000000 +#define SECPLUS_V2_HEADER_MASK 0xFFFF3C0000000000 +#define SECPLUS_V2_PACKET_1 0x000000000000 +#define SECPLUS_V2_PACKET_2 0x010000000000 +#define SECPLUS_V2_PACKET_MASK 0x30000000000 + +static const SubGhzBlockConst subghz_protocol_secplus_v2_const = { + .te_short = 250, + .te_long = 500, + .te_delta = 110, + .min_count_bit_for_found = 62, +}; + +struct SubGhzProtocolDecoderSecPlus_v2 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + ManchesterState manchester_saved_state; + uint64_t secplus_packet_1; +}; + +struct SubGhzProtocolEncoderSecPlus_v2 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + SecPlus_v2DecoderStepReset = 0, + SecPlus_v2DecoderStepDecoderData, +} SecPlus_v2DecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_secplus_v2_decoder = { + .alloc = subghz_protocol_decoder_secplus_v2_alloc, + .free = subghz_protocol_decoder_secplus_v2_free, + + .feed = subghz_protocol_decoder_secplus_v2_feed, + .reset = subghz_protocol_decoder_secplus_v2_reset, + + .get_hash_data = subghz_protocol_decoder_secplus_v2_get_hash_data, + .serialize = subghz_protocol_decoder_secplus_v2_serialize, + .deserialize = subghz_protocol_decoder_secplus_v2_deserialize, + .get_string = subghz_protocol_decoder_secplus_v2_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_secplus_v2_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_secplus_v2 = { + .name = SUBGHZ_PROTOCOL_SECPLUS_V2_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &subghz_protocol_secplus_v2_decoder, + .encoder = &subghz_protocol_secplus_v2_encoder, +}; + +void* subghz_protocol_decoder_secplus_v2_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderSecPlus_v2* instance = malloc(sizeof(SubGhzProtocolDecoderSecPlus_v2)); + instance->base.protocol = &subghz_protocol_secplus_v2; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_secplus_v2_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v2* instance = context; + free(instance); +} + +void subghz_protocol_decoder_secplus_v2_reset(void* context) { + furi_assert(context); + // SubGhzProtocolDecoderSecPlus_v2* instance = context; + // does not reset the decoder because you need to get 2 parts of the package +} + +static bool subghz_protocol_secplus_v2_check_packet(SubGhzProtocolDecoderSecPlus_v2* instance) { + if((instance->decoder.decode_data & SECPLUS_V2_HEADER_MASK) == SECPLUS_V2_HEADER) { + if((instance->decoder.decode_data & SECPLUS_V2_PACKET_MASK) == SECPLUS_V2_PACKET_1) { + instance->secplus_packet_1 = instance->decoder.decode_data; + } else if( + ((instance->decoder.decode_data & SECPLUS_V2_PACKET_MASK) == SECPLUS_V2_PACKET_2) && + (instance->secplus_packet_1)) { + return true; + } + } + return false; +} + +void subghz_protocol_decoder_secplus_v2_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v2* instance = context; + + ManchesterEvent event = ManchesterEventReset; + switch(instance->decoder.parser_step) { + case SecPlus_v2DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_secplus_v2_const.te_long * 130) < + subghz_protocol_secplus_v2_const.te_delta * 100)) { + //Found header Security+ 2.0 + instance->decoder.parser_step = SecPlus_v2DecoderStepDecoderData; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->secplus_packet_1 = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongHigh, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventShortLow, + &instance->manchester_saved_state, + NULL); + } + break; + case SecPlus_v2DecoderStepDecoderData: + if(!level) { + if(DURATION_DIFF(duration, subghz_protocol_secplus_v2_const.te_short) < + subghz_protocol_secplus_v2_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, subghz_protocol_secplus_v2_const.te_long) < + subghz_protocol_secplus_v2_const.te_delta) { + event = ManchesterEventLongLow; + } else if( + duration >= (subghz_protocol_secplus_v2_const.te_long * 2 + + subghz_protocol_secplus_v2_const.te_delta)) { + if(instance->decoder.decode_count_bit >= + subghz_protocol_secplus_v2_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(subghz_protocol_secplus_v2_check_packet(instance)) { + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.parser_step = SecPlus_v2DecoderStepReset; + } + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventLongHigh, + &instance->manchester_saved_state, + NULL); + manchester_advance( + instance->manchester_saved_state, + ManchesterEventShortLow, + &instance->manchester_saved_state, + NULL); + } else { + instance->decoder.parser_step = SecPlus_v2DecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, subghz_protocol_secplus_v2_const.te_short) < + subghz_protocol_secplus_v2_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, subghz_protocol_secplus_v2_const.te_long) < + subghz_protocol_secplus_v2_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = SecPlus_v2DecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data; + instance->decoder.decode_count_bit++; + } + } + break; + } +} + +/** + * Security+ 2.0 half-message decoding + * @param data data + * @param roll_array[] return roll_array part + * @param fixed[] return fixed part + * @return true On success + */ + +static bool + subghz_protocol_secplus_v2_decode_half(uint64_t data, uint8_t roll_array[], uint32_t* fixed) { + uint8_t order = (data >> 34) & 0x0f; + uint8_t invert = (data >> 30) & 0x0f; + uint16_t p[3] = {0}; + + for(int i = 29; i >= 0; i -= 3) { + p[0] = p[0] << 1 | bit_read(data, i); + p[1] = p[1] << 1 | bit_read(data, i - 1); + p[2] = p[2] << 1 | bit_read(data, i - 2); + } + + // selectively invert buffers + switch(invert) { + case 0x00: // 0b0000 (True, True, False), + p[0] = ~p[0] & 0x03FF; + p[1] = ~p[1] & 0x03FF; + break; + case 0x01: // 0b0001 (False, True, False), + p[1] = ~p[1] & 0x03FF; + break; + case 0x02: // 0b0010 (False, False, True), + p[2] = ~p[2] & 0x03FF; + break; + case 0x04: // 0b0100 (True, True, True), + p[0] = ~p[0] & 0x03FF; + p[1] = ~p[1] & 0x03FF; + p[2] = ~p[2] & 0x03FF; + break; + case 0x05: // 0b0101 (True, False, True), + case 0x0a: // 0b1010 (True, False, True), + p[0] = ~p[0] & 0x03FF; + p[2] = ~p[2] & 0x03FF; + break; + case 0x06: // 0b0110 (False, True, True), + p[1] = ~p[1] & 0x03FF; + p[2] = ~p[2] & 0x03FF; + break; + case 0x08: // 0b1000 (True, False, False), + p[0] = ~p[0] & 0x03FF; + break; + case 0x09: // 0b1001 (False, False, False), + break; + default: + FURI_LOG_E(TAG, "Invert FAIL"); + return false; + } + + uint16_t a = p[0], b = p[1], c = p[2]; + + // selectively reorder buffers + switch(order) { + case 0x06: // 0b0110 2, 1, 0], + case 0x09: // 0b1001 2, 1, 0], + p[2] = a; + p[1] = b; + p[0] = c; + break; + case 0x08: // 0b1000 1, 2, 0], + case 0x04: // 0b0100 1, 2, 0], + p[1] = a; + p[2] = b; + p[0] = c; + break; + case 0x01: // 0b0001 2, 0, 1], + p[2] = a; + p[0] = b; + p[1] = c; + break; + case 0x00: // 0b0000 0, 2, 1], + p[0] = a; + p[2] = b; + p[1] = c; + break; + case 0x05: // 0b0101 1, 0, 2], + p[1] = a; + p[0] = b; + p[2] = c; + break; + case 0x02: // 0b0010 0, 1, 2], + case 0x0A: // 0b1010 0, 1, 2], + p[0] = a; + p[1] = b; + p[2] = c; + break; + default: + FURI_LOG_E(TAG, "Order FAIL"); + return false; + } + + data = order << 4 | invert; + int k = 0; + for(int i = 6; i >= 0; i -= 2) { + roll_array[k++] = (data >> i) & 0x03; + if(roll_array[k] == 3) { + FURI_LOG_E(TAG, "Roll_Array FAIL"); + return false; + } + } + + for(int i = 8; i >= 0; i -= 2) { + roll_array[k++] = (p[2] >> i) & 0x03; + if(roll_array[k] == 3) { + FURI_LOG_E(TAG, "Roll_Array FAIL"); + return false; + } + } + + fixed[0] = p[0] << 10 | p[1]; + return true; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param packet_1 first part of the message + */ +static void + subghz_protocol_secplus_v2_remote_controller(SubGhzBlockGeneric* instance, uint64_t packet_1) { + uint32_t fixed_1[1]; + uint8_t roll_1[9] = {0}; + uint32_t fixed_2[1]; + uint8_t roll_2[9] = {0}; + uint8_t rolling_digits[18] = {0}; + + if(subghz_protocol_secplus_v2_decode_half(packet_1, roll_1, fixed_1) && + subghz_protocol_secplus_v2_decode_half(instance->data, roll_2, fixed_2)) { + rolling_digits[0] = roll_2[8]; + rolling_digits[1] = roll_1[8]; + + rolling_digits[2] = roll_2[4]; + rolling_digits[3] = roll_2[5]; + rolling_digits[4] = roll_2[6]; + rolling_digits[5] = roll_2[7]; + + rolling_digits[6] = roll_1[4]; + rolling_digits[7] = roll_1[5]; + rolling_digits[8] = roll_1[6]; + rolling_digits[9] = roll_1[7]; + + rolling_digits[10] = roll_2[0]; + rolling_digits[11] = roll_2[1]; + rolling_digits[12] = roll_2[2]; + rolling_digits[13] = roll_2[3]; + + rolling_digits[14] = roll_1[0]; + rolling_digits[15] = roll_1[1]; + rolling_digits[16] = roll_1[2]; + rolling_digits[17] = roll_1[3]; + + uint32_t rolling = 0; + for(int i = 0; i < 18; i++) { + rolling = (rolling * 3) + rolling_digits[i]; + } + // Max value = 2^28 (268435456) + if(rolling >= 0x10000000) { + FURI_LOG_E(TAG, "Rolling FAIL"); + instance->cnt = 0; + instance->btn = 0; + instance->serial = 0; + } else { + instance->cnt = subghz_protocol_blocks_reverse_key(rolling, 28); + instance->btn = fixed_1[0] >> 12; + instance->serial = fixed_1[0] << 20 | fixed_2[0]; + } + } else { + instance->cnt = 0; + instance->btn = 0; + instance->serial = 0; + } +} + +uint8_t subghz_protocol_decoder_secplus_v2_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v2* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +bool subghz_protocol_decoder_secplus_v2_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v2* instance = context; + bool res = + subghz_block_generic_serialize(&instance->generic, flipper_format, frequency, preset); + + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->secplus_packet_1 >> i * 8) & 0xFF; + } + + if(res && + !flipper_format_write_hex(flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Secplus_packet_1"); + res = false; + } + return res; +} + +bool subghz_protocol_decoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v2* instance = context; + bool res = false; + do { + if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex( + flipper_format, "Secplus_packet_1", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Secplus_packet_1"); + break; + } + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->secplus_packet_1 = instance->secplus_packet_1 << 8 | key_data[i]; + } + res = true; + } while(false); + + return res; +} + +void subghz_protocol_decoder_secplus_v2_get_string(void* context, string_t output) { + furi_assert(context); + SubGhzProtocolDecoderSecPlus_v2* instance = context; + subghz_protocol_secplus_v2_remote_controller(&instance->generic, instance->secplus_packet_1); + + string_cat_printf( + output, + "%s %db\r\n" + "Pk1:0x%lX%08lX\r\n" + "Pk2:0x%lX%08lX\r\n" + "Sn:0x%08lX Btn:0x%01X\r\n" + "Cnt:0x%03X\r\n", + + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->secplus_packet_1 >> 32), + (uint32_t)instance->secplus_packet_1, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)instance->generic.data, + instance->generic.serial, + instance->generic.btn, + instance->generic.cnt); +} diff --git a/lib/subghz/protocols/secplus_v2.h b/lib/subghz/protocols/secplus_v2.h new file mode 100644 index 000000000..dbb1e1bd2 --- /dev/null +++ b/lib/subghz/protocols/secplus_v2.h @@ -0,0 +1,74 @@ +#pragma once +#include "base.h" + +#define SUBGHZ_PROTOCOL_SECPLUS_V2_NAME "Security+ 2.0" + +typedef struct SubGhzProtocolDecoderSecPlus_v2 SubGhzProtocolDecoderSecPlus_v2; +typedef struct SubGhzProtocolEncoderSecPlus_v2 SubGhzProtocolEncoderSecPlus_v2; + +extern const SubGhzProtocolDecoder subghz_protocol_secplus_v2_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_secplus_v2_encoder; +extern const SubGhzProtocol subghz_protocol_secplus_v2; + +/** + * Allocate SubGhzProtocolDecoderSecPlus_v2. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderSecPlus_v2* pointer to a SubGhzProtocolDecoderSecPlus_v2 instance + */ +void* subghz_protocol_decoder_secplus_v2_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderSecPlus_v2. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance + */ +void subghz_protocol_decoder_secplus_v2_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderSecPlus_v2. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance + */ +void subghz_protocol_decoder_secplus_v2_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_secplus_v2_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_secplus_v2_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderSecPlus_v2. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param frequency The frequency at which the signal was received, Hz + * @param preset The modulation on which the signal was received, FuriHalSubGhzPreset + * @return true On success + */ +bool subghz_protocol_decoder_secplus_v2_serialize( + void* context, + FlipperFormat* flipper_format, + uint32_t frequency, + FuriHalSubGhzPreset preset); + +/** + * Deserialize data SubGhzProtocolDecoderSecPlus_v2. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +bool subghz_protocol_decoder_secplus_v2_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_secplus_v2_get_string(void* context, string_t output);