mh_z19app.c 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. #include "mh_z19_icons.h"
  2. #include <furi.h>
  3. #include <furi_hal.h>
  4. #include <gui/gui.h>
  5. #include <gui/elements.h>
  6. #include <input/input.h>
  7. #include <notification/notification_messages.h>
  8. #include <assets_icons.h>
  9. typedef enum
  10. {
  11. GreenStatus,
  12. YellowStatus,
  13. RedStatus,
  14. } StatusPPM;
  15. typedef enum
  16. {
  17. RANGE_2000 = 2000,
  18. RANGE_5000 = 5000
  19. } SensorRange;
  20. struct MHZ19App
  21. {
  22. Gui *gui;
  23. ViewPort *view_port;
  24. FuriMessageQueue *event_queue;
  25. FuriMutex *mutex;
  26. NotificationApp *notifications;
  27. bool have_5v;
  28. int32_t current_page;
  29. StatusPPM status_ppm;
  30. SensorRange sensor_range;
  31. const GpioPin *input_pin;
  32. int32_t co2_ppm;
  33. };
  34. typedef struct MHZ19App MHZ19App;
  35. const NotificationSequence green_led_sequence = {
  36. &message_green_255,
  37. &message_do_not_reset,
  38. NULL,
  39. };
  40. const NotificationSequence yellow_led_sequence = {
  41. &message_green_255,
  42. &message_red_255,
  43. &message_do_not_reset,
  44. &message_vibro_on,
  45. &message_note_c5,
  46. &message_delay_50,
  47. &message_vibro_off,
  48. &message_sound_off,
  49. NULL,
  50. };
  51. const NotificationSequence red_led_sequence = {
  52. &message_red_255,
  53. &message_do_not_reset,
  54. &message_vibro_on,
  55. &message_note_c5,
  56. &message_delay_50,
  57. &message_vibro_off,
  58. &message_sound_off,
  59. NULL,
  60. };
  61. void mh_z19app_draw_callback(Canvas *canvas, void *ctx)
  62. {
  63. furi_assert(ctx);
  64. MHZ19App *app = ctx;
  65. canvas_clear(canvas);
  66. if (!app->have_5v)
  67. {
  68. canvas_set_font(canvas, FontPrimary);
  69. elements_multiline_text_aligned(
  70. canvas,
  71. 4,
  72. 28,
  73. AlignLeft,
  74. AlignTop,
  75. "5V on GPIO must be\nenabled, or USB must\nbe connected.");
  76. return;
  77. }
  78. else if (app->current_page == 0)
  79. {
  80. canvas_set_font(canvas, FontPrimary);
  81. elements_multiline_text_aligned(
  82. canvas,
  83. 4,
  84. 1,
  85. AlignLeft,
  86. AlignTop,
  87. "Connect sensor MH-Z19 to pins:\n5V -> 1\nGND -> 8\nPWM -> 3\nPress center button...");
  88. return;
  89. }
  90. else if (app->current_page == 1)
  91. {
  92. canvas_set_font(canvas, FontPrimary);
  93. elements_multiline_text_aligned(
  94. canvas,
  95. 4,
  96. 1,
  97. AlignLeft,
  98. AlignTop,
  99. "Select sensor measuring range by arrows.\nAvailable:\n\t- 0-2000ppm\n\t- 0-5000ppm\nPress center button...");
  100. return;
  101. }
  102. FuriString *strbuf = furi_string_alloc();
  103. const Icon *icon;
  104. FuriString *status_text = furi_string_alloc();
  105. switch (app->status_ppm)
  106. {
  107. case GreenStatus:
  108. icon = &I_passport_okay1_46x49;
  109. furi_string_set_str(status_text, "it's OK!");
  110. break;
  111. case YellowStatus:
  112. icon = &I_passport_bad1_46x49;
  113. furi_string_set_str(status_text, "Not good!");
  114. break;
  115. case RedStatus:
  116. icon = &I_passport_bad3_46x49;
  117. furi_string_set_str(status_text, "Very bad!");
  118. break;
  119. default:
  120. icon = &I_passport_okay1_46x49;
  121. furi_string_set_str(status_text, "It's OK!");
  122. break;
  123. }
  124. const Icon *co2_icon = &I_co2;
  125. canvas_draw_icon(canvas, 9, 7, icon);
  126. canvas_draw_icon(canvas, 59, 8, co2_icon);
  127. furi_string_printf(strbuf, "%ld", app->co2_ppm);
  128. canvas_set_font(canvas, FontBigNumbers);
  129. canvas_draw_str(canvas, 60, 40, furi_string_get_cstr(strbuf));
  130. canvas_set_font(canvas, FontPrimary);
  131. canvas_draw_str(canvas, 60, 55, furi_string_get_cstr(status_text));
  132. canvas_set_font(canvas, FontSecondary);
  133. canvas_draw_str(canvas, 110, 40, "ppm");
  134. furi_string_printf(strbuf, "%d", app->sensor_range);
  135. canvas_set_font(canvas, FontKeyboard);
  136. canvas_draw_str(canvas, 104, 8, furi_string_get_cstr(strbuf));
  137. furi_string_free(strbuf);
  138. furi_string_free(status_text);
  139. }
  140. void mh_z19app_input_callback(InputEvent *input_event, void *ctx)
  141. {
  142. furi_assert(ctx);
  143. FuriMessageQueue *event_queue = ctx;
  144. furi_message_queue_put(event_queue, input_event, FuriWaitForever);
  145. }
  146. MHZ19App *mh_z19app_alloc()
  147. {
  148. MHZ19App *app = (MHZ19App *)malloc(sizeof(MHZ19App));
  149. app->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  150. if (!app->mutex)
  151. {
  152. FURI_LOG_E("MH-Z19", "cannot create mutex\r\n");
  153. free(app);
  154. return NULL;
  155. }
  156. furi_mutex_acquire(app->mutex, FuriWaitForever);
  157. app->view_port = view_port_alloc();
  158. app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
  159. view_port_draw_callback_set(app->view_port, mh_z19app_draw_callback, app);
  160. view_port_input_callback_set(app->view_port, mh_z19app_input_callback, app->event_queue);
  161. app->gui = furi_record_open(RECORD_GUI);
  162. gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
  163. // Enable 5v power, multiple attempts to avoid issues with power chip protection false triggering
  164. uint8_t attempts = 0;
  165. while (!furi_hal_power_is_otg_enabled() && attempts++ < 5)
  166. {
  167. furi_hal_power_enable_otg();
  168. furi_delay_ms(10);
  169. }
  170. if (furi_hal_power_is_otg_enabled() || furi_hal_power_is_charging())
  171. {
  172. app->have_5v = true;
  173. }
  174. else
  175. {
  176. app->have_5v = false;
  177. }
  178. app->input_pin = &gpio_ext_pa6;
  179. furi_hal_gpio_init(app->input_pin, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
  180. app->notifications = furi_record_open(RECORD_NOTIFICATION);
  181. furi_mutex_release(app->mutex);
  182. return app;
  183. }
  184. void mh_z19app_free(MHZ19App *app)
  185. {
  186. furi_assert(app);
  187. if (furi_hal_power_is_otg_enabled())
  188. {
  189. furi_hal_power_disable_otg();
  190. }
  191. view_port_enabled_set(app->view_port, false);
  192. gui_remove_view_port(app->gui, app->view_port);
  193. view_port_free(app->view_port);
  194. furi_message_queue_free(app->event_queue);
  195. furi_hal_light_set(LightRed | LightGreen | LightBlue, 0x00);
  196. furi_record_close(RECORD_NOTIFICATION);
  197. furi_record_close(RECORD_GUI);
  198. furi_mutex_free(app->mutex);
  199. free(app);
  200. }
  201. void mh_z19app_init(MHZ19App *app)
  202. {
  203. furi_mutex_acquire(app->mutex, FuriWaitForever);
  204. app->co2_ppm = 0;
  205. app->status_ppm = GreenStatus;
  206. app->current_page = 0;
  207. app->sensor_range = RANGE_2000;
  208. app->have_5v = true;
  209. notification_message(app->notifications, &green_led_sequence);
  210. furi_mutex_release(app->mutex);
  211. }
  212. int32_t calculate_ppm(
  213. int32_t *prevVal,
  214. int32_t val,
  215. int32_t *th,
  216. int32_t *tl,
  217. int32_t *h,
  218. int32_t *l,
  219. SensorRange range)
  220. {
  221. int32_t tt = furi_get_tick();
  222. if (val == 1)
  223. {
  224. if (val != *prevVal)
  225. {
  226. *h = tt;
  227. *tl = *h - *l;
  228. *prevVal = val;
  229. }
  230. }
  231. else
  232. {
  233. if (val != *prevVal)
  234. {
  235. *l = tt;
  236. *th = *l - *h;
  237. *prevVal = val;
  238. return range * (*th - 2) / (*th + *tl - 4);
  239. }
  240. }
  241. return -1;
  242. }
  243. void change_sensor_range(MHZ19App *app)
  244. {
  245. if (app->sensor_range == RANGE_2000)
  246. {
  247. app->sensor_range = RANGE_5000;
  248. }
  249. else
  250. {
  251. app->sensor_range = RANGE_2000;
  252. }
  253. }
  254. void send_notification_if_needed(MHZ19App *app, int32_t ppm)
  255. {
  256. if (ppm > 0)
  257. {
  258. if (ppm > 1000)
  259. {
  260. if (app->status_ppm != RedStatus)
  261. {
  262. notification_message(app->notifications, &red_led_sequence);
  263. app->status_ppm = RedStatus;
  264. }
  265. }
  266. else if (ppm > 800)
  267. {
  268. if (app->status_ppm != YellowStatus)
  269. {
  270. notification_message(app->notifications, &yellow_led_sequence);
  271. app->status_ppm = YellowStatus;
  272. }
  273. }
  274. else
  275. {
  276. if (app->status_ppm != GreenStatus)
  277. {
  278. notification_message(app->notifications, &green_led_sequence);
  279. app->status_ppm = GreenStatus;
  280. }
  281. }
  282. }
  283. }
  284. int32_t mh_z19_app(void *p)
  285. {
  286. UNUSED(p);
  287. MHZ19App *app = mh_z19app_alloc();
  288. if (!app)
  289. {
  290. FURI_LOG_E("MH-Z19", "cannot create app\r\n");
  291. return -1;
  292. }
  293. mh_z19app_init(app);
  294. InputEvent event;
  295. int32_t prevVal = 0;
  296. int32_t th, tl, h = 0;
  297. int32_t l = 0;
  298. int32_t ppm = 0;
  299. for (bool processing = true; processing;)
  300. {
  301. furi_mutex_acquire(app->mutex, FuriWaitForever);
  302. ppm = calculate_ppm(
  303. &prevVal, furi_hal_gpio_read(app->input_pin), &th, &tl, &h, &l, app->sensor_range);
  304. if (ppm > 0)
  305. {
  306. app->co2_ppm = ppm;
  307. }
  308. send_notification_if_needed(app, app->co2_ppm);
  309. if (furi_message_queue_get(app->event_queue, &event, 100) == FuriStatusOk)
  310. {
  311. if (event.type == InputTypeShort)
  312. {
  313. switch (event.key)
  314. {
  315. case InputKeyBack:
  316. processing = false;
  317. break;
  318. case InputKeyOk:
  319. app->current_page++;
  320. break;
  321. case InputKeyLeft:
  322. case InputKeyRight:
  323. change_sensor_range(app);
  324. break;
  325. default:
  326. break;
  327. }
  328. }
  329. }
  330. furi_mutex_release(app->mutex);
  331. view_port_update(app->view_port);
  332. }
  333. mh_z19app_free(app);
  334. return 0;
  335. }