mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 04:34:43 +04:00
cli: Buzzer command (#4006)
* Add args_read_float_and_trim function * Add args_read_duration function * Add notes_frequency_from_name function * Add cli_sleep function and sleep CLI command * Update CLI top command to use cli_sleep * Add buzzer CLI command * toolbox: make args_read_duration less convoluted * notification: make notification_messages_notes_frequency_from_name less convoluted * unit_tests: better float checking * fix formatting and f18 --------- Co-authored-by: Anna Antonenko <portasynthinca3@gmail.com> Co-authored-by: hedger <hedger@nanode.su>
This commit is contained in:
@@ -49,3 +49,11 @@ App(
|
||||
requires=["cli"],
|
||||
sources=["commands/subshell_demo.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="cli_buzzer",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="cli_buzzer_ep",
|
||||
requires=["cli"],
|
||||
sources=["commands/buzzer.c"],
|
||||
)
|
||||
|
||||
@@ -356,11 +356,13 @@ void cli_command_led(PipeSide* pipe, FuriString* args, void* context) {
|
||||
static void cli_command_top(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
int interval = 1000;
|
||||
args_read_int_and_trim(args, &interval);
|
||||
uint32_t interval;
|
||||
if(!args_read_duration(args, &interval, NULL)) {
|
||||
interval = 1000;
|
||||
}
|
||||
|
||||
FuriThreadList* thread_list = furi_thread_list_alloc();
|
||||
while(!cli_is_pipe_broken_or_is_etx_next_char(pipe)) {
|
||||
do {
|
||||
uint32_t tick = furi_get_tick();
|
||||
furi_thread_enumerate(thread_list);
|
||||
|
||||
@@ -416,12 +418,8 @@ static void cli_command_top(PipeSide* pipe, FuriString* args, void* context) {
|
||||
printf(ANSI_ERASE_DISPLAY(ANSI_ERASE_FROM_CURSOR_TO_END));
|
||||
fflush(stdout);
|
||||
|
||||
if(interval > 0) {
|
||||
furi_delay_ms(interval);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while(interval > 0 && cli_sleep(pipe, interval));
|
||||
|
||||
furi_thread_list_free(thread_list);
|
||||
}
|
||||
|
||||
@@ -491,6 +489,37 @@ void cli_command_echo(PipeSide* pipe, FuriString* args, void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Pause for a specified duration or until Ctrl+C is pressed or the
|
||||
* session is terminated.
|
||||
*
|
||||
* The duration can be specified in various units such as milliseconds (ms),
|
||||
* seconds (s), minutes (m), or hours (h). If the unit is not specified, the
|
||||
* second is used by default.
|
||||
*
|
||||
* Example:
|
||||
* sleep 5s
|
||||
*/
|
||||
void cli_command_sleep(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
FuriString* duration_string;
|
||||
duration_string = furi_string_alloc();
|
||||
|
||||
do {
|
||||
uint32_t duration_in_ms = 0;
|
||||
if(!args_read_string_and_trim(args, duration_string) ||
|
||||
!args_read_duration(duration_string, &duration_in_ms, "s")) {
|
||||
cli_print_usage("sleep", "[<0-...>[<ms|s|m|h>]]", furi_string_get_cstr(args));
|
||||
break;
|
||||
}
|
||||
|
||||
cli_sleep(pipe, duration_in_ms);
|
||||
|
||||
} while(false);
|
||||
|
||||
furi_string_free(duration_string);
|
||||
}
|
||||
|
||||
void cli_main_commands_init(CliRegistry* registry) {
|
||||
cli_registry_add_command(
|
||||
registry, "!", CliCommandFlagParallelSafe, cli_command_info, (void*)true);
|
||||
@@ -508,6 +537,8 @@ void cli_main_commands_init(CliRegistry* registry) {
|
||||
cli_registry_add_command(
|
||||
registry, "free_blocks", CliCommandFlagParallelSafe, cli_command_free_blocks, NULL);
|
||||
cli_registry_add_command(registry, "echo", CliCommandFlagParallelSafe, cli_command_echo, NULL);
|
||||
cli_registry_add_command(
|
||||
registry, "sleep", CliCommandFlagParallelSafe, cli_command_sleep, NULL);
|
||||
|
||||
cli_registry_add_command(registry, "vibro", CliCommandFlagDefault, cli_command_vibro, NULL);
|
||||
cli_registry_add_command(registry, "led", CliCommandFlagDefault, cli_command_led, NULL);
|
||||
|
||||
135
applications/services/cli/commands/buzzer.c
Normal file
135
applications/services/cli/commands/buzzer.c
Normal file
@@ -0,0 +1,135 @@
|
||||
#include "../cli_main_commands.h"
|
||||
#include <notification/notification_app.h>
|
||||
#include <toolbox/args.h>
|
||||
#include <toolbox/cli/cli_command.h>
|
||||
|
||||
void cli_command_buzzer_print_usage(bool is_freq_subcommand, FuriString* args) {
|
||||
if(is_freq_subcommand) {
|
||||
cli_print_usage(
|
||||
"buzzer freq", "<freq_hz> [<0-...>[<ms|s|m|h>]]", furi_string_get_cstr(args));
|
||||
|
||||
} else {
|
||||
cli_print_usage("buzzer note", "<note> [<0-...>[<ms|s|m|h>]]", furi_string_get_cstr(args));
|
||||
}
|
||||
}
|
||||
|
||||
float cli_command_buzzer_read_frequency(bool is_freq_subcommand, FuriString* args) {
|
||||
float frequency = 0.0f;
|
||||
|
||||
if(is_freq_subcommand) {
|
||||
args_read_float_and_trim(args, &frequency);
|
||||
return frequency;
|
||||
}
|
||||
|
||||
// Extract note frequency from name
|
||||
|
||||
FuriString* note_name_string;
|
||||
note_name_string = furi_string_alloc();
|
||||
|
||||
do {
|
||||
if(!args_read_string_and_trim(args, note_name_string)) {
|
||||
break;
|
||||
}
|
||||
const char* note_name = furi_string_get_cstr(note_name_string);
|
||||
frequency = notification_messages_notes_frequency_from_name(note_name);
|
||||
} while(false);
|
||||
|
||||
furi_string_free(note_name_string);
|
||||
|
||||
return frequency;
|
||||
}
|
||||
|
||||
void cli_command_buzzer_play(
|
||||
PipeSide* pipe,
|
||||
NotificationApp* notification,
|
||||
bool is_freq_subcommand,
|
||||
FuriString* args) {
|
||||
FuriString* duration_string;
|
||||
duration_string = furi_string_alloc();
|
||||
|
||||
do {
|
||||
float frequency = cli_command_buzzer_read_frequency(is_freq_subcommand, args);
|
||||
if(frequency <= 0.0f) {
|
||||
cli_command_buzzer_print_usage(is_freq_subcommand, args);
|
||||
break;
|
||||
}
|
||||
|
||||
const NotificationMessage notification_buzzer_message = {
|
||||
.type = NotificationMessageTypeSoundOn,
|
||||
.data.sound.frequency = frequency,
|
||||
.data.sound.volume = 1.0,
|
||||
};
|
||||
|
||||
// Optional duration
|
||||
uint32_t duration_ms = 100;
|
||||
if(args_read_string_and_trim(args, duration_string)) {
|
||||
if(!args_read_duration(duration_string, &duration_ms, NULL)) {
|
||||
cli_command_buzzer_print_usage(is_freq_subcommand, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const NotificationSequence sound_on_sequence = {
|
||||
¬ification_buzzer_message,
|
||||
&message_do_not_reset,
|
||||
NULL,
|
||||
};
|
||||
|
||||
// Play sound
|
||||
notification_message_block(notification, &sound_on_sequence);
|
||||
|
||||
cli_sleep(pipe, duration_ms);
|
||||
|
||||
// Stop sound
|
||||
const NotificationSequence sound_off_sequence = {
|
||||
&message_sound_off,
|
||||
NULL,
|
||||
};
|
||||
notification_message_block(notification, &sound_off_sequence);
|
||||
|
||||
} while(false);
|
||||
|
||||
furi_string_free(duration_string);
|
||||
}
|
||||
|
||||
void execute(PipeSide* pipe, FuriString* args, void* context) {
|
||||
UNUSED(context);
|
||||
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
FuriString* command_string;
|
||||
command_string = furi_string_alloc();
|
||||
|
||||
do {
|
||||
if(!args_read_string_and_trim(args, command_string)) {
|
||||
cli_print_usage("buzzer", "<freq|note>", furi_string_get_cstr(args));
|
||||
break;
|
||||
}
|
||||
|
||||
// Check volume
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode)) {
|
||||
printf("Flipper is in stealth mode. Unmute the device to control buzzer.");
|
||||
break;
|
||||
}
|
||||
if(notification->settings.speaker_volume == 0.0f) {
|
||||
printf("Sound is disabled in settings. Increase volume to control buzzer.");
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp(command_string, "freq") == 0) {
|
||||
cli_command_buzzer_play(pipe, notification, true, args);
|
||||
break;
|
||||
} else if(furi_string_cmp(command_string, "note") == 0) {
|
||||
cli_command_buzzer_play(pipe, notification, false, args);
|
||||
break;
|
||||
}
|
||||
|
||||
cli_print_usage("buzzer", "<freq|note>", furi_string_get_cstr(args));
|
||||
|
||||
} while(false);
|
||||
|
||||
furi_string_free(command_string);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
}
|
||||
|
||||
CLI_COMMAND_INTERFACE(buzzer, execute, CliCommandFlagDefault, 2048, CLI_APPID);
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "notification.h"
|
||||
#include <toolbox/strint.h>
|
||||
|
||||
/*
|
||||
Python script for note messages generation
|
||||
@@ -571,3 +572,35 @@ const NotificationMessage message_note_b8 = {
|
||||
.data.sound.frequency = 7902.13f,
|
||||
.data.sound.volume = 1.0f,
|
||||
};
|
||||
|
||||
float notification_messages_notes_frequency_from_name(const char* note_name) {
|
||||
const float base_note = 16.3515979f; // C0
|
||||
|
||||
const char* note_names[] = {"c", "cs", "d", "ds", "e", "f", "fs", "g", "gs", "a", "as", "b"};
|
||||
const size_t notes_count = COUNT_OF(note_names);
|
||||
|
||||
char note_wo_octave[3] = {0};
|
||||
for(size_t i = 0; i < sizeof(note_wo_octave) - 1; i++) {
|
||||
char in = *note_name;
|
||||
if(!in) break;
|
||||
if(!isalpha(in)) break;
|
||||
note_wo_octave[i] = in;
|
||||
note_name++;
|
||||
}
|
||||
|
||||
int note_index = -1;
|
||||
for(size_t i = 0; i < notes_count; i++) {
|
||||
if(strcasecmp(note_wo_octave, note_names[i]) == 0) note_index = i;
|
||||
}
|
||||
if(note_index < 0) return 0.0;
|
||||
|
||||
uint16_t octave;
|
||||
StrintParseError error = strint_to_uint16(note_name, NULL, &octave, 10);
|
||||
if(error != StrintParseNoError) return 0.0;
|
||||
if(octave > 8) return 0.0;
|
||||
|
||||
int semitone_index = octave * notes_count + note_index;
|
||||
float frequency = base_note * powf(2.0f, semitone_index / 12.0f);
|
||||
|
||||
return roundf(frequency * 100) / 100.0f;
|
||||
}
|
||||
|
||||
@@ -115,6 +115,17 @@ extern const NotificationMessage message_note_a8;
|
||||
extern const NotificationMessage message_note_as8;
|
||||
extern const NotificationMessage message_note_b8;
|
||||
|
||||
/**
|
||||
* @brief Returns the frequency of the given note
|
||||
*
|
||||
* This function calculates and returns the frequency (in Hz) of the specified note.
|
||||
* If the input note name is invalid, the function returns 0.0.
|
||||
*
|
||||
* @param [in] note_name The name of the note (e.g., "A4", cs5")
|
||||
* @return The frequency of the note in Hz, or 0.0 if the note name is invalid
|
||||
*/
|
||||
extern float notification_messages_notes_frequency_from_name(const char* note_name);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user