uv_meter_data.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. #include "uv_meter_data.hpp"
  2. #include <gui/elements.h>
  3. #include "uv_meter_as7331_icons.h"
  4. #include <locale/locale.h>
  5. #include <math.h>
  6. #define UV_METER_MAX_RAW_VALUE 65535.0
  7. struct UVMeterData {
  8. View* view;
  9. AS7331* sensor;
  10. FuriMutex* sensor_mutex;
  11. UVMeterDataEnterSettingsCallback callback;
  12. void* context;
  13. };
  14. // Configuration mode states
  15. typedef enum {
  16. UVMeterConfigModeNone, // No config selected
  17. UVMeterConfigModeGain, // Configure gain
  18. UVMeterConfigModeExposureTime, // Configure exposure time
  19. UVMeterConfigModeEyesProtection, // Configure eyes protection mode
  20. } UVMeterConfigMode;
  21. typedef struct {
  22. FuriString* buffer;
  23. UVMeterConfigMode current_config_mode;
  24. AS7331::Results results;
  25. AS7331::RawResults raw_results;
  26. UVMeterEffectiveResults effective_results;
  27. int16_t gain_value;
  28. double conversion_time;
  29. bool eyes_protected;
  30. UVMeterUnit unit;
  31. } UVMeterDataModel;
  32. static void uv_meter_data_draw_raw_meter(
  33. Canvas* canvas,
  34. int x,
  35. int y,
  36. uint16_t raw_value,
  37. double max_meter_value) {
  38. int meter_value = (int)lround((double)raw_value / max_meter_value * (double)8.0);
  39. // Draw the meter frame
  40. canvas_draw_frame(canvas, x, y, 3, 10);
  41. // Draw the meter level
  42. canvas_draw_line(canvas, x + 1, y + 9, x + 1, y + 9 - meter_value);
  43. // Show alert if value is too low or at max
  44. if(raw_value < 5 || raw_value >= max_meter_value) {
  45. canvas_draw_icon(canvas, x + 5, y + 1, &I_Alert_9x8);
  46. }
  47. }
  48. static void uv_meter_data_draw_uv_measurements(
  49. Canvas* canvas,
  50. const AS7331::Results* results,
  51. const AS7331::RawResults* raw_results,
  52. UVMeterUnit unit,
  53. FuriString* buffer) {
  54. canvas_set_font(canvas, FontSecondary);
  55. int x_1_align = 0;
  56. int y_uva_bottom = 9;
  57. int y_uvb_bottom = 23;
  58. int y_uvc_bottom = 37;
  59. // Draw labels
  60. canvas_draw_str_aligned(canvas, x_1_align, y_uva_bottom, AlignLeft, AlignBottom, "UVA:");
  61. canvas_draw_str_aligned(canvas, x_1_align, y_uvb_bottom, AlignLeft, AlignBottom, "UVB:");
  62. canvas_draw_str_aligned(canvas, x_1_align, y_uvc_bottom, AlignLeft, AlignBottom, "UVC:");
  63. // Display UV measurements
  64. canvas_set_font(canvas, FontPrimary);
  65. int x_2_align = 51;
  66. double multiplier = 1.0;
  67. switch(unit) {
  68. case UVMeterUnituW_cm_2:
  69. multiplier = 1.0;
  70. break;
  71. case UVMeterUnitW_m_2:
  72. multiplier = 0.01;
  73. break;
  74. case UVMeterUnitmW_m_2:
  75. multiplier = 10;
  76. break;
  77. }
  78. // Display UVA value
  79. double uv_a = results->uv_a * multiplier;
  80. furi_string_printf(buffer, "%.*f", (uv_a >= 1000 ? 0 : (uv_a >= 100 ? 1 : 2)), uv_a);
  81. canvas_draw_str_aligned(
  82. canvas, x_2_align, y_uva_bottom, AlignRight, AlignBottom, furi_string_get_cstr(buffer));
  83. // Display UVB value
  84. double uv_b = results->uv_b * multiplier;
  85. furi_string_printf(buffer, "%.*f", (uv_b >= 1000 ? 0 : (uv_b >= 100 ? 1 : 2)), uv_b);
  86. canvas_draw_str_aligned(
  87. canvas, x_2_align, y_uvb_bottom, AlignRight, AlignBottom, furi_string_get_cstr(buffer));
  88. // Display UVC value
  89. double uv_c = results->uv_c * multiplier;
  90. furi_string_printf(buffer, "%.*f", (uv_c >= 1000 ? 0 : (uv_c >= 100 ? 1 : 2)), uv_c);
  91. canvas_draw_str_aligned(
  92. canvas, x_2_align, y_uvc_bottom, AlignRight, AlignBottom, furi_string_get_cstr(buffer));
  93. // Draw raw meters with alerts
  94. int raw_meter_x = x_2_align + 3;
  95. // UVA raw meter
  96. uv_meter_data_draw_raw_meter(
  97. canvas, raw_meter_x, y_uva_bottom - 9, raw_results->uv_a, UV_METER_MAX_RAW_VALUE);
  98. // UVB raw meter
  99. uv_meter_data_draw_raw_meter(
  100. canvas, raw_meter_x, y_uvb_bottom - 9, raw_results->uv_b, UV_METER_MAX_RAW_VALUE);
  101. // UVC raw meter
  102. uv_meter_data_draw_raw_meter(
  103. canvas, raw_meter_x, y_uvc_bottom - 9, raw_results->uv_c, UV_METER_MAX_RAW_VALUE);
  104. }
  105. static void uv_meter_data_draw_uv_effectiveness(
  106. Canvas* canvas,
  107. const UVMeterEffectiveResults* effective_results,
  108. FuriString* buffer) {
  109. canvas_set_font(canvas, FontSecondary);
  110. int x_3_align = 94;
  111. int y_uva_bottom = 9;
  112. int y_uvb_bottom = 23;
  113. int y_uvc_bottom = 37;
  114. // Skip if total is zero to avoid division by zero
  115. if(effective_results->uv_total_eff <= 0) {
  116. return;
  117. }
  118. // UVA percentage of Maximum Daily Exposure Time
  119. furi_string_printf(
  120. buffer,
  121. "%d%%",
  122. (int)lround(effective_results->uv_a_eff / effective_results->uv_total_eff * 100));
  123. canvas_draw_str_aligned(
  124. canvas, x_3_align, y_uva_bottom, AlignRight, AlignBottom, furi_string_get_cstr(buffer));
  125. // UVB percentage of Maximum Daily Exposure Time
  126. furi_string_printf(
  127. buffer,
  128. "%d%%",
  129. (int)lround(effective_results->uv_b_eff / effective_results->uv_total_eff * 100));
  130. canvas_draw_str_aligned(
  131. canvas, x_3_align, y_uvb_bottom, AlignRight, AlignBottom, furi_string_get_cstr(buffer));
  132. // UVC percentage of Maximum Daily Exposure Time
  133. furi_string_printf(
  134. buffer,
  135. "%d%%",
  136. (int)lround(effective_results->uv_c_eff / effective_results->uv_total_eff * 100));
  137. canvas_draw_str_aligned(
  138. canvas, x_3_align, y_uvc_bottom, AlignRight, AlignBottom, furi_string_get_cstr(buffer));
  139. // Draw separator line and arrow
  140. int x_3_4 = 96;
  141. canvas_draw_line(canvas, x_3_4, y_uva_bottom - 9, x_3_4, y_uvc_bottom);
  142. canvas_draw_icon(canvas, x_3_4 + 1, y_uvb_bottom - 6, &I_ButtonRightSmall_3x5);
  143. }
  144. static void uv_meter_data_draw_maximum_daily_exposure_time(
  145. Canvas* canvas,
  146. double t_max,
  147. FuriString* buffer) {
  148. int x_center_4_4 = 112;
  149. int y_uvb_bottom = 23;
  150. // Draw sun icon
  151. canvas_draw_icon(canvas, x_center_4_4 - 7, -7, &I_Sun_15x16);
  152. // Draw "min" label
  153. canvas_set_font(canvas, FontSecondary);
  154. canvas_draw_str_aligned(canvas, x_center_4_4, y_uvb_bottom + 2, AlignCenter, AlignTop, "min");
  155. // Draw max exposure time in minutes
  156. canvas_set_font(canvas, FontPrimary);
  157. double t_max_minutes = t_max / 60;
  158. furi_string_printf(
  159. buffer, "%.*f", (t_max_minutes >= 100 ? 0 : (t_max_minutes >= 10 ? 1 : 2)), t_max_minutes);
  160. canvas_draw_str_aligned(
  161. canvas, x_center_4_4, y_uvb_bottom, AlignCenter, AlignBottom, furi_string_get_cstr(buffer));
  162. }
  163. static void uv_meter_data_draw_config_section(
  164. Canvas* canvas,
  165. int16_t gain_value,
  166. double conversion_time,
  167. bool eyes_protected,
  168. UVMeterUnit unit,
  169. UVMeterConfigMode current_config_mode,
  170. FuriString* buffer) {
  171. // Draw unit
  172. const Icon* icon = NULL;
  173. switch(unit) {
  174. case UVMeterUnituW_cm_2:
  175. icon = &I_Unit_uW_cm2_34x11;
  176. break;
  177. case UVMeterUnitW_m_2:
  178. icon = &I_Unit_W_m2_22x11;
  179. break;
  180. case UVMeterUnitmW_m_2:
  181. icon = &I_Unit_mW_m2_28x11;
  182. break;
  183. }
  184. int x_1_align = 0;
  185. int y_conf = 52;
  186. canvas_draw_icon(canvas, x_1_align, y_conf - 11, icon);
  187. // Draw text explaining selected setting
  188. canvas_set_font(canvas, FontSecondary);
  189. int x_setting_right = 123;
  190. const char* setting_string;
  191. switch(current_config_mode) {
  192. case UVMeterConfigModeGain:
  193. setting_string = "Gain";
  194. break;
  195. case UVMeterConfigModeExposureTime:
  196. setting_string = "Exposure Time (s)";
  197. break;
  198. case UVMeterConfigModeEyesProtection:
  199. setting_string = "Eyes Protected";
  200. break;
  201. default:
  202. setting_string = "";
  203. break;
  204. }
  205. uint16_t text_width = canvas_string_width(canvas, setting_string);
  206. canvas_draw_str_aligned(
  207. canvas, x_setting_right, y_conf - 2, AlignRight, AlignBottom, setting_string);
  208. // Navigation arrows
  209. canvas_draw_icon(canvas, x_setting_right + 2, y_conf - 8, &I_ButtonRightSmall_3x5);
  210. canvas_draw_icon(canvas, x_setting_right - text_width - 5, y_conf - 8, &I_ButtonLeftSmall_3x5);
  211. // Frames/Boxes for settings
  212. int setting_x_size = 33;
  213. int setting_y_size = 14;
  214. // Config button box
  215. canvas_draw_rframe(canvas, 0, y_conf, setting_x_size, setting_y_size, 3);
  216. // Gain box
  217. if(current_config_mode == UVMeterConfigModeGain) {
  218. canvas_draw_rbox(canvas, setting_x_size - 1, y_conf, setting_x_size, setting_y_size, 3);
  219. } else {
  220. canvas_draw_rframe(canvas, setting_x_size - 1, y_conf, setting_x_size, setting_y_size, 3);
  221. }
  222. // Exposure Time box
  223. if(current_config_mode == UVMeterConfigModeExposureTime) {
  224. canvas_draw_rbox(
  225. canvas, setting_x_size * 2 - 2, y_conf, setting_x_size, setting_y_size, 3);
  226. } else {
  227. canvas_draw_rframe(
  228. canvas, setting_x_size * 2 - 2, y_conf, setting_x_size, setting_y_size, 3);
  229. }
  230. // Eyes Protection box
  231. if(current_config_mode == UVMeterConfigModeEyesProtection) {
  232. canvas_draw_rbox(
  233. canvas, setting_x_size * 3 - 3, y_conf, setting_x_size - 1, setting_y_size, 3);
  234. } else {
  235. canvas_draw_rframe(
  236. canvas, setting_x_size * 3 - 3, y_conf, setting_x_size - 1, setting_y_size, 3);
  237. }
  238. // Config button
  239. canvas_draw_icon(canvas, 2, y_conf + 3, &I_ButtonCenter_7x7);
  240. canvas_draw_str_aligned(canvas, 11, y_conf + 3, AlignLeft, AlignTop, "Conf");
  241. // Gain value
  242. if(current_config_mode == UVMeterConfigModeGain) {
  243. canvas_set_color(canvas, ColorWhite);
  244. }
  245. furi_string_printf(buffer, "%d", gain_value);
  246. canvas_draw_str_aligned(
  247. canvas,
  248. setting_x_size - 1 + (setting_x_size / 2),
  249. y_conf + 3,
  250. AlignCenter,
  251. AlignTop,
  252. furi_string_get_cstr(buffer));
  253. canvas_set_color(canvas, ColorBlack);
  254. // Exposure Time value
  255. if(current_config_mode == UVMeterConfigModeExposureTime) {
  256. canvas_set_color(canvas, ColorWhite);
  257. }
  258. furi_string_printf(
  259. buffer,
  260. "%.*f",
  261. (conversion_time >= 100 ? 1 : (conversion_time >= 10 ? 2 : 3)),
  262. conversion_time);
  263. canvas_draw_str_aligned(
  264. canvas,
  265. setting_x_size * 2 - 2 + (setting_x_size / 2),
  266. y_conf + 3,
  267. AlignCenter,
  268. AlignTop,
  269. furi_string_get_cstr(buffer));
  270. canvas_set_color(canvas, ColorBlack);
  271. // Eyes Protection
  272. if(current_config_mode == UVMeterConfigModeEyesProtection) {
  273. canvas_set_color(canvas, ColorWhite);
  274. }
  275. canvas_draw_icon(
  276. canvas,
  277. setting_x_size * 3 - 3 + 4,
  278. y_conf + 2,
  279. (eyes_protected) ? &I_Sunglasses_24x8 : &I_Glasses_24x8);
  280. canvas_set_color(canvas, ColorBlack);
  281. }
  282. static void uv_meter_data_draw_callback(Canvas* canvas, void* model) {
  283. auto* m = static_cast<UVMeterDataModel*>(model);
  284. FURI_LOG_D("UV_Meter Data", "Redrawing");
  285. // Draw UV measurements section
  286. uv_meter_data_draw_uv_measurements(canvas, &m->results, &m->raw_results, m->unit, m->buffer);
  287. // Draw UV effectiveness percentages
  288. uv_meter_data_draw_uv_effectiveness(canvas, &m->effective_results, m->buffer);
  289. // Draw maximum daily exposure time
  290. uv_meter_data_draw_maximum_daily_exposure_time(canvas, m->effective_results.t_max, m->buffer);
  291. // Draw configuration section
  292. uv_meter_data_draw_config_section(
  293. canvas,
  294. m->gain_value,
  295. m->conversion_time,
  296. m->eyes_protected,
  297. m->unit,
  298. m->current_config_mode,
  299. m->buffer);
  300. }
  301. static bool uv_meter_data_input_callback(InputEvent* event, void* context) {
  302. auto* instance = static_cast<UVMeterData*>(context);
  303. bool consumed = false;
  304. bool setting_changed = false;
  305. if(event->type != InputTypeShort) {
  306. return false;
  307. }
  308. if(event->key == InputKeyOk) {
  309. if(instance->callback) {
  310. instance->callback(instance->context);
  311. return true;
  312. }
  313. return false;
  314. }
  315. FURI_LOG_D("UV_Meter Data", "Update Input");
  316. with_view_model_cpp(
  317. instance->view,
  318. UVMeterDataModel*,
  319. model,
  320. {
  321. // Handle left/right to select config
  322. if(event->key == InputKeyLeft || event->key == InputKeyRight) {
  323. if(event->key == InputKeyLeft) {
  324. // Move to previous config mode
  325. switch(model->current_config_mode) {
  326. case UVMeterConfigModeNone:
  327. case UVMeterConfigModeGain:
  328. model->current_config_mode = UVMeterConfigModeEyesProtection;
  329. break;
  330. case UVMeterConfigModeExposureTime:
  331. model->current_config_mode = UVMeterConfigModeGain;
  332. break;
  333. case UVMeterConfigModeEyesProtection:
  334. model->current_config_mode = UVMeterConfigModeExposureTime;
  335. break;
  336. }
  337. } else { // InputKeyRight
  338. // Move to next config mode
  339. switch(model->current_config_mode) {
  340. case UVMeterConfigModeNone:
  341. case UVMeterConfigModeGain:
  342. model->current_config_mode = UVMeterConfigModeExposureTime;
  343. break;
  344. case UVMeterConfigModeExposureTime:
  345. model->current_config_mode = UVMeterConfigModeEyesProtection;
  346. break;
  347. case UVMeterConfigModeEyesProtection:
  348. model->current_config_mode = UVMeterConfigModeGain;
  349. break;
  350. }
  351. }
  352. consumed = true;
  353. setting_changed = true;
  354. }
  355. // Handle up/down to change selected config mode
  356. else if(
  357. instance->sensor && model->current_config_mode != UVMeterConfigModeNone &&
  358. (event->key == InputKeyUp || event->key == InputKeyDown)) {
  359. furi_mutex_acquire(instance->sensor_mutex, FuriWaitForever);
  360. switch(model->current_config_mode) {
  361. case UVMeterConfigModeGain: {
  362. // Adjust gain
  363. int current_gain = static_cast<int>(instance->sensor->getGain());
  364. int new_gain = (event->key == InputKeyUp) ? current_gain - 1 :
  365. current_gain + 1;
  366. if(new_gain >= static_cast<int>(GAIN_2048) &&
  367. new_gain <= static_cast<int>(GAIN_1)) {
  368. instance->sensor->setGain(static_cast<as7331_gain_t>(new_gain));
  369. model->gain_value = instance->sensor->getGainValue();
  370. setting_changed = true;
  371. }
  372. break;
  373. }
  374. case UVMeterConfigModeExposureTime: {
  375. // Adjust integration time
  376. int current_time = static_cast<int>(instance->sensor->getIntegrationTime());
  377. int new_time = (event->key == InputKeyUp) ? current_time + 1 :
  378. current_time - 1;
  379. if(new_time >= static_cast<int>(TIME_1MS) &&
  380. new_time <= static_cast<int>(TIME_16384MS)) {
  381. instance->sensor->setIntegrationTime(
  382. static_cast<as7331_integration_time_t>(new_time));
  383. model->conversion_time = instance->sensor->getConversionTime();
  384. setting_changed = true;
  385. }
  386. break;
  387. }
  388. case UVMeterConfigModeEyesProtection:
  389. // Toggle eyes protected
  390. model->eyes_protected = !model->eyes_protected;
  391. model->effective_results = uv_meter_data_calculate_effective_results(
  392. &model->results, model->eyes_protected);
  393. setting_changed = true;
  394. break;
  395. default:
  396. break;
  397. }
  398. furi_mutex_release(instance->sensor_mutex);
  399. consumed = true;
  400. }
  401. },
  402. setting_changed);
  403. return consumed;
  404. }
  405. UVMeterData* uv_meter_data_alloc(void) {
  406. UVMeterData* instance = new UVMeterData();
  407. instance->view = view_alloc();
  408. view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(UVMeterDataModel));
  409. view_set_draw_callback(instance->view, uv_meter_data_draw_callback);
  410. view_set_input_callback(instance->view, uv_meter_data_input_callback);
  411. view_set_context(instance->view, instance);
  412. FURI_LOG_D("UV_Meter Data", "Update Alloc");
  413. with_view_model_cpp(
  414. instance->view,
  415. UVMeterDataModel*,
  416. model,
  417. {
  418. model->buffer = furi_string_alloc();
  419. model->current_config_mode = UVMeterConfigModeGain;
  420. model->results.uv_a = 0;
  421. model->results.uv_b = 0;
  422. model->results.uv_c = 0;
  423. model->raw_results.uv_a = 0;
  424. model->raw_results.uv_b = 0;
  425. model->raw_results.uv_c = 0;
  426. model->effective_results.uv_a_eff = 0;
  427. model->effective_results.uv_b_eff = 0;
  428. model->effective_results.uv_c_eff = 0;
  429. model->effective_results.uv_total_eff = 0;
  430. model->effective_results.t_max = 0;
  431. model->gain_value = 1;
  432. model->conversion_time = 1.0;
  433. model->eyes_protected = true;
  434. model->unit = UVMeterUnituW_cm_2;
  435. },
  436. true);
  437. return instance;
  438. }
  439. void uv_meter_data_free(UVMeterData* instance) {
  440. furi_assert(instance);
  441. with_view_model_cpp(
  442. instance->view, UVMeterDataModel*, model, { furi_string_free(model->buffer); }, false);
  443. view_free(instance->view);
  444. delete instance;
  445. // Data View is not responsible for `sensor` and `sensor_mutex`
  446. }
  447. void uv_meter_data_reset(UVMeterData* instance, bool update) {
  448. furi_assert(instance);
  449. FURI_LOG_D("UV_Meter Data", "Update Reset");
  450. with_view_model_cpp(
  451. instance->view,
  452. UVMeterDataModel*,
  453. model,
  454. {
  455. model->current_config_mode = UVMeterConfigModeGain;
  456. model->results.uv_a = 0;
  457. model->results.uv_b = 0;
  458. model->results.uv_c = 0;
  459. model->raw_results.uv_a = 0;
  460. model->raw_results.uv_b = 0;
  461. model->raw_results.uv_c = 0;
  462. model->effective_results.uv_a_eff = 0;
  463. model->effective_results.uv_b_eff = 0;
  464. model->effective_results.uv_c_eff = 0;
  465. model->effective_results.uv_total_eff = 0;
  466. model->effective_results.t_max = 0;
  467. model->gain_value = 1;
  468. model->conversion_time = 1.0;
  469. model->eyes_protected = true;
  470. model->unit = UVMeterUnituW_cm_2;
  471. },
  472. update);
  473. }
  474. View* uv_meter_data_get_view(UVMeterData* instance) {
  475. furi_assert(instance);
  476. return instance->view;
  477. }
  478. void uv_meter_data_set_enter_settings_callback(
  479. UVMeterData* instance,
  480. UVMeterDataEnterSettingsCallback callback,
  481. void* context) {
  482. furi_assert(instance);
  483. furi_assert(callback);
  484. with_view_model_cpp(
  485. instance->view,
  486. UVMeterDataModel*,
  487. model,
  488. {
  489. UNUSED(model);
  490. instance->callback = callback;
  491. instance->context = context;
  492. },
  493. false);
  494. }
  495. void uv_meter_data_set_sensor(UVMeterData* instance, AS7331* sensor, FuriMutex* sensor_mutex) {
  496. furi_assert(instance);
  497. furi_assert(sensor);
  498. furi_assert(sensor_mutex);
  499. with_view_model_cpp(
  500. instance->view,
  501. UVMeterDataModel*,
  502. model,
  503. {
  504. UNUSED(model);
  505. instance->sensor = sensor;
  506. instance->sensor_mutex = sensor_mutex;
  507. },
  508. false);
  509. }
  510. void uv_meter_update_from_sensor(UVMeterData* instance) {
  511. furi_assert(instance);
  512. if(instance->sensor) {
  513. furi_mutex_acquire(instance->sensor_mutex, FuriWaitForever);
  514. FURI_LOG_D("UV_Meter Data", "Update From Sensor");
  515. with_view_model_cpp(
  516. instance->view,
  517. UVMeterDataModel*,
  518. model,
  519. {
  520. model->gain_value = instance->sensor->getGainValue();
  521. model->conversion_time = instance->sensor->getConversionTime();
  522. },
  523. true);
  524. furi_mutex_release(instance->sensor_mutex);
  525. }
  526. }
  527. void uv_meter_data_set_results(
  528. UVMeterData* instance,
  529. const AS7331::Results* results,
  530. const AS7331::RawResults* raw_results) {
  531. furi_assert(instance);
  532. furi_assert(results);
  533. furi_assert(raw_results);
  534. FURI_LOG_D("UV_Meter Data", "Update Set Results");
  535. with_view_model_cpp(
  536. instance->view,
  537. UVMeterDataModel*,
  538. model,
  539. {
  540. model->results = *results;
  541. model->raw_results = *raw_results;
  542. model->effective_results =
  543. uv_meter_data_calculate_effective_results(&model->results, model->eyes_protected);
  544. },
  545. true);
  546. }
  547. UVMeterEffectiveResults uv_meter_data_get_effective_results(UVMeterData* instance) {
  548. furi_assert(instance);
  549. UVMeterEffectiveResults effective_results;
  550. with_view_model_cpp(
  551. instance->view,
  552. UVMeterDataModel*,
  553. model,
  554. { effective_results = model->effective_results; },
  555. false);
  556. return effective_results;
  557. }
  558. void uv_meter_data_set_eyes_protected(UVMeterData* instance, bool eyes_protected) {
  559. furi_assert(instance);
  560. FURI_LOG_D("UV_Meter Data", "Update Set Eyes Protected");
  561. with_view_model_cpp(
  562. instance->view,
  563. UVMeterDataModel*,
  564. model,
  565. { model->eyes_protected = eyes_protected; },
  566. true);
  567. }
  568. bool uv_meter_data_get_eyes_protected(UVMeterData* instance) {
  569. furi_assert(instance);
  570. bool eyes_protected = false;
  571. with_view_model_cpp(
  572. instance->view,
  573. UVMeterDataModel*,
  574. model,
  575. { eyes_protected = model->eyes_protected; },
  576. false);
  577. return eyes_protected;
  578. }
  579. void uv_meter_data_set_unit(UVMeterData* instance, UVMeterUnit unit) {
  580. furi_assert(instance);
  581. FURI_LOG_D("UV_Meter Data", "Update Set Unit");
  582. with_view_model_cpp(instance->view, UVMeterDataModel*, model, { model->unit = unit; }, true);
  583. }
  584. UVMeterEffectiveResults
  585. uv_meter_data_calculate_effective_results(const AS7331::Results* results, bool eyes_protected) {
  586. // Weighted Spectral Effectiveness
  587. double w_spectral_eff_uv_a = 0.0002824;
  588. double w_spectral_eff_uv_b = 0.3814;
  589. double w_spectral_eff_uv_c = 0.6047;
  590. if(eyes_protected) { // 😎
  591. // w_spectral_eff_uv_a is the same
  592. w_spectral_eff_uv_b = 0.2009;
  593. w_spectral_eff_uv_c = 0.2547;
  594. }
  595. UVMeterEffectiveResults effective_results;
  596. // Effective Irradiance
  597. effective_results.uv_a_eff = results->uv_a * w_spectral_eff_uv_a;
  598. effective_results.uv_b_eff = results->uv_b * w_spectral_eff_uv_b;
  599. effective_results.uv_c_eff = results->uv_c * w_spectral_eff_uv_c;
  600. effective_results.uv_total_eff =
  601. effective_results.uv_a_eff + effective_results.uv_b_eff + effective_results.uv_c_eff;
  602. // Daily dose (seconds) based on the total effective irradiance
  603. double daily_dose = 0.003; // J/cm^2
  604. double uW_to_W = 1e-6;
  605. effective_results.t_max = daily_dose / (effective_results.uv_total_eff * uW_to_W);
  606. return effective_results;
  607. }