* fbt, faploader: minimal app module implementation * faploader, libs: moved API hashtable core to flipper_application * example: compound api * lib: flipper_application: naming fixes, doxygen comments * fbt: changed `requires` manifest field behavior for app extensions * examples: refactored plugin apps; faploader: changed new API naming; fbt: changed PLUGIN app type meaning * loader: dropped support for debug apps & plugin menus * moved applications/plugins -> applications/external * Restored x bit on chiplist_convert.py * git: fixed free-dap submodule path * pvs: updated submodule paths * examples: example_advanced_plugins.c: removed potential memory leak on errors * examples: example_plugins: refined requires * fbt: not deploying app modules for debug/sample apps; extra validation for .PLUGIN-type apps * apps: removed cdefines for external apps * fbt: moved ext app path definition * fbt: reworked fap_dist handling; f18: synced api_symbols.csv * fbt: removed resources_paths for extapps * scripts: reworked storage * scripts: reworked runfap.py & selfupdate.py to use new api * wip: fal runner * fbt: moved file packaging into separate module * scripts: storage: fixes * scripts: storage: minor fixes for new api * fbt: changed internal artifact storage details for external apps * scripts: storage: additional fixes and better error reporting; examples: using APP_DATA_PATH() * fbt, scripts: reworked launch_app to deploy plugins; moved old runfap.py to distfap.py * fbt: extra check for plugins descriptors * fbt: additional checks in emitter * fbt: better info message on SDK rebuild * scripts: removed requirements.txt * loader: removed remnants of plugins & debug menus * post-review fixes
		
			
				
	
	
		
			311 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			311 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
#include "../signal_gen_app_i.h"
 | 
						|
#include <furi_hal.h>
 | 
						|
#include <gui/elements.h>
 | 
						|
#include <signal_generator_icons.h>
 | 
						|
 | 
						|
typedef enum {
 | 
						|
    LineIndexChannel,
 | 
						|
    LineIndexFrequency,
 | 
						|
    LineIndexDuty,
 | 
						|
    LineIndexTotalCount
 | 
						|
} LineIndex;
 | 
						|
 | 
						|
static const char* const pwm_ch_names[] = {"2(A7)", "4(A4)"};
 | 
						|
 | 
						|
struct SignalGenPwm {
 | 
						|
    View* view;
 | 
						|
    SignalGenPwmViewCallback callback;
 | 
						|
    void* context;
 | 
						|
};
 | 
						|
 | 
						|
typedef struct {
 | 
						|
    LineIndex line_sel;
 | 
						|
    bool edit_mode;
 | 
						|
    uint8_t edit_digit;
 | 
						|
 | 
						|
    uint8_t channel_id;
 | 
						|
    uint32_t freq;
 | 
						|
    uint8_t duty;
 | 
						|
 | 
						|
} SignalGenPwmViewModel;
 | 
						|
 | 
						|
#define ITEM_H 64 / 3
 | 
						|
#define ITEM_W 128
 | 
						|
 | 
						|
#define VALUE_X 100
 | 
						|
#define VALUE_W 45
 | 
						|
 | 
						|
#define FREQ_VALUE_X 62
 | 
						|
#define FREQ_MAX 1000000UL
 | 
						|
#define FREQ_DIGITS_NB 7
 | 
						|
 | 
						|
static void pwm_set_config(SignalGenPwm* pwm) {
 | 
						|
    FuriHalPwmOutputId channel;
 | 
						|
    uint32_t freq;
 | 
						|
    uint8_t duty;
 | 
						|
 | 
						|
    with_view_model(
 | 
						|
        pwm->view,
 | 
						|
        SignalGenPwmViewModel * model,
 | 
						|
        {
 | 
						|
            channel = model->channel_id;
 | 
						|
            freq = model->freq;
 | 
						|
            duty = model->duty;
 | 
						|
        },
 | 
						|
        false);
 | 
						|
 | 
						|
    furi_assert(pwm->callback);
 | 
						|
    pwm->callback(channel, freq, duty, pwm->context);
 | 
						|
}
 | 
						|
 | 
						|
static void pwm_channel_change(SignalGenPwmViewModel* model, InputEvent* event) {
 | 
						|
    if(event->key == InputKeyLeft) {
 | 
						|
        if(model->channel_id > 0) {
 | 
						|
            model->channel_id--;
 | 
						|
        }
 | 
						|
    } else if(event->key == InputKeyRight) {
 | 
						|
        if(model->channel_id < (COUNT_OF(pwm_ch_names) - 1)) {
 | 
						|
            model->channel_id++;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static void pwm_duty_change(SignalGenPwmViewModel* model, InputEvent* event) {
 | 
						|
    if(event->key == InputKeyLeft) {
 | 
						|
        if(model->duty > 0) {
 | 
						|
            model->duty--;
 | 
						|
        }
 | 
						|
    } else if(event->key == InputKeyRight) {
 | 
						|
        if(model->duty < 100) {
 | 
						|
            model->duty++;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static bool pwm_freq_edit(SignalGenPwmViewModel* model, InputEvent* event) {
 | 
						|
    bool consumed = false;
 | 
						|
    if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) {
 | 
						|
        if(event->key == InputKeyRight) {
 | 
						|
            if(model->edit_digit > 0) {
 | 
						|
                model->edit_digit--;
 | 
						|
            }
 | 
						|
            consumed = true;
 | 
						|
        } else if(event->key == InputKeyLeft) {
 | 
						|
            if(model->edit_digit < (FREQ_DIGITS_NB - 1)) {
 | 
						|
                model->edit_digit++;
 | 
						|
            }
 | 
						|
            consumed = true;
 | 
						|
        } else if(event->key == InputKeyUp) {
 | 
						|
            uint32_t step = 1;
 | 
						|
            for(uint8_t i = 0; i < model->edit_digit; i++) {
 | 
						|
                step *= 10;
 | 
						|
            }
 | 
						|
            if((model->freq + step) < FREQ_MAX) {
 | 
						|
                model->freq += step;
 | 
						|
            } else {
 | 
						|
                model->freq = FREQ_MAX;
 | 
						|
            }
 | 
						|
            consumed = true;
 | 
						|
        } else if(event->key == InputKeyDown) {
 | 
						|
            uint32_t step = 1;
 | 
						|
            for(uint8_t i = 0; i < model->edit_digit; i++) {
 | 
						|
                step *= 10;
 | 
						|
            }
 | 
						|
            if(model->freq > (step + 1)) {
 | 
						|
                model->freq -= step;
 | 
						|
            } else {
 | 
						|
                model->freq = 1;
 | 
						|
            }
 | 
						|
            consumed = true;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return consumed;
 | 
						|
}
 | 
						|
 | 
						|
static void signal_gen_pwm_draw_callback(Canvas* canvas, void* _model) {
 | 
						|
    SignalGenPwmViewModel* model = _model;
 | 
						|
    char* line_label = NULL;
 | 
						|
    char val_text[16];
 | 
						|
 | 
						|
    for(size_t line = 0; line < LineIndexTotalCount; line++) {
 | 
						|
        if(line == LineIndexChannel) {
 | 
						|
            line_label = "GPIO Pin";
 | 
						|
        } else if(line == LineIndexFrequency) {
 | 
						|
            line_label = "Frequency";
 | 
						|
        } else if(line == LineIndexDuty) { //-V547
 | 
						|
            line_label = "Pulse width";
 | 
						|
        }
 | 
						|
 | 
						|
        canvas_set_color(canvas, ColorBlack);
 | 
						|
        if(line == model->line_sel) {
 | 
						|
            elements_slightly_rounded_box(canvas, 0, ITEM_H * line + 1, ITEM_W, ITEM_H - 1);
 | 
						|
            canvas_set_color(canvas, ColorWhite);
 | 
						|
        }
 | 
						|
 | 
						|
        uint8_t text_y = ITEM_H * line + ITEM_H / 2 + 2;
 | 
						|
 | 
						|
        canvas_draw_str_aligned(canvas, 6, text_y, AlignLeft, AlignCenter, line_label);
 | 
						|
 | 
						|
        if(line == LineIndexChannel) {
 | 
						|
            snprintf(val_text, sizeof(val_text), "%s", pwm_ch_names[model->channel_id]);
 | 
						|
            canvas_draw_str_aligned(canvas, VALUE_X, text_y, AlignCenter, AlignCenter, val_text);
 | 
						|
            if(model->channel_id != 0) {
 | 
						|
                canvas_draw_str_aligned(
 | 
						|
                    canvas, VALUE_X - VALUE_W / 2, text_y, AlignCenter, AlignCenter, "<");
 | 
						|
            }
 | 
						|
            if(model->channel_id != (COUNT_OF(pwm_ch_names) - 1)) {
 | 
						|
                canvas_draw_str_aligned(
 | 
						|
                    canvas, VALUE_X + VALUE_W / 2, text_y, AlignCenter, AlignCenter, ">");
 | 
						|
            }
 | 
						|
        } else if(line == LineIndexFrequency) {
 | 
						|
            snprintf(val_text, sizeof(val_text), "%7lu Hz", model->freq);
 | 
						|
            canvas_set_font(canvas, FontKeyboard);
 | 
						|
            canvas_draw_str_aligned(
 | 
						|
                canvas, FREQ_VALUE_X, text_y, AlignLeft, AlignCenter, val_text);
 | 
						|
            canvas_set_font(canvas, FontSecondary);
 | 
						|
 | 
						|
            if(model->edit_mode) {
 | 
						|
                uint8_t icon_x = (FREQ_VALUE_X) + (FREQ_DIGITS_NB - model->edit_digit - 1) * 6;
 | 
						|
                canvas_draw_icon(canvas, icon_x, text_y - 9, &I_SmallArrowUp_3x5);
 | 
						|
                canvas_draw_icon(canvas, icon_x, text_y + 5, &I_SmallArrowDown_3x5);
 | 
						|
            }
 | 
						|
        } else if(line == LineIndexDuty) { //-V547
 | 
						|
            snprintf(val_text, sizeof(val_text), "%d%%", model->duty);
 | 
						|
            canvas_draw_str_aligned(canvas, VALUE_X, text_y, AlignCenter, AlignCenter, val_text);
 | 
						|
            if(model->duty != 0) {
 | 
						|
                canvas_draw_str_aligned(
 | 
						|
                    canvas, VALUE_X - VALUE_W / 2, text_y, AlignCenter, AlignCenter, "<");
 | 
						|
            }
 | 
						|
            if(model->duty != 100) {
 | 
						|
                canvas_draw_str_aligned(
 | 
						|
                    canvas, VALUE_X + VALUE_W / 2, text_y, AlignCenter, AlignCenter, ">");
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static bool signal_gen_pwm_input_callback(InputEvent* event, void* context) {
 | 
						|
    furi_assert(context);
 | 
						|
    SignalGenPwm* pwm = context;
 | 
						|
    bool consumed = false;
 | 
						|
    bool need_update = false;
 | 
						|
 | 
						|
    with_view_model(
 | 
						|
        pwm->view,
 | 
						|
        SignalGenPwmViewModel * model,
 | 
						|
        {
 | 
						|
            if(model->edit_mode == false) {
 | 
						|
                if((event->type == InputTypeShort) || (event->type == InputTypeRepeat)) {
 | 
						|
                    if(event->key == InputKeyUp) {
 | 
						|
                        if(model->line_sel == 0) {
 | 
						|
                            model->line_sel = LineIndexTotalCount - 1;
 | 
						|
                        } else {
 | 
						|
                            model->line_sel =
 | 
						|
                                CLAMP(model->line_sel - 1, LineIndexTotalCount - 1, 0);
 | 
						|
                        }
 | 
						|
                        consumed = true;
 | 
						|
                    } else if(event->key == InputKeyDown) {
 | 
						|
                        if(model->line_sel == LineIndexTotalCount - 1) {
 | 
						|
                            model->line_sel = 0;
 | 
						|
                        } else {
 | 
						|
                            model->line_sel =
 | 
						|
                                CLAMP(model->line_sel + 1, LineIndexTotalCount - 1, 0);
 | 
						|
                        }
 | 
						|
                        consumed = true;
 | 
						|
                    } else if((event->key == InputKeyLeft) || (event->key == InputKeyRight)) {
 | 
						|
                        if(model->line_sel == LineIndexChannel) {
 | 
						|
                            pwm_channel_change(model, event);
 | 
						|
                            need_update = true;
 | 
						|
                        } else if(model->line_sel == LineIndexDuty) {
 | 
						|
                            pwm_duty_change(model, event);
 | 
						|
                            need_update = true;
 | 
						|
                        } else if(model->line_sel == LineIndexFrequency) {
 | 
						|
                            model->edit_mode = true;
 | 
						|
                        }
 | 
						|
                        consumed = true;
 | 
						|
                    } else if(event->key == InputKeyOk) {
 | 
						|
                        if(model->line_sel == LineIndexFrequency) {
 | 
						|
                            model->edit_mode = true;
 | 
						|
                        }
 | 
						|
                        consumed = true;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            } else {
 | 
						|
                if((event->key == InputKeyOk) || (event->key == InputKeyBack)) {
 | 
						|
                    if(event->type == InputTypeShort) {
 | 
						|
                        model->edit_mode = false;
 | 
						|
                        consumed = true;
 | 
						|
                    }
 | 
						|
                } else {
 | 
						|
                    if(model->line_sel == LineIndexFrequency) {
 | 
						|
                        consumed = pwm_freq_edit(model, event);
 | 
						|
                        need_update = consumed;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        },
 | 
						|
        true);
 | 
						|
 | 
						|
    if(need_update) {
 | 
						|
        pwm_set_config(pwm);
 | 
						|
    }
 | 
						|
 | 
						|
    return consumed;
 | 
						|
}
 | 
						|
 | 
						|
SignalGenPwm* signal_gen_pwm_alloc() {
 | 
						|
    SignalGenPwm* pwm = malloc(sizeof(SignalGenPwm));
 | 
						|
 | 
						|
    pwm->view = view_alloc();
 | 
						|
    view_allocate_model(pwm->view, ViewModelTypeLocking, sizeof(SignalGenPwmViewModel));
 | 
						|
    view_set_context(pwm->view, pwm);
 | 
						|
    view_set_draw_callback(pwm->view, signal_gen_pwm_draw_callback);
 | 
						|
    view_set_input_callback(pwm->view, signal_gen_pwm_input_callback);
 | 
						|
 | 
						|
    return pwm;
 | 
						|
}
 | 
						|
 | 
						|
void signal_gen_pwm_free(SignalGenPwm* pwm) {
 | 
						|
    furi_assert(pwm);
 | 
						|
    view_free(pwm->view);
 | 
						|
    free(pwm);
 | 
						|
}
 | 
						|
 | 
						|
View* signal_gen_pwm_get_view(SignalGenPwm* pwm) {
 | 
						|
    furi_assert(pwm);
 | 
						|
    return pwm->view;
 | 
						|
}
 | 
						|
 | 
						|
void signal_gen_pwm_set_callback(
 | 
						|
    SignalGenPwm* pwm,
 | 
						|
    SignalGenPwmViewCallback callback,
 | 
						|
    void* context) {
 | 
						|
    furi_assert(pwm);
 | 
						|
    furi_assert(callback);
 | 
						|
 | 
						|
    with_view_model(
 | 
						|
        pwm->view,
 | 
						|
        SignalGenPwmViewModel * model,
 | 
						|
        {
 | 
						|
            UNUSED(model);
 | 
						|
            pwm->callback = callback;
 | 
						|
            pwm->context = context;
 | 
						|
        },
 | 
						|
        false);
 | 
						|
}
 | 
						|
 | 
						|
void signal_gen_pwm_set_params(SignalGenPwm* pwm, uint8_t channel_id, uint32_t freq, uint8_t duty) {
 | 
						|
    with_view_model(
 | 
						|
        pwm->view,
 | 
						|
        SignalGenPwmViewModel * model,
 | 
						|
        {
 | 
						|
            model->channel_id = channel_id;
 | 
						|
            model->freq = freq;
 | 
						|
            model->duty = duty;
 | 
						|
        },
 | 
						|
        true);
 | 
						|
 | 
						|
    furi_assert(pwm->callback);
 | 
						|
    pwm->callback(channel_id, freq, duty, pwm->context);
 | 
						|
}
 |