ir_scope.c 5.5 KB

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