ir_scope.c 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. // Author: github.com/kallanreed
  2. #include <furi.h>
  3. #include <furi_hal.h>
  4. #include <infrared.h>
  5. #include <infrared_worker.h>
  6. #include <furi_hal_infrared.h>
  7. #include <gui/gui.h>
  8. #define TAG "IR Scope"
  9. #define COLS 128
  10. #define ROWS 8
  11. typedef struct {
  12. bool autoscale;
  13. uint16_t us_per_sample;
  14. size_t timings_cnt;
  15. uint32_t* timings;
  16. uint32_t timings_sum;
  17. FuriMutex* mutex;
  18. } IRScopeState;
  19. static void state_set_autoscale(IRScopeState* state) {
  20. if(state->autoscale) state->us_per_sample = state->timings_sum / (ROWS * COLS);
  21. }
  22. static void canvas_draw_str_outline(Canvas* canvas, int x, int y, const char* str) {
  23. canvas_set_color(canvas, ColorWhite);
  24. for(int y1 = -1; y1 <= 1; ++y1)
  25. for(int x1 = -1; x1 <= 1; ++x1)
  26. canvas_draw_str(canvas, x + x1, y + y1, str);
  27. canvas_set_color(canvas, ColorBlack);
  28. canvas_draw_str(canvas, x, y, str);
  29. }
  30. static void render_callback(Canvas* canvas, void* ctx) {
  31. const IRScopeState* state = (IRScopeState*)ctx;
  32. furi_mutex_acquire(state->mutex, FuriWaitForever);
  33. canvas_clear(canvas);
  34. canvas_draw_frame(canvas, 0, 0, 128, 64);
  35. // Draw the signal chart.
  36. bool on = false;
  37. bool done = false;
  38. size_t ix = 0;
  39. int timing_cols = -1; // Count of columns used to draw the current timing
  40. for(size_t row = 0; row < ROWS && !done; ++row) {
  41. for(size_t col = 0; col < COLS && !done; ++col) {
  42. done = ix >= state->timings_cnt;
  43. if(!done && timing_cols < 0) {
  44. timing_cols = state->timings[ix] / state->us_per_sample;
  45. on = !on;
  46. }
  47. if(timing_cols == 0) ++ix;
  48. int y = row * 8 + 7;
  49. canvas_draw_line(canvas, col, y, col, y - (on ? 5 : 0));
  50. --timing_cols;
  51. }
  52. }
  53. canvas_set_font(canvas, FontSecondary);
  54. if(state->autoscale)
  55. canvas_draw_str_outline(canvas, 100, 64, "Auto");
  56. else {
  57. char buf[20];
  58. snprintf(buf, sizeof(buf), "%uus", state->us_per_sample);
  59. canvas_draw_str_outline(canvas, 100, 64, buf);
  60. }
  61. furi_mutex_release(state->mutex);
  62. }
  63. static void input_callback(InputEvent* input_event, void* ctx) {
  64. FuriMessageQueue* event_queue = ctx;
  65. furi_message_queue_put(event_queue, input_event, FuriWaitForever);
  66. }
  67. static void ir_received_callback(void* ctx, InfraredWorkerSignal* signal) {
  68. furi_check(signal);
  69. IRScopeState* state = (IRScopeState*)ctx;
  70. furi_mutex_acquire(state->mutex, FuriWaitForever);
  71. const uint32_t* timings;
  72. infrared_worker_get_raw_signal(signal, &timings, &state->timings_cnt);
  73. if(state->timings) {
  74. free(state->timings);
  75. state->timings_sum = 0;
  76. }
  77. state->timings = malloc(state->timings_cnt * sizeof(uint32_t));
  78. // Copy and sum.
  79. for(size_t i = 0; i < state->timings_cnt; ++i) {
  80. state->timings[i] = timings[i];
  81. state->timings_sum += timings[i];
  82. }
  83. state_set_autoscale(state);
  84. furi_mutex_release(state->mutex);
  85. }
  86. int32_t ir_scope_app(void* p) {
  87. UNUSED(p);
  88. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
  89. furi_check(event_queue);
  90. if(furi_hal_infrared_is_busy()) {
  91. FURI_LOG_E(TAG, "Infrared is busy.");
  92. return -1;
  93. }
  94. IRScopeState state = {
  95. .autoscale = false, .us_per_sample = 200, .timings = NULL, .timings_cnt = 0, .mutex = NULL};
  96. state.mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  97. if(!state.mutex) {
  98. FURI_LOG_E(TAG, "Cannot create mutex.");
  99. return -1;
  100. }
  101. ViewPort* view_port = view_port_alloc();
  102. view_port_draw_callback_set(view_port, render_callback, &state);
  103. view_port_input_callback_set(view_port, input_callback, event_queue);
  104. Gui* gui = furi_record_open("gui");
  105. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  106. InfraredWorker* worker = infrared_worker_alloc();
  107. infrared_worker_rx_enable_signal_decoding(worker, false);
  108. infrared_worker_rx_enable_blink_on_receiving(worker, true);
  109. infrared_worker_rx_set_received_signal_callback(worker, ir_received_callback, &state);
  110. infrared_worker_rx_start(worker);
  111. InputEvent event;
  112. bool processing = true;
  113. while(processing) {
  114. if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) {
  115. if(event.type == InputTypeRelease) {
  116. furi_mutex_acquire(state.mutex, FuriWaitForever);
  117. if(event.key == InputKeyBack) {
  118. processing = false;
  119. } else if(event.key == InputKeyUp) {
  120. state.us_per_sample = MIN(1000, state.us_per_sample + 25);
  121. state.autoscale = false;
  122. } else if(event.key == InputKeyDown) {
  123. state.us_per_sample = MAX(25, state.us_per_sample - 25);
  124. state.autoscale = false;
  125. } else if(event.key == InputKeyOk) {
  126. state.autoscale = !state.autoscale;
  127. if(state.autoscale)
  128. state_set_autoscale(&state);
  129. else
  130. state.us_per_sample = 200;
  131. }
  132. furi_mutex_release(state.mutex);
  133. }
  134. }
  135. view_port_update(view_port);
  136. }
  137. // Clean up.
  138. infrared_worker_rx_stop(worker);
  139. infrared_worker_free(worker);
  140. if(state.timings) free(state.timings);
  141. view_port_enabled_set(view_port, false);
  142. gui_remove_view_port(gui, view_port);
  143. furi_record_close("gui");
  144. view_port_free(view_port);
  145. furi_message_queue_free(event_queue);
  146. furi_mutex_free(state.mutex);
  147. return 0;
  148. }