view_info.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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. /* This view has subviews accessible navigating up/down. This
  7. * enumaration is used to track the currently active subview. */
  8. enum {
  9. SubViewInfoMain,
  10. SubViewInfoSave,
  11. SubViewInfoLast, /* Just a sentinel. */
  12. };
  13. /* Our view private data. */
  14. #define SAVE_FILENAME_LEN 64
  15. typedef struct {
  16. /* Our save view displays an oscilloscope-alike resampled signal,
  17. * so that the user can see what they are saving. With left/right
  18. * you can move to next rows. Here we store where we are. */
  19. uint32_t signal_display_start_row;
  20. char *filename;
  21. } InfoViewPrivData;
  22. /* Render the view with the detected message information. */
  23. static void render_subview_main(Canvas *const canvas, ProtoViewApp *app) {
  24. /* Protocol name as title. */
  25. canvas_set_font(canvas, FontPrimary);
  26. uint8_t y = 8, lineheight = 10;
  27. canvas_draw_str(canvas, 0, y, app->msg_info->name);
  28. y += lineheight;
  29. /* Info fields. */
  30. char buf[128];
  31. canvas_set_font(canvas, FontSecondary);
  32. if (app->msg_info->raw[0]) {
  33. snprintf(buf,sizeof(buf),"Raw: %s", app->msg_info->raw);
  34. canvas_draw_str(canvas, 0, y, buf);
  35. y += lineheight;
  36. }
  37. canvas_draw_str(canvas, 0, y, app->msg_info->info1); y += lineheight;
  38. canvas_draw_str(canvas, 0, y, app->msg_info->info2); y += lineheight;
  39. canvas_draw_str(canvas, 0, y, app->msg_info->info3); y += lineheight;
  40. canvas_draw_str(canvas, 0, y, app->msg_info->info4); y += lineheight;
  41. y = 37;
  42. lineheight = 7;
  43. canvas_draw_str(canvas, 119, y, "s"); y += lineheight;
  44. canvas_draw_str(canvas, 119, y, "a"); y += lineheight;
  45. canvas_draw_str(canvas, 119, y, "v"); y += lineheight;
  46. canvas_draw_str(canvas, 119, y, "e"); y += lineheight;
  47. }
  48. /* Render view with save option. */
  49. static void render_subview_save(Canvas *const canvas, ProtoViewApp *app) {
  50. InfoViewPrivData *privdata = app->view_privdata;
  51. /* Display our signal in digital form: here we don't show the
  52. * signal with the exact timing of the received samples, but as it
  53. * is in its logic form, in exact multiples of the short pulse length. */
  54. uint8_t rows = 6;
  55. uint8_t rowheight = 11;
  56. uint8_t bitwidth = 4;
  57. uint8_t bitheight = 5;
  58. uint32_t idx = privdata->signal_display_start_row * (128/4);
  59. bool prevbit = false;
  60. for (uint8_t y = bitheight+12; y <= rows*rowheight; y += rowheight) {
  61. for (uint8_t x = 0; x < 128; x += 4) {
  62. bool bit = bitmap_get(app->msg_info->bits,
  63. app->msg_info->bits_bytes,idx);
  64. uint8_t prevy = y + prevbit*(bitheight*-1) - 1;
  65. uint8_t thisy = y + bit*(bitheight*-1) - 1;
  66. canvas_draw_line(canvas,x,prevy,x,thisy);
  67. canvas_draw_line(canvas,x,thisy,x+bitwidth-1,thisy);
  68. prevbit = bit;
  69. if (idx >= app->msg_info->pulses_count) {
  70. canvas_set_color(canvas, ColorWhite);
  71. canvas_draw_dot(canvas, x+1,thisy);
  72. canvas_draw_dot(canvas, x+3,thisy);
  73. canvas_set_color(canvas, ColorBlack);
  74. }
  75. idx++; // Draw next bit
  76. }
  77. }
  78. canvas_set_font(canvas, FontSecondary);
  79. canvas_draw_str(canvas, 0, 6, "ok: send, long ok: save");
  80. }
  81. /* Render the selected subview of this view. */
  82. void render_view_info(Canvas *const canvas, ProtoViewApp *app) {
  83. if (app->signal_decoded == false) {
  84. canvas_set_font(canvas, FontSecondary);
  85. canvas_draw_str(canvas, 30,36,"No signal decoded");
  86. return;
  87. }
  88. show_available_subviews(canvas,app,SubViewInfoLast);
  89. switch(app->current_subview[app->current_view]) {
  90. case SubViewInfoMain: render_subview_main(canvas,app); break;
  91. case SubViewInfoSave: render_subview_save(canvas,app); break;
  92. }
  93. }
  94. /* The user typed the file name. Let's save it and remove the keyboard
  95. * view. */
  96. void text_input_done_callback(void* context) {
  97. ProtoViewApp *app = context;
  98. InfoViewPrivData *privdata = app->view_privdata;
  99. FuriString *save_path = furi_string_alloc_printf(
  100. "%s/%s.sub", EXT_PATH("subghz"), privdata->filename);
  101. save_signal(app, furi_string_get_cstr(save_path));
  102. furi_string_free(save_path);
  103. free(privdata->filename);
  104. dismiss_keyboard(app);
  105. }
  106. /* Replace all the occurrences of character c1 with c2 in the specified
  107. * string. */
  108. void str_replace(char *buf, char c1, char c2) {
  109. char *p = buf;
  110. while(*p) {
  111. if (*p == c1) *p = c2;
  112. p++;
  113. }
  114. }
  115. /* Set a random filename the user can edit. */
  116. void set_signal_random_filename(ProtoViewApp *app, char *buf, size_t buflen) {
  117. char suffix[6];
  118. set_random_name(suffix,sizeof(suffix));
  119. snprintf(buf,buflen,"%.10s-%s-%d",app->msg_info->name,suffix,rand()%1000);
  120. str_replace(buf,' ','_');
  121. str_replace(buf,'-','_');
  122. str_replace(buf,'/','_');
  123. }
  124. /* ========================== Signal transmission =========================== */
  125. /* This is the context we pass to the data yield callback for
  126. * asynchronous tx. */
  127. typedef enum {
  128. SendSignalSendStartGap,
  129. SendSignalSendBits,
  130. SendSignalSendEndGap,
  131. SendSignalEndTransmission
  132. } SendSignalState;
  133. #define PROTOVIEW_SENDSIGNAL_START_GAP 10000 /* microseconds. */
  134. #define PROTOVIEW_SENDSIGNAL_END_GAP 10000 /* microseconds. */
  135. typedef struct {
  136. SendSignalState state; // Current state.
  137. uint32_t curpos; // Current bit position of data to send.
  138. ProtoViewApp *app; // App reference.
  139. uint32_t start_gap_dur; // Gap to send at the start.
  140. uint32_t end_gap_dur; // Gap to send at the end.
  141. } SendSignalCtx;
  142. /* Setup the state context for the callback responsible to feed data
  143. * to the subghz async tx system. */
  144. static void send_signal_init(SendSignalCtx *ss, ProtoViewApp *app) {
  145. ss->state = SendSignalSendStartGap;
  146. ss->curpos = 0;
  147. ss->app = app;
  148. ss->start_gap_dur = PROTOVIEW_SENDSIGNAL_START_GAP;
  149. ss->end_gap_dur = PROTOVIEW_SENDSIGNAL_END_GAP;
  150. }
  151. /* Send signal data feeder callback. When the asynchronous transmission is
  152. * active, this function is called to return new samples from the currently
  153. * decoded signal in app->msg_info. The subghz subsystem aspects this function,
  154. * that is the data feeder, to return LevelDuration types (that is a structure
  155. * with level, that is pulse or gap, and duration in microseconds).
  156. *
  157. * The position into the transmission is stored in the context 'ctx', that
  158. * references a SendSignalCtx structure.
  159. *
  160. * In the SendSignalCtx structure 'ss' we remember at which bit of the
  161. * message we are, in ss->curoff. We also send a start and end gap in order
  162. * to make sure the transmission is clear.
  163. */
  164. LevelDuration radio_tx_feed_data(void *ctx) {
  165. SendSignalCtx *ss = ctx;
  166. /* Send start gap. */
  167. if (ss->state == SendSignalSendStartGap) {
  168. ss->state = SendSignalSendBits;
  169. return level_duration_make(0,ss->start_gap_dur);
  170. }
  171. /* Send data. */
  172. if (ss->state == SendSignalSendBits) {
  173. uint32_t dur = 0, j;
  174. uint32_t level = 0;
  175. /* Let's see how many consecutive bits we have with the same
  176. * level. */
  177. for (j = 0; ss->curpos+j < ss->app->msg_info->pulses_count; j++) {
  178. uint32_t l = bitmap_get(ss->app->msg_info->bits,
  179. ss->app->msg_info->bits_bytes,
  180. ss->curpos+j);
  181. if (j == 0) {
  182. /* At the first bit of this sequence, we store the
  183. * level of the sequence. */
  184. level = l;
  185. dur += ss->app->msg_info->short_pulse_dur;
  186. continue;
  187. }
  188. /* As long as the level is the same, we update the duration.
  189. * Otherwise stop the loop and return this sample. */
  190. if (l != level) break;
  191. dur += ss->app->msg_info->short_pulse_dur;
  192. }
  193. ss->curpos += j;
  194. /* If this was the last set of bits, change the state to
  195. * send the final gap. */
  196. if (ss->curpos >= ss->app->msg_info->pulses_count)
  197. ss->state = SendSignalSendEndGap;
  198. return level_duration_make(level, dur);
  199. }
  200. /* Send end gap. */
  201. if (ss->state == SendSignalSendEndGap) {
  202. ss->state = SendSignalEndTransmission;
  203. return level_duration_make(0,ss->end_gap_dur);
  204. }
  205. /* End transmission. Here state is guaranteed
  206. * to be SendSignalEndTransmission */
  207. return level_duration_reset();
  208. }
  209. /* Vibrate and produce a click sound when a signal is sent. */
  210. void notify_signal_sent(ProtoViewApp *app) {
  211. static const NotificationSequence sent_seq = {
  212. &message_blue_255,
  213. &message_vibro_on,
  214. &message_note_g1,
  215. &message_delay_10,
  216. &message_sound_off,
  217. &message_vibro_off,
  218. &message_blue_0,
  219. NULL
  220. };
  221. notification_message(app->notification, &sent_seq);
  222. }
  223. /* Handle input for the info view. */
  224. void process_input_info(ProtoViewApp *app, InputEvent input) {
  225. if (process_subview_updown(app,input,SubViewInfoLast)) return;
  226. InfoViewPrivData *privdata = app->view_privdata;
  227. int subview = get_current_subview(app);
  228. /* Main subview. */
  229. if (subview == SubViewInfoMain) {
  230. if (input.type == InputTypeShort && input.key == InputKeyOk) {
  231. /* Reset the current sample to capture the next. */
  232. reset_current_signal(app);
  233. }
  234. } else if (subview == SubViewInfoSave) {
  235. /* Save subview. */
  236. if (input.type == InputTypePress && input.key == InputKeyRight) {
  237. privdata->signal_display_start_row++;
  238. } else if (input.type == InputTypePress && input.key == InputKeyLeft) {
  239. if (privdata->signal_display_start_row != 0)
  240. privdata->signal_display_start_row--;
  241. } else if (input.type == InputTypeLong && input.key == InputKeyOk)
  242. {
  243. privdata->filename = malloc(SAVE_FILENAME_LEN);
  244. set_signal_random_filename(app,privdata->filename,SAVE_FILENAME_LEN);
  245. show_keyboard(app, privdata->filename, SAVE_FILENAME_LEN,
  246. text_input_done_callback);
  247. } else if (input.type == InputTypeShort && input.key == InputKeyOk) {
  248. SendSignalCtx send_state;
  249. send_signal_init(&send_state,app);
  250. radio_tx_signal(app,radio_tx_feed_data,&send_state);
  251. notify_signal_sent(app);
  252. }
  253. }
  254. }