rad_sens.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. #include "rad_sens.h"
  2. #include "notifications.h"
  3. #include "rad_sens_i2c.h"
  4. typedef enum {
  5. WorkerEventReserved = (1 << 0), // Reserved for StreamBuffer internal event
  6. WorkerEventStop = (1 << 1),
  7. WorkerEventTick = (1 << 2),
  8. } WorkerEventFlags;
  9. #define WORKER_EVENTS_MASK (WorkerEventStop | WorkerEventTick)
  10. static void rad_sens_view_draw_battery(Canvas* canvas, RadSensModel* model) {
  11. if(model->info.gauge_is_ok) {
  12. canvas_draw_icon(canvas, 100, 2, &I_Battery_26x8);
  13. canvas_draw_box(canvas, 102, 4, model->info.charge / 5, 4);
  14. }
  15. if(model->vibro_on) {
  16. canvas_draw_icon(canvas, 90, 2, &I_vibro_on);
  17. }
  18. }
  19. static void rad_sens_view_draw_info(Canvas* canvas, RadSensModel* model) {
  20. FuriString* dyn_intensity =
  21. furi_string_alloc_printf("%0.1f", ((double)model->dyn_intensity / 10));
  22. FuriString* stat_intensity = furi_string_alloc_printf(
  23. "Static intensity: %0.1f uR/h", ((double)model->stat_intensity / 10));
  24. FuriString* impulse_count = furi_string_alloc_printf("Impulses: %d", model->impulse_count);
  25. canvas_set_font(canvas, FontSecondary);
  26. uint8_t height = canvas_current_font_height(canvas);
  27. canvas_draw_str(canvas, 0, height, "RadSens connected");
  28. canvas_set_font(canvas, FontBigNumbers);
  29. uint8_t width_dyn = canvas_string_width(canvas, furi_string_get_cstr(dyn_intensity));
  30. uint8_t height_dyn = canvas_current_font_height(canvas);
  31. canvas_draw_str(
  32. canvas,
  33. (128 - width_dyn) / 2,
  34. (64 - height + height_dyn) / 2,
  35. furi_string_get_cstr(dyn_intensity));
  36. canvas_set_font(canvas, FontPrimary);
  37. canvas_draw_str(canvas, (128 + width_dyn) / 2 + 4, (64 - height + height_dyn) / 2, "uR/h");
  38. canvas_set_font(canvas, FontSecondary);
  39. canvas_draw_str(canvas, 0, 64 - 1 * height, furi_string_get_cstr(stat_intensity));
  40. canvas_draw_str(canvas, 0, 64 - 0 * height, furi_string_get_cstr(impulse_count));
  41. furi_string_free(dyn_intensity);
  42. furi_string_free(stat_intensity);
  43. furi_string_free(impulse_count);
  44. }
  45. static void rad_sens_view_dotted_line_v(Canvas* canvas, uint8_t x, uint8_t y1, uint8_t y2) {
  46. for(uint8_t y = MIN(y1, y2); y < MAX(y1, y2); y += 2) {
  47. canvas_draw_dot(canvas, x, y);
  48. }
  49. }
  50. static void rad_sens_view_dotted_line_h(Canvas* canvas, uint8_t x1, uint8_t x2, uint8_t y) {
  51. for(uint8_t x = MIN(x1, x2); x < MAX(x1, x2); x += 2) {
  52. canvas_draw_dot(canvas, x, y);
  53. }
  54. }
  55. static void rad_sens_view_draw_history(Canvas* canvas, RadSensModel* model) {
  56. // Get Max Intensity
  57. uint32_t max = 0;
  58. for(uint8_t i = 0; i < HISTORY_LENGTH; i++) {
  59. max = MAX(max, model->dyn_intensity_history[i]);
  60. }
  61. // Calculate Y Axis Unit
  62. uint8_t unit = 5;
  63. for(uint8_t i = 0; i < 4; i++) {
  64. if(2 * unit > max / 10) {
  65. break;
  66. }
  67. unit *= 2;
  68. }
  69. canvas_set_font(canvas, FontSecondary);
  70. // X Axis
  71. canvas_draw_line(canvas, 4, 63, 120, 63);
  72. canvas_draw_str(canvas, 124, 63, "s");
  73. // Y Axis
  74. canvas_draw_line(canvas, 4, 63, 4, 8);
  75. canvas_draw_str(canvas, 0, 7, "uR/h");
  76. // Time Marks
  77. rad_sens_view_dotted_line_v(canvas, 94, 8, 63);
  78. canvas_draw_str(canvas, 96, 62, "30");
  79. rad_sens_view_dotted_line_v(canvas, 64, 8, 63);
  80. canvas_draw_str(canvas, 66, 62, "60");
  81. rad_sens_view_dotted_line_v(canvas, 34, 8, 63);
  82. canvas_draw_str(canvas, 36, 62, "90");
  83. // Intensity Marks
  84. FuriString* unit1_mark = furi_string_alloc_printf("%d", unit);
  85. FuriString* unit2_mark = furi_string_alloc_printf("%d", unit * 2);
  86. rad_sens_view_dotted_line_h(canvas, 4, 124, 24);
  87. canvas_draw_str(canvas, 6, 23, furi_string_get_cstr(unit2_mark));
  88. rad_sens_view_dotted_line_h(canvas, 4, 124, 44);
  89. canvas_draw_str(canvas, 6, 43, furi_string_get_cstr(unit1_mark));
  90. furi_string_free(unit1_mark);
  91. furi_string_free(unit2_mark);
  92. // Plot
  93. for(uint8_t x = 0; x < HISTORY_LENGTH; x++) {
  94. uint32_t index = (model->dyn_intensity_history_index + x) % HISTORY_LENGTH;
  95. // magic 2 is 20 pixels per 1 uR/h which is 10 units from RadSens
  96. uint8_t y = 64 - (model->dyn_intensity_history[index] * 2 / unit);
  97. canvas_draw_dot(canvas, x + 4, y);
  98. }
  99. FuriString* dyn_intensity =
  100. furi_string_alloc_printf("%0.1f", ((double)model->dyn_intensity / 10));
  101. uint8_t width_dyn = canvas_string_width(canvas, furi_string_get_cstr(dyn_intensity));
  102. uint8_t y = 64 - model->dyn_intensity * 2 / unit - 2;
  103. canvas_draw_str(canvas, 124 - width_dyn, y, furi_string_get_cstr(dyn_intensity));
  104. furi_string_free(dyn_intensity);
  105. }
  106. static void rad_sens_view_draw_error(Canvas* canvas, RadSensModel* model) {
  107. canvas_set_font(canvas, FontSecondary);
  108. uint8_t height = canvas_current_font_height(canvas);
  109. canvas_draw_str(
  110. canvas,
  111. 0,
  112. (64 + height) / 2,
  113. model->connected ? "RadSens not detected" : "RadSens not connected");
  114. }
  115. static void rad_sens_view_draw_callback(Canvas* canvas, void* _model) {
  116. RadSensModel* model = _model;
  117. furi_assert(model);
  118. canvas_clear(canvas);
  119. canvas_set_color(canvas, ColorBlack);
  120. if(model->verified) {
  121. if(model->show_history) {
  122. rad_sens_view_draw_history(canvas, model);
  123. } else {
  124. rad_sens_view_draw_info(canvas, model);
  125. }
  126. } else {
  127. rad_sens_view_draw_error(canvas, model);
  128. }
  129. rad_sens_view_draw_battery(canvas, model);
  130. }
  131. static bool rad_sens_view_input_callback(InputEvent* event, void* context) {
  132. RadSensApp* app = context;
  133. furi_assert(app);
  134. bool consumed = false;
  135. if(event->type == InputTypeShort) {
  136. switch(event->key) {
  137. case InputKeyUp:
  138. consumed = true;
  139. app->model->vibro_on = !app->model->vibro_on;
  140. break;
  141. case InputKeyRight:
  142. app->model->show_history = true;
  143. break;
  144. case InputKeyLeft:
  145. app->model->show_history = false;
  146. break;
  147. default:
  148. break;
  149. }
  150. }
  151. return consumed;
  152. }
  153. static uint32_t rad_sens_exit(void* context) {
  154. UNUSED(context);
  155. return VIEW_NONE;
  156. }
  157. static void rad_sens_get_battery_info(RadSensApp* app, RadSensModel* model) {
  158. power_get_info(app->power, &model->info);
  159. }
  160. static int32_t rad_sens_worker(void* context) {
  161. furi_assert(context);
  162. RadSensApp* app = context;
  163. while(1) {
  164. uint32_t events =
  165. furi_thread_flags_wait(WORKER_EVENTS_MASK, FuriFlagWaitAny, FuriWaitForever);
  166. furi_check((events & FuriFlagError) == 0);
  167. if(events & WorkerEventStop) break;
  168. if(events & WorkerEventTick) {
  169. bool done = false;
  170. uint16_t new_impulse_count = 0;
  171. uint8_t vibro = 0;
  172. with_view_model(
  173. app->view,
  174. RadSensModel * model,
  175. {
  176. done = rad_sens_read_data(model);
  177. new_impulse_count = model->new_impulse_count > 2 ? 3 :
  178. model->new_impulse_count;
  179. vibro = model->vibro_on ? 1 : 0;
  180. if(done) {
  181. model->dyn_intensity_history[model->dyn_intensity_history_index] =
  182. model->dyn_intensity;
  183. model->dyn_intensity_history_index++;
  184. model->dyn_intensity_history_index %= HISTORY_LENGTH;
  185. }
  186. rad_sens_get_battery_info(app, model);
  187. },
  188. true);
  189. if(!done) {
  190. notification_message(app->notification, &sequence_notification_fail);
  191. } else {
  192. notification_message(
  193. app->notification, sequence_notification_imps[new_impulse_count][vibro]);
  194. }
  195. }
  196. }
  197. return 0;
  198. }
  199. static void rad_sens_timer_callback(void* context) {
  200. furi_assert(context);
  201. RadSensApp* app = context;
  202. furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventTick);
  203. }
  204. static uint32_t rad_sens_load_value() {
  205. uint32_t value = 0;
  206. bool result;
  207. Storage* api = furi_record_open(RECORD_STORAGE);
  208. File* file = storage_file_alloc(api);
  209. const char* path = ANY_PATH("rad_sens.txt");
  210. result = storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING);
  211. if(result) {
  212. uint64_t size = storage_file_size(file);
  213. char* str = malloc(sizeof(char) * (size + 1));
  214. memset(str, 0, size + 1);
  215. uint8_t bytes_count = storage_file_read(file, str, size);
  216. if(bytes_count > 0) {
  217. value = strtol(str, NULL, 10);
  218. }
  219. free(str);
  220. storage_file_close(file);
  221. }
  222. storage_file_free(file);
  223. furi_record_close(RECORD_STORAGE);
  224. return value;
  225. }
  226. static void rad_sens_save_value(uint32_t value) {
  227. bool result;
  228. Storage* api = furi_record_open(RECORD_STORAGE);
  229. File* file = storage_file_alloc(api);
  230. const char* path = ANY_PATH("rad_sens.txt");
  231. result = storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS);
  232. if(result) {
  233. FuriString* str = furi_string_alloc_printf("%ld", value);
  234. storage_file_write(file, furi_string_get_cstr(str), furi_string_size(str));
  235. furi_string_free(str);
  236. storage_file_close(file);
  237. }
  238. storage_file_free(file);
  239. furi_record_close(RECORD_STORAGE);
  240. }
  241. static RadSensApp* rad_sens_app_alloc() {
  242. RadSensApp* app = malloc(sizeof(RadSensApp));
  243. // Gui
  244. app->gui = furi_record_open(RECORD_GUI);
  245. app->notification = furi_record_open(RECORD_NOTIFICATION);
  246. app->power = furi_record_open(RECORD_POWER);
  247. // View dispatcher
  248. app->view_dispatcher = view_dispatcher_alloc();
  249. view_dispatcher_enable_queue(app->view_dispatcher);
  250. view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
  251. // Views
  252. app->view = view_alloc();
  253. view_set_context(app->view, app);
  254. view_set_draw_callback(app->view, rad_sens_view_draw_callback);
  255. view_set_input_callback(app->view, rad_sens_view_input_callback);
  256. view_allocate_model(app->view, ViewModelTypeLocking, sizeof(RadSensModel));
  257. with_view_model(
  258. app->view,
  259. RadSensModel * model,
  260. {
  261. app->model = model;
  262. model->vibro_on = false;
  263. model->connected = false;
  264. model->dyn_intensity = 0;
  265. model->stat_intensity = 0;
  266. model->new_impulse_count = 0;
  267. model->impulse_count = rad_sens_load_value();
  268. model->show_history = false;
  269. for(uint8_t i = 0; i < HISTORY_LENGTH; i++) {
  270. model->dyn_intensity_history[i] = 0;
  271. }
  272. model->dyn_intensity_history_index = 0;
  273. rad_sens_read_data(model);
  274. rad_sens_get_battery_info(app, model);
  275. },
  276. true);
  277. view_set_previous_callback(app->view, rad_sens_exit);
  278. view_dispatcher_add_view(app->view_dispatcher, 0, app->view);
  279. view_dispatcher_switch_to_view(app->view_dispatcher, 0);
  280. app->worker_thread = furi_thread_alloc_ex("RadSensWorker", 1024, rad_sens_worker, app);
  281. furi_thread_start(app->worker_thread);
  282. app->timer = furi_timer_alloc(rad_sens_timer_callback, FuriTimerTypePeriodic, app);
  283. furi_timer_start(app->timer, furi_ms_to_ticks(1000));
  284. return app;
  285. }
  286. static void rad_sens_app_free(RadSensApp* app) {
  287. furi_assert(app);
  288. furi_timer_stop(app->timer);
  289. furi_timer_free(app->timer);
  290. furi_thread_flags_set(furi_thread_get_id(app->worker_thread), WorkerEventStop);
  291. furi_thread_join(app->worker_thread);
  292. furi_thread_free(app->worker_thread);
  293. // Free views
  294. view_dispatcher_remove_view(app->view_dispatcher, 0);
  295. with_view_model(
  296. app->view,
  297. RadSensModel * model,
  298. {
  299. rad_sens_save_value(model->impulse_count);
  300. model->connected = false;
  301. model->dyn_intensity = 0;
  302. model->stat_intensity = 0;
  303. model->impulse_count = 0;
  304. },
  305. true);
  306. view_free(app->view);
  307. view_dispatcher_free(app->view_dispatcher);
  308. // Close gui record
  309. furi_record_close(RECORD_GUI);
  310. app->gui = NULL;
  311. furi_record_close(RECORD_NOTIFICATION);
  312. app->notification = NULL;
  313. furi_record_close(RECORD_POWER);
  314. app->power = NULL;
  315. // Free rest
  316. free(app);
  317. }
  318. int32_t rad_sens_app(void* p) {
  319. UNUSED(p);
  320. RadSensApp* app = rad_sens_app_alloc();
  321. view_dispatcher_run(app->view_dispatcher);
  322. rad_sens_app_free(app);
  323. return 0;
  324. }