view_info.c 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. /* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
  2. * See the LICENSE file for information about the license. */
  3. #include "app.h"
  4. #include <gui/view_i.h>
  5. #include <lib/toolbox/random_name.h>
  6. enum {
  7. SubViewInfoMain,
  8. SubViewInfoSave,
  9. SubViewInfoLast, /* Just a sentinel. */
  10. };
  11. /* This is the context we pass to the data yield callback for
  12. * asynchronous tx. */
  13. #define SENDSIGNAL_CURPOS_START_GAP UINT32_MAX-1
  14. #define SENDSIGNAL_CURPOS_END_GAP UINT32_MAX-2
  15. typedef struct {
  16. uint32_t curpos; // Current bit position of data to send
  17. ProtoViewApp *app; // App reference.
  18. uint32_t start_gap_dur;
  19. uint32_t end_gap_dur;
  20. } SendSignalState;
  21. /* Our view private data. */
  22. #define SAVE_FILENAME_LEN 64
  23. typedef struct {
  24. /* Our save view displays an oscilloscope-alike resampled signal,
  25. * so that the user can see what they are saving. With left/right
  26. * you can move to next rows. Here we store where we are. */
  27. uint32_t signal_display_start_row;
  28. char *filename;
  29. } InfoViewPrivData;
  30. /* Render the view with the detected message information. */
  31. static void render_subview_main(Canvas *const canvas, ProtoViewApp *app) {
  32. /* Protocol name as title. */
  33. canvas_set_font(canvas, FontPrimary);
  34. uint8_t y = 8, lineheight = 10;
  35. canvas_draw_str(canvas, 0, y, app->msg_info->name);
  36. y += lineheight;
  37. /* Info fields. */
  38. char buf[128];
  39. canvas_set_font(canvas, FontSecondary);
  40. if (app->msg_info->raw[0]) {
  41. snprintf(buf,sizeof(buf),"Raw: %s", app->msg_info->raw);
  42. canvas_draw_str(canvas, 0, y, buf);
  43. y += lineheight;
  44. }
  45. canvas_draw_str(canvas, 0, y, app->msg_info->info1); y += lineheight;
  46. canvas_draw_str(canvas, 0, y, app->msg_info->info2); y += lineheight;
  47. canvas_draw_str(canvas, 0, y, app->msg_info->info3); y += lineheight;
  48. canvas_draw_str(canvas, 0, y, app->msg_info->info4); y += lineheight;
  49. y = 37;
  50. lineheight = 7;
  51. canvas_draw_str(canvas, 119, y, "s"); y += lineheight;
  52. canvas_draw_str(canvas, 119, y, "a"); y += lineheight;
  53. canvas_draw_str(canvas, 119, y, "v"); y += lineheight;
  54. canvas_draw_str(canvas, 119, y, "e"); y += lineheight;
  55. }
  56. /* Render view with save option. */
  57. static void render_subview_save(Canvas *const canvas, ProtoViewApp *app) {
  58. InfoViewPrivData *privdata = app->view_privdata;
  59. /* Display our signal in digital form: here we don't show the
  60. * signal with the exact timing of the received samples, but as it
  61. * is in its logic form, in exact multiples of the short pulse length. */
  62. uint8_t rows = 6;
  63. uint8_t rowheight = 11;
  64. uint8_t bitwidth = 4;
  65. uint8_t bitheight = 5;
  66. uint32_t idx = privdata->signal_display_start_row * (128/4);
  67. bool prevbit = false;
  68. for (uint8_t y = bitheight+12; y <= rows*rowheight; y += rowheight) {
  69. for (uint8_t x = 0; x < 128; x += 4) {
  70. bool bit = bitmap_get(app->msg_info->bits,
  71. app->msg_info->bits_bytes,idx);
  72. uint8_t prevy = y + prevbit*(bitheight*-1) - 1;
  73. uint8_t thisy = y + bit*(bitheight*-1) - 1;
  74. canvas_draw_line(canvas,x,prevy,x,thisy);
  75. canvas_draw_line(canvas,x,thisy,x+bitwidth-1,thisy);
  76. prevbit = bit;
  77. if (idx >= app->msg_info->pulses_count) {
  78. canvas_set_color(canvas, ColorWhite);
  79. canvas_draw_dot(canvas, x+1,thisy);
  80. canvas_draw_dot(canvas, x+3,thisy);
  81. canvas_set_color(canvas, ColorBlack);
  82. }
  83. idx++; // Draw next bit
  84. }
  85. }
  86. canvas_set_font(canvas, FontSecondary);
  87. canvas_draw_str(canvas, 0, 6, "ok: send, long ok: save");
  88. }
  89. /* Render the selected subview of this view. */
  90. void render_view_info(Canvas *const canvas, ProtoViewApp *app) {
  91. if (app->signal_decoded == false) {
  92. canvas_set_font(canvas, FontSecondary);
  93. canvas_draw_str(canvas, 30,36,"No signal decoded");
  94. return;
  95. }
  96. show_available_subviews(canvas,app,SubViewInfoLast);
  97. switch(app->current_subview[app->current_view]) {
  98. case SubViewInfoMain: render_subview_main(canvas,app); break;
  99. case SubViewInfoSave: render_subview_save(canvas,app); break;
  100. }
  101. }
  102. /* The user typed the file name. Let's save it and remove the keyboard
  103. * view. */
  104. void text_input_done_callback(void* context) {
  105. ProtoViewApp *app = context;
  106. InfoViewPrivData *privdata = app->view_privdata;
  107. FuriString *save_path = furi_string_alloc_printf(
  108. "%s/%s.sub", EXT_PATH("subghz"), privdata->filename);
  109. save_signal(app, furi_string_get_cstr(save_path));
  110. furi_string_free(save_path);
  111. free(privdata->filename);
  112. dismiss_keyboard(app);
  113. }
  114. /* Replace all the occurrences of character c1 with c2 in the specified
  115. * string. */
  116. void str_replace(char *buf, char c1, char c2) {
  117. char *p = buf;
  118. while(*p) {
  119. if (*p == c1) *p = c2;
  120. p++;
  121. }
  122. }
  123. /* Set a random filename the user can edit. */
  124. void set_signal_random_filename(ProtoViewApp *app, char *buf, size_t buflen) {
  125. char suffix[6];
  126. set_random_name(suffix,sizeof(suffix));
  127. snprintf(buf,buflen,"%.10s-%s-%d",app->msg_info->name,suffix,rand()%1000);
  128. str_replace(buf,' ','_');
  129. str_replace(buf,'-','_');
  130. str_replace(buf,'/','_');
  131. }
  132. /* Send signal data feeder callback. When the asynchronous transmission is
  133. * active, this function is called to return new samples as LevelDuration
  134. * types (that is a structure with level, that is pulse or gap, and
  135. * duration in microseconds). The position into the transmission is stored
  136. * in the context 'ctx':
  137. *
  138. * In the SendSignalState structure 'ss' we remember at which bit of the
  139. * message we are in ss->curoff, however this offset has two special
  140. * values to indicate we need to send the initial and final gap.
  141. */
  142. LevelDuration radio_tx_feed_data(void *ctx) {
  143. SendSignalState *ss = ctx;
  144. uint32_t dur = 0, j;
  145. uint32_t level = 0;
  146. if (ss->start_gap_dur) {
  147. LevelDuration ld = level_duration_make(0,ss->start_gap_dur);
  148. ss->start_gap_dur = 0;
  149. ss->curpos = 0;
  150. return ld;
  151. }
  152. if (ss->curpos >= ss->app->msg_info->pulses_count) {
  153. if (ss->end_gap_dur) {
  154. LevelDuration ld = level_duration_make(0,ss->end_gap_dur);
  155. ss->end_gap_dur = 0;
  156. return ld;
  157. } else {
  158. return level_duration_reset();
  159. }
  160. }
  161. for (j = 0; ss->curpos+j < ss->app->msg_info->pulses_count; j++) {
  162. uint32_t l = bitmap_get(ss->app->msg_info->bits,
  163. ss->app->msg_info->bits_bytes,
  164. ss->curpos+j);
  165. if (j == 0) {
  166. level = l;
  167. dur += ss->app->msg_info->short_pulse_dur;
  168. continue;
  169. }
  170. if (l != level) break;
  171. dur += ss->app->msg_info->short_pulse_dur;
  172. }
  173. ss->curpos += j;
  174. return level_duration_make(level, dur);
  175. }
  176. /* Handle input for the info view. */
  177. void process_input_info(ProtoViewApp *app, InputEvent input) {
  178. if (process_subview_updown(app,input,SubViewInfoLast)) return;
  179. InfoViewPrivData *privdata = app->view_privdata;
  180. int subview = get_current_subview(app);
  181. /* Main subview. */
  182. if (subview == SubViewInfoMain) {
  183. if (input.type == InputTypeShort && input.key == InputKeyOk) {
  184. /* Reset the current sample to capture the next. */
  185. reset_current_signal(app);
  186. }
  187. } else if (subview == SubViewInfoSave) {
  188. /* Save subview. */
  189. if (input.type == InputTypePress && input.key == InputKeyRight) {
  190. privdata->signal_display_start_row++;
  191. } else if (input.type == InputTypePress && input.key == InputKeyLeft) {
  192. if (privdata->signal_display_start_row != 0)
  193. privdata->signal_display_start_row--;
  194. } else if (input.type == InputTypeLong && input.key == InputKeyOk)
  195. {
  196. privdata->filename = malloc(SAVE_FILENAME_LEN);
  197. set_signal_random_filename(app,privdata->filename,SAVE_FILENAME_LEN);
  198. show_keyboard(app, privdata->filename, SAVE_FILENAME_LEN,
  199. text_input_done_callback);
  200. } else if (input.type == InputTypeShort && input.key == InputKeyOk) {
  201. SendSignalState send_state;
  202. send_state.curpos = SENDSIGNAL_CURPOS_START_GAP;
  203. send_state.app = app;
  204. send_state.start_gap_dur = 10000;
  205. send_state.end_gap_dur = 10000;
  206. radio_tx_signal(app,radio_tx_feed_data,&send_state);
  207. }
  208. }
  209. }