Skip to content

Commit

Permalink
Message builder: User input progresses.
Browse files Browse the repository at this point in the history
  • Loading branch information
antirez committed Jan 22, 2023
1 parent baecc6c commit e38ad0f
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 69 deletions.
1 change: 1 addition & 0 deletions app.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ static void app_switch_view(ProtoViewApp *app, ProtoViewCurrentView switchto) {
if (old == ViewDirectSampling) view_exit_direct_sampling(app);
if (new == ViewDirectSampling) view_enter_direct_sampling(app);
if (old == ViewBuildMessage) view_exit_build_message(app);
if (old == ViewInfo) view_exit_info(app);
/* The frequency/modulation settings are actually a single view:
* as long as the user stays between the two modes of this view we
* don't need to call the exit-view callback. */
Expand Down
3 changes: 3 additions & 0 deletions app.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ void view_exit_build_message(ProtoViewApp *app);
void view_enter_direct_sampling(ProtoViewApp *app);
void view_exit_direct_sampling(ProtoViewApp *app);
void view_exit_settings(ProtoViewApp *app);
void view_exit_info(ProtoViewApp *app);
void adjust_raw_view_scale(ProtoViewApp *app, uint32_t short_pulse_dur);

/* ui.c */
Expand All @@ -307,6 +308,8 @@ void fieldset_add_str(ProtoViewFieldSet *fs, const char *name, const char *s);
void fieldset_add_bytes(ProtoViewFieldSet *fs, const char *name, const uint8_t *bytes, uint32_t count);
void fieldset_add_float(ProtoViewFieldSet *fs, const char *name, float val, uint32_t digits_after_dot);
const char *field_get_type_name(ProtoViewField *f);
int field_to_string(char *buf, size_t len, ProtoViewField *f);
bool field_set_from_string(ProtoViewField *f, char *buf, size_t len);

/* crc.c */
uint8_t crc8(const uint8_t *data, size_t len, uint8_t init, uint8_t poly);
124 changes: 124 additions & 0 deletions fields.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,130 @@ const char *field_get_type_name(ProtoViewField *f) {
return "unknown";
}

/* Set a string representation of the specified field in buf. */
int field_to_string(char *buf, size_t len, ProtoViewField *f) {
switch(f->type) {
case FieldTypeStr:
return snprintf(buf,len,"%s", f->str);
case FieldTypeSignedInt:
return snprintf(buf,len,"%lld", (long long) f->value);
case FieldTypeUnsignedInt:
return snprintf(buf,len,"%llu", (unsigned long long) f->uvalue);
case FieldTypeBinary:
{
uint64_t test_bit = (1 << (f->len-1));
uint64_t idx = 0;
while(idx < len-1 && test_bit) {
buf[idx++] = (f->uvalue & test_bit) ? '1' : '0';
test_bit >>= 1;
}
buf[idx] = 0;
return idx;
}
case FieldTypeHex:
return snprintf(buf, len, "%*llX", (int)(f->len+7)/8, f->uvalue);
case FieldTypeFloat:
return snprintf(buf, len, "%.*f", (int)f->len, (double)f->fvalue);
case FieldTypeBytes:
{
uint64_t idx = 0;
uint32_t nibble_num = 0;
while(idx < len-1 && nibble_num < f->len) {
const char *charset = "0123456789ABCDEF";
uint32_t nibble = nibble_num & 1 ?
(f->bytes[nibble_num/2] >> 4) :
(f->bytes[nibble_num/2] & 0xf);
buf[idx++] = charset[nibble];
nibble_num++;
}
buf[idx] = 0;
return idx;
}
}
return 0;
}

/* Set the field value from its string representation in 'buf'.
* The field type must already be set and the field should be valid.
* The string represenation 'buf' must be null termianted. Note that
* even when representing binary values containing zero, this values
* are taken as representations, so that would be the string "00" as
* the Bytes type representation.
*
* The function returns true if the filed was successfully set to the
* new value, otherwise if the specified value is invalid for the
* field type, false is returned. */
bool field_set_from_string(ProtoViewField *f, char *buf, size_t len) {
long long val;
unsigned long long uval;
float fval;

switch(f->type) {
case FieldTypeStr:
free(f->str);
f->len = len;
f->str = malloc(len+1);
memcpy(f->str,buf,len+1);
break;
case FieldTypeSignedInt:
if (!sscanf(buf,"%lld",&val)) return false;
f->value = val;
break;
case FieldTypeUnsignedInt:
if (!sscanf(buf,"%llu",&uval)) return false;
f->uvalue = uval;
break;
case FieldTypeBinary:
{
uint64_t bit_to_set = (1 << (len-1));
uint64_t idx = 0;
uval = 0;
while(buf[idx]) {
if (buf[idx] == '1') uval |= bit_to_set;
else if (buf[idx] != '0') return false;
bit_to_set >>= 1;
idx++;
}
}
break;
case FieldTypeHex:
if (!sscanf(buf,"%llx",&uval) &&
!sscanf(buf,"%llX",&uval)) return false;
f->uvalue = uval;
break;
case FieldTypeFloat:
if (!sscanf(buf,"%f",&fval)) return false;
f->fvalue = fval;
break;
case FieldTypeBytes:
{
if (len > f->len) return false;
uint64_t idx = 0;
uint64_t nibble_idx = len-1;
while(buf[idx]) {
uint8_t nibble = 0;
char c = toupper(buf[idx]);
if (c >= '0' && c <= '9') nibble = c-'0';
else if (c >= 'A' && c <= 'F') nibble = c-'A';
else return false;

if (nibble_idx & 1) {
f->bytes[idx/2] =
(f->bytes[idx/2] & 0x0F) | (nibble<<4);
} else {
f->bytes[idx/2] =
(f->bytes[idx/2] & 0xF0) | nibble;
}
nibble_idx--;
idx++;
}
buf[idx] = 0;
}
break;
}
return true;
}

/* Free a field set and its contained fields. */
void fieldset_free(ProtoViewFieldSet *fs) {
for (uint32_t j = 0; j < fs->numfields; j++)
Expand Down
76 changes: 55 additions & 21 deletions view_build.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@
extern ProtoViewDecoder *Decoders[]; // Defined in signal.c.

/* Our view private data. */
#define USER_VALUE_LEN 64
typedef struct {
ProtoViewDecoder *decoder; // Decoder we are using to create a message.
uint32_t cur_decoder; // Decoder index when we are yet selecting
// a decoder. Used when decoder is NULL.
ProtoViewFieldSet *fieldset; // The fields to populate.
uint32_t cur_field; // Field we are editing right now. This
// is the index inside the 'fieldset'
// fields.
ProtoViewDecoder *decoder; /* Decoder we are using to create a
message. */
uint32_t cur_decoder; /* Decoder index when we are yet selecting
a decoder. Used when decoder is NULL. */
ProtoViewFieldSet *fieldset; /* The fields to populate. */
uint32_t cur_field; /* Field we are editing right now. This
is the index inside the 'fieldset'
fields. */
char *user_value; /* Keyboard input to replace the current
field value goes here. */
} BuildViewPrivData;

/* Not all the decoders support message bulding, so we can't just
Expand Down Expand Up @@ -44,7 +48,7 @@ static void select_prev_decoder(ProtoViewApp *app) {
static void render_view_select_decoder(Canvas *const canvas, ProtoViewApp *app) {
BuildViewPrivData *privdata = app->view_privdata;
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 0, 9, "Signal builder");
canvas_draw_str(canvas, 0, 9, "Signal creator");
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 0, 19, "up/down: select, ok: choose");

Expand All @@ -54,9 +58,7 @@ static void render_view_select_decoder(Canvas *const canvas, ProtoViewApp *app)
select_next_decoder(app);

canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 0, 9, "Signal builder");

canvas_draw_str_aligned(canvas,64,36,AlignCenter,AlignCenter,
canvas_draw_str_aligned(canvas,64,38,AlignCenter,AlignCenter,
Decoders[privdata->cur_decoder]->name);
}

Expand All @@ -68,26 +70,32 @@ static void render_view_set_fields(Canvas *const canvas, ProtoViewApp *app) {
snprintf(buf,sizeof(buf), "%s field %d/%d",
privdata->decoder->name, (int)privdata->cur_field+1,
(int)privdata->fieldset->numfields);
canvas_set_color(canvas,ColorBlack);
canvas_draw_box(canvas,0,0,128,21);
canvas_set_color(canvas,ColorWhite);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 0, 9, buf);
canvas_draw_str(canvas, 1, 9, buf);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 0, 19, "up/down: next field, ok: edit");
canvas_draw_str(canvas, 0, 62, "Long ok: create, < > incr/decr");
canvas_draw_str(canvas, 1, 19, "up/down: next field, ok: edit");

/* Write the field name, type, current content. For this part we
* write white text on black screen, to visually separate the UI
* description part from the UI current field editing part. */
/* Write the field name, type, current content. */
canvas_set_color(canvas,ColorBlack);
canvas_draw_box(canvas,0,21,128,32);

canvas_set_color(canvas,ColorWhite);
ProtoViewField *field = privdata->fieldset->fields[privdata->cur_field];
snprintf(buf,sizeof(buf), "%s %s:%d", field->name,
field_get_type_name(field), (int)field->len);
buf[0] = toupper(buf[0]);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas,64,30,AlignCenter,AlignCenter,buf);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_aligned(canvas,63,45,AlignCenter,AlignCenter,"\"foobar\"");

/* Render the current value between "" */
unsigned int written = (unsigned int) field_to_string(buf+1,sizeof(buf)-1,field);
buf[0] = '"';
if (written+3 < sizeof(buf)) memcpy(buf+written+1,"\"\x00",2);
canvas_draw_str_aligned(canvas,63,45,AlignCenter,AlignCenter,buf);

/* Footer instructions. */
canvas_draw_str(canvas, 0, 62, "Long ok: create, < > incr/decr");
}

/* Render the build message view. */
Expand Down Expand Up @@ -122,12 +130,37 @@ static void process_input_select_decoder(ProtoViewApp *app, InputEvent input) {
}
}

/* Called after the user typed the new field value in the keyboard.
* Let's save it and remove the keyboard view. */
static void text_input_done_callback(void* context) {
ProtoViewApp *app = context;
BuildViewPrivData *privdata = app->view_privdata;

if (field_set_from_string(privdata->fieldset->fields[privdata->cur_field],
privdata->user_value, strlen(privdata->user_value)) == false)
{
ui_show_alert(app, "Invalid value", 1500);
}

free(privdata->user_value);
privdata->user_value = NULL;
ui_dismiss_keyboard(app);
}

/* Handle input for fields editing mode. */
static void process_input_set_fields(ProtoViewApp *app, InputEvent input) {
BuildViewPrivData *privdata = app->view_privdata;
ProtoViewFieldSet *fs = privdata->fieldset;
if (input.type == InputTypeShort) {
if (input.key == InputKeyOk) {
/* Show the keyboard to let the user type the new
* value. */
if (privdata->user_value == NULL)
privdata->user_value = malloc(USER_VALUE_LEN);
field_to_string(privdata->user_value, USER_VALUE_LEN,
fs->fields[privdata->cur_field]);
ui_show_keyboard(app, privdata->user_value, USER_VALUE_LEN,
text_input_done_callback);
} else if (input.key == InputKeyDown) {
privdata->cur_field = (privdata->cur_field+1) % fs->numfields;
} else if (input.key == InputKeyUp) {
Expand All @@ -152,4 +185,5 @@ void process_input_build_message(ProtoViewApp *app, InputEvent input) {
void view_exit_build_message(ProtoViewApp *app) {
BuildViewPrivData *privdata = app->view_privdata;
if (privdata->fieldset) fieldset_free(privdata->fieldset);
if (privdata->user_value) free(privdata->user_value);
}
66 changes: 18 additions & 48 deletions view_info.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,53 +31,11 @@ static void render_info_field(Canvas *const canvas,
ProtoViewField *f, uint8_t x, uint8_t y)
{
char buf[64];
char strval[32];

field_to_string(strval,sizeof(strval),f);
snprintf(buf,sizeof(buf),"%s: %s", f->name, strval);
canvas_set_font(canvas, FontSecondary);
switch(f->type) {
case FieldTypeStr:
snprintf(buf,sizeof(buf),"%s: %s", f->name, f->str);
break;
case FieldTypeSignedInt:
snprintf(buf,sizeof(buf),"%s: %lld", f->name, (long long) f->value);
break;
case FieldTypeUnsignedInt:
snprintf(buf,sizeof(buf),"%s: %llu", f->name,
(unsigned long long) f->uvalue);
break;
case FieldTypeBinary:
{
uint64_t test_bit = (1 << (f->len-1));
uint64_t idx = snprintf(buf,sizeof(buf),"%s: ", f->name);
while(idx < sizeof(buf)-1 && test_bit) {
buf[idx++] = (f->uvalue & test_bit) ? '1' : '0';
test_bit >>= 1;
}
buf[idx] = 0;
}
break;
case FieldTypeHex:
snprintf(buf, sizeof(buf), "%s: 0x%*llX", f->name,
(int)(f->len+7)/8, f->uvalue);
break;
case FieldTypeFloat:
snprintf(buf, sizeof(buf), "%s: 0x%.*f", f->name,
(int)f->len, (double)f->fvalue);
break;
case FieldTypeBytes:
{
uint64_t idx = snprintf(buf,sizeof(buf),"%s: ", f->name);
uint32_t nibble_num = 0;
while(idx < sizeof(buf)-1 && nibble_num < f->len) {
const char *charset = "0123456789ABCDEF";
uint32_t nibble = nibble_num & 1 ?
(f->bytes[nibble_num/2] >> 4) :
(f->bytes[nibble_num/2] & 0xf);
buf[idx++] = charset[nibble];
nibble_num++;
}
buf[idx] = 0;
}
break;
}
canvas_draw_str(canvas, x, y, buf);
}

Expand Down Expand Up @@ -176,7 +134,7 @@ void render_view_info(Canvas *const canvas, ProtoViewApp *app) {

/* The user typed the file name. Let's save it and remove the keyboard
* view. */
void text_input_done_callback(void* context) {
static void text_input_done_callback(void* context) {
ProtoViewApp *app = context;
InfoViewPrivData *privdata = app->view_privdata;

Expand All @@ -186,6 +144,7 @@ void text_input_done_callback(void* context) {
furi_string_free(save_path);

free(privdata->filename);
privdata->filename = NULL; // Don't free it again on view exit
ui_dismiss_keyboard(app);
ui_show_alert(app, "Signal saved", 1500);
}
Expand Down Expand Up @@ -351,7 +310,10 @@ void process_input_info(ProtoViewApp *app, InputEvent input) {
privdata->signal_display_start_row--;
} else if (input.type == InputTypeLong && input.key == InputKeyOk)
{
privdata->filename = malloc(SAVE_FILENAME_LEN);
// We have have the buffer already allocated, in case the
// user aborted with BACK a previous saving.
if (privdata->filename == NULL)
privdata->filename = malloc(SAVE_FILENAME_LEN);
set_signal_random_filename(app,privdata->filename,SAVE_FILENAME_LEN);
ui_show_keyboard(app, privdata->filename, SAVE_FILENAME_LEN,
text_input_done_callback);
Expand All @@ -363,3 +325,11 @@ void process_input_info(ProtoViewApp *app, InputEvent input) {
}
}
}

/* Called on view exit. */
void view_exit_info(ProtoViewApp *app) {
InfoViewPrivData *privdata = app->view_privdata;
// When the user aborts the keyboard input, we are left with the
// filename buffer allocated.
if (privdata->filename) free(privdata->filename);
}

0 comments on commit e38ad0f

Please sign in to comment.