ESP32 / ESP8266 & BME280 / SHT2x sensor with InfluxDB support
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ui.cpp 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. /*
  2. * ui.cpp
  3. *
  4. * https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display/blob/main/Examples/Basics/2-TouchTest/2-TouchTest.ino
  5. * https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display/blob/main/Examples/Basics/4-BacklightControlTest/4-BacklightControlTest.ino
  6. *
  7. * ESP8266 / ESP32 Environmental Sensor
  8. *
  9. * ----------------------------------------------------------------------------
  10. * "THE BEER-WARE LICENSE" (Revision 42):
  11. * <xythobuz@xythobuz.de> wrote this file. As long as you retain this notice
  12. * you can do whatever you want with this stuff. If we meet some day, and you
  13. * think this stuff is worth it, you can buy me a beer in return. Thomas Buck
  14. * ----------------------------------------------------------------------------
  15. */
  16. #include <Arduino.h>
  17. #include <WiFi.h>
  18. #include <time.h>
  19. #include "config.h"
  20. #include "DebugLog.h"
  21. #include "mqtt.h"
  22. #include "ui.h"
  23. #ifdef FEATURE_UI
  24. #include <SPI.h>
  25. #include <XPT2046_Touchscreen.h>
  26. #include <TFT_eSPI.h>
  27. #define XPT2046_IRQ 36
  28. #define XPT2046_MOSI 32
  29. #define XPT2046_MISO 39
  30. #define XPT2046_CLK 25
  31. #define XPT2046_CS 33
  32. #define LEDC_CHANNEL_0 0
  33. #define LEDC_TIMER_12_BIT 12
  34. #define LEDC_BASE_FREQ 5000
  35. #define LDR_PIN 34
  36. #define BTN_PIN 0
  37. #define TOUCH_LEFT 180
  38. #define TOUCH_RIGHT 3750
  39. #define TOUCH_TOP 230
  40. #define TOUCH_BOTTOM 3800
  41. #define BTN_W 120
  42. #define BTN_H 60
  43. #define BTN_GAP 20
  44. #define BTNS_OFF_X ((LCD_WIDTH - (2 * BTN_W) - (1 * BTN_GAP)) / 2)
  45. #define BTNS_OFF_Y ((LCD_HEIGHT - (3 * BTN_H) - (2 * BTN_GAP)) / 2)
  46. #define INVERT_BOOL(x) (x) = !(x)
  47. #define LDR_CHECK_MS 1000
  48. #define MIN_TOUCH_DELAY_MS 200
  49. #define TOUCH_PRESSURE_MIN 200
  50. #define FULL_BRIGHT_MS (1000 * 30)
  51. #define NO_BRIGHT_MS (1000 * 2)
  52. #define STANDBY_BRIGHTNESS 10
  53. #define NTP_SERVER "pool.ntp.org"
  54. #define STANDBY_REDRAW_MS (1000 * 10)
  55. // TODO make configurable
  56. #define gmtOffset_sec (60 * 60)
  57. #define daylightOffset_sec (60 * 60)
  58. static SPIClass mySpi = SPIClass(HSPI);
  59. static XPT2046_Touchscreen ts(XPT2046_CS, XPT2046_IRQ);
  60. static TFT_eSPI tft = TFT_eSPI();
  61. struct ui_status ui_status = {0};
  62. enum ui_pages {
  63. UI_START = 0,
  64. UI_LIVINGROOM1,
  65. UI_LIVINGROOM2,
  66. UI_BATHROOM,
  67. UI_INFO,
  68. UI_NUM_PAGES
  69. };
  70. static enum ui_pages ui_page = UI_START;
  71. static bool is_touched = false;
  72. static unsigned long last_ldr = 0;
  73. static int ldr_value = 0;
  74. static unsigned long last_touch_time = 0;
  75. static int curr_brightness = 255;
  76. static int set_max_brightness = 255;
  77. static unsigned long last_standby_draw = 0;
  78. static TS_Point touchToScreen(TS_Point p) {
  79. p.x = map(p.x, TOUCH_LEFT, TOUCH_RIGHT, 0, LCD_WIDTH);
  80. p.y = map(p.y, TOUCH_TOP, TOUCH_BOTTOM, 0, LCD_HEIGHT);
  81. return p;
  82. }
  83. static void ledcAnalogWrite(uint8_t channel, uint32_t value, uint32_t valueMax = 255) {
  84. uint32_t duty = (4095 / valueMax) * min(value, valueMax);
  85. ledcWrite(channel, duty);
  86. }
  87. static void draw_button(const char *name, uint32_t x, uint32_t y, uint32_t color) {
  88. tft.fillRect(x - BTN_W / 2, y - BTN_H / 2, BTN_W, BTN_H, color);
  89. tft.setTextDatum(MC_DATUM); // middle center
  90. tft.drawString(name, x, y, 2);
  91. }
  92. static void draw_livingroom1(void) {
  93. // 1
  94. draw_button("Lights Corner",
  95. BTNS_OFF_X + BTN_W / 2,
  96. BTNS_OFF_Y + BTN_H / 2,
  97. ui_status.light_corner ? TFT_GREEN : TFT_RED);
  98. // 2
  99. draw_button("Lights Workspace",
  100. BTNS_OFF_X + BTN_W / 2,
  101. BTNS_OFF_Y + BTN_H / 2 + BTN_H + BTN_GAP,
  102. ui_status.light_workspace ? TFT_GREEN : TFT_RED);
  103. // 3
  104. draw_button("Lights Sink",
  105. BTNS_OFF_X + BTN_W / 2,
  106. BTNS_OFF_Y + BTN_H / 2 + (BTN_H + BTN_GAP) * 2,
  107. ui_status.light_sink ? TFT_GREEN : TFT_RED);
  108. // 4
  109. draw_button("Sound Amp.",
  110. BTNS_OFF_X + BTN_W / 2 + BTN_W + BTN_GAP,
  111. BTNS_OFF_Y + BTN_H / 2,
  112. ui_status.sound_amplifier ? TFT_GREEN : TFT_RED);
  113. // 5
  114. bool on = ui_status.light_corner || ui_status.light_sink || ui_status.light_workspace
  115. || ui_status.light_amp || ui_status.light_bench || ui_status.light_box
  116. || ui_status.light_kitchen || ui_status.light_pc;
  117. draw_button(on ? "All Lights Off" : "Wake Up Lights",
  118. BTNS_OFF_X + BTN_W / 2 + BTN_W + BTN_GAP,
  119. BTNS_OFF_Y + BTN_H / 2 + BTN_H + BTN_GAP,
  120. TFT_MAGENTA);
  121. }
  122. static void draw_livingroom2(void) {
  123. // 1
  124. draw_button("Lights PC",
  125. BTNS_OFF_X + BTN_W / 2,
  126. BTNS_OFF_Y + BTN_H / 2,
  127. ui_status.light_pc ? TFT_GREEN : TFT_RED);
  128. // 2
  129. draw_button("Lights Bench",
  130. BTNS_OFF_X + BTN_W / 2,
  131. BTNS_OFF_Y + BTN_H / 2 + BTN_H + BTN_GAP,
  132. ui_status.light_bench ? TFT_GREEN : TFT_RED);
  133. // 3
  134. draw_button("Lights Kitchen",
  135. BTNS_OFF_X + BTN_W / 2,
  136. BTNS_OFF_Y + BTN_H / 2 + (BTN_H + BTN_GAP) * 2,
  137. ui_status.light_kitchen ? TFT_GREEN : TFT_RED);
  138. // 4
  139. draw_button("Lights Amp.",
  140. BTNS_OFF_X + BTN_W / 2 + BTN_W + BTN_GAP,
  141. BTNS_OFF_Y + BTN_H / 2,
  142. ui_status.light_amp ? TFT_GREEN : TFT_RED);
  143. // 5
  144. draw_button("Lights Box",
  145. BTNS_OFF_X + BTN_W / 2 + BTN_W + BTN_GAP,
  146. BTNS_OFF_Y + BTN_H / 2 + BTN_H + BTN_GAP,
  147. ui_status.light_box ? TFT_GREEN : TFT_RED);
  148. }
  149. static void draw_bathroom(void) {
  150. // 1
  151. draw_button("Bath Lights Auto",
  152. BTNS_OFF_X + BTN_W / 2,
  153. BTNS_OFF_Y + BTN_H / 2,
  154. ui_status.bathroom_lights == BATH_LIGHT_NONE ? TFT_GREEN : TFT_RED);
  155. // 2
  156. draw_button("Bath Lights Big",
  157. BTNS_OFF_X + BTN_W / 2,
  158. BTNS_OFF_Y + BTN_H / 2 + BTN_H + BTN_GAP,
  159. ui_status.bathroom_lights == BATH_LIGHT_BIG ? TFT_GREEN : TFT_RED);
  160. // 4
  161. draw_button("Bath Lights Off",
  162. BTNS_OFF_X + BTN_W / 2 + BTN_W + BTN_GAP,
  163. BTNS_OFF_Y + BTN_H / 2,
  164. ui_status.bathroom_lights == BATH_LIGHT_OFF ? TFT_GREEN : TFT_RED);
  165. // 5
  166. draw_button("Bath Lights Small",
  167. BTNS_OFF_X + BTN_W / 2 + BTN_W + BTN_GAP,
  168. BTNS_OFF_Y + BTN_H / 2 + BTN_H + BTN_GAP,
  169. ui_status.bathroom_lights == BATH_LIGHT_SMALL ? TFT_GREEN : TFT_RED);
  170. }
  171. static void draw_info(void) {
  172. tft.fillScreen(TFT_BLACK);
  173. tft.setTextDatum(TC_DATUM); // top center
  174. tft.drawString(ESP_PLATFORM_NAME " " NAME_OF_FEATURE " V" ESP_ENV_VERSION, LCD_WIDTH / 2, 0, 2);
  175. tft.drawString("by xythobuz.de", LCD_WIDTH / 2, 16, 2);
  176. tft.setTextDatum(TL_DATUM); // top left
  177. tft.drawString("Build Date: " __DATE__, 0, 40 + 16 * 0, 1);
  178. tft.drawString("Build Time: " __TIME__, 0, 40 + 16 * 1, 1);
  179. tft.drawString("Location: " SENSOR_LOCATION, 0, 40 + 16 * 2, 1);
  180. tft.drawString("ID: " SENSOR_ID, 0, 40 + 16 * 3, 1);
  181. tft.drawString("MAC: " + String(WiFi.macAddress()), 0, 40 + 16 * 4, 1);
  182. tft.drawString("Free heap: " + String(ESP.getFreeHeap() / 1024.0f) + "k", 0, 40 + 16 * 5, 1);
  183. tft.drawString("Free sketch space: " + String(ESP.getFreeSketchSpace() / 1024.0f) + "k", 0, 40 + 16 * 6, 1);
  184. tft.drawString("Flash chip size: " + String(ESP.getFlashChipSize() / 1024.0f) + "k", 0, 40 + 16 * 7, 1);
  185. tft.drawString("Uptime: " + String(millis() / 1000) + "sec", 0, 40 + 16 * 8, 1);
  186. tft.drawString("IPv4: " + WiFi.localIP().toString(), 0, 40 + 16 * 9, 1);
  187. tft.drawString("IPv6: " + WiFi.localIPv6().toString(), 0, 40 + 16 * 10, 1);
  188. tft.drawString("Hostname: " + String(SENSOR_HOSTNAME_PREFIX) + String(SENSOR_ID), 0, 40 + 16 * 11, 1);
  189. tft.drawString("LDR: " + String(ldr_value), 0, 40 + 16 * 12, 1);
  190. }
  191. static void draw_standby(void) {
  192. tft.fillScreen(TFT_BLACK);
  193. tft.setTextDatum(TC_DATUM); // top center
  194. tft.drawString(ESP_PLATFORM_NAME " " NAME_OF_FEATURE " V" ESP_ENV_VERSION, LCD_WIDTH / 2, 0, 2);
  195. tft.drawString("by xythobuz.de", LCD_WIDTH / 2, 16, 2);
  196. struct tm timeinfo;
  197. String date, time;
  198. if(getLocalTime(&timeinfo)) {
  199. if (timeinfo.tm_mday < 10) {
  200. date += "0";
  201. }
  202. date += String(timeinfo.tm_mday);
  203. date += ".";
  204. if ((timeinfo.tm_mon + 1) < 10) {
  205. date += "0";
  206. }
  207. date += String(timeinfo.tm_mon + 1);
  208. date += ".";
  209. date += String(timeinfo.tm_year + 1900);
  210. if (timeinfo.tm_hour < 10) {
  211. time += "0";
  212. }
  213. time += String(timeinfo.tm_hour);
  214. time += ":";
  215. if (timeinfo.tm_min < 10) {
  216. time += "0";
  217. }
  218. time += String(timeinfo.tm_min);
  219. }
  220. tft.setTextDatum(MC_DATUM); // middle center
  221. tft.drawString(date, LCD_WIDTH / 2, LCD_HEIGHT / 2 - 8, 2);
  222. tft.drawString(time, LCD_WIDTH / 2, LCD_HEIGHT / 2 + 8, 2);
  223. tft.setTextDatum(BC_DATUM); // bottom center
  224. tft.drawString("Touch to begin...", LCD_WIDTH / 2, LCD_HEIGHT, 2);
  225. }
  226. void ui_init(void) {
  227. mySpi.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
  228. ts.begin(mySpi);
  229. ts.setRotation(1);
  230. tft.init();
  231. tft.setRotation(1);
  232. ledcSetup(LEDC_CHANNEL_0, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
  233. ledcAttachPin(TFT_BL, LEDC_CHANNEL_0);
  234. curr_brightness = set_max_brightness;
  235. ledcAnalogWrite(LEDC_CHANNEL_0, curr_brightness);
  236. pinMode(BTN_PIN, INPUT);
  237. pinMode(LDR_PIN, ANALOG);
  238. analogSetAttenuation(ADC_0db);
  239. analogReadResolution(12);
  240. analogSetPinAttenuation(LDR_PIN, ADC_0db);
  241. ldr_value = analogRead(LDR_PIN);
  242. ui_progress(UI_INIT);
  243. }
  244. static void ui_draw_menu(void) {
  245. switch (ui_page) {
  246. case UI_START:
  247. tft.fillScreen(TFT_BLACK);
  248. ui_page = UI_LIVINGROOM1;
  249. // fall-through
  250. case UI_LIVINGROOM1:
  251. draw_livingroom1();
  252. break;
  253. case UI_LIVINGROOM2:
  254. draw_livingroom2();
  255. break;
  256. case UI_BATHROOM:
  257. draw_bathroom();
  258. break;
  259. case UI_INFO:
  260. draw_info();
  261. return; // no next button
  262. default:
  263. ui_page = UI_START;
  264. ui_draw_menu();
  265. return;
  266. }
  267. draw_button("Next...", BTNS_OFF_X + BTN_W / 2 + BTN_W + BTN_GAP, BTNS_OFF_Y + BTN_H / 2 + (BTN_H + BTN_GAP) * 2, TFT_CYAN);
  268. }
  269. void ui_progress(enum ui_state state) {
  270. int x = LCD_WIDTH / 2;
  271. int y = LCD_HEIGHT / 2;
  272. int fontSize = 2;
  273. switch (state) {
  274. case UI_INIT: {
  275. tft.fillScreen(TFT_BLACK);
  276. tft.setTextDatum(MC_DATUM); // middle center
  277. tft.drawString("Initializing ESP-ENV", x, y - 32, fontSize);
  278. tft.drawString("xythobuz.de", x, y, fontSize);
  279. } break;
  280. case UI_WIFI_CONNECT: {
  281. tft.setTextDatum(MC_DATUM); // middle center
  282. tft.drawString("Connecting to '" WIFI_SSID "'", x, y + 32, fontSize);
  283. } break;
  284. case UI_WIFI_CONNECTING: {
  285. static int n = 0;
  286. const char anim[] = { '\\', '|', '/', '-' };
  287. n++;
  288. if (n >= sizeof(anim)) {
  289. n = 0;
  290. }
  291. char s[2] = { anim[n], '\0' };
  292. tft.drawCentreString(s, x, y + 64, fontSize);
  293. } break;
  294. case UI_WIFI_CONNECTED: {
  295. tft.setTextDatum(MC_DATUM); // middle center
  296. tft.drawString("Connected!", x, y + 64, fontSize);
  297. } break;
  298. case UI_READY: {
  299. // get time via NTP
  300. configTime(gmtOffset_sec, daylightOffset_sec, NTP_SERVER);
  301. ui_page = UI_START;
  302. ui_draw_menu();
  303. } break;
  304. case UI_UPDATE: {
  305. ui_draw_menu();
  306. } break;
  307. }
  308. }
  309. void ui_run(void) {
  310. unsigned long now = millis();
  311. // adjust backlight brightness
  312. unsigned long diff = now - last_touch_time;
  313. if (diff < FULL_BRIGHT_MS) {
  314. curr_brightness = set_max_brightness;
  315. } else if (diff < (FULL_BRIGHT_MS + NO_BRIGHT_MS)) {
  316. curr_brightness = map(diff - FULL_BRIGHT_MS, 0, NO_BRIGHT_MS, set_max_brightness, STANDBY_BRIGHTNESS);
  317. } else {
  318. if ((curr_brightness > STANDBY_BRIGHTNESS) || ((now - last_standby_draw) >= STANDBY_REDRAW_MS)) {
  319. // enter standby screen
  320. draw_standby();
  321. last_standby_draw = now;
  322. }
  323. curr_brightness = STANDBY_BRIGHTNESS;
  324. }
  325. ledcAnalogWrite(LEDC_CHANNEL_0, curr_brightness);
  326. // go to info page when BOOT button is pressed
  327. if (!digitalRead(BTN_PIN)) {
  328. ui_page = UI_INFO;
  329. }
  330. // read out LDR in regular intervals
  331. if (now >= (last_ldr + LDR_CHECK_MS)) {
  332. last_ldr = now;
  333. int ldr = analogRead(LDR_PIN);
  334. // TODO lowpass?
  335. //ldr_value = (ldr_value * 0.9f) + (ldr * 0.1f);
  336. ldr_value = ldr;
  337. // refresh info page, it shows the LDR value
  338. if (ui_page == UI_INFO) {
  339. ui_draw_menu();
  340. }
  341. }
  342. bool touched = ts.tirqTouched() && ts.touched();
  343. TS_Point p;
  344. if (touched) {
  345. p = touchToScreen(ts.getPoint());
  346. // minimum pressure
  347. if (p.z < TOUCH_PRESSURE_MIN) {
  348. touched = false;
  349. }
  350. }
  351. if (touched && (!is_touched)) {
  352. is_touched = true;
  353. last_touch_time = millis();
  354. // skip touch event and just go back to full brightness
  355. if (curr_brightness < set_max_brightness) {
  356. tft.fillScreen(TFT_BLACK); // exit standby screen
  357. ui_draw_menu(); // re-draw normal screen contents
  358. return ui_run(); // skip touch and increase brightness
  359. }
  360. if (ui_page == UI_INFO) {
  361. // switch to next page, skip init and info screen
  362. do {
  363. ui_page = (enum ui_pages)((ui_page + 1) % UI_NUM_PAGES);
  364. } while ((ui_page == UI_START) || (ui_page == UI_INFO));
  365. tft.fillScreen(TFT_BLACK);
  366. ui_draw_menu();
  367. return;
  368. }
  369. if ((p.x >= BTNS_OFF_X) && (p.x <= BTNS_OFF_X + BTN_W) && (p.y >= BTNS_OFF_Y) && (p.y <= BTNS_OFF_Y + BTN_H)) {
  370. // 1
  371. if (ui_page == UI_LIVINGROOM1) {
  372. INVERT_BOOL(ui_status.light_corner);
  373. } else if (ui_page == UI_LIVINGROOM2) {
  374. INVERT_BOOL(ui_status.light_pc);
  375. } else if (ui_page == UI_BATHROOM) {
  376. ui_status.bathroom_lights = BATH_LIGHT_NONE;
  377. }
  378. writeMQTT_UI();
  379. } else if ((p.x >= BTNS_OFF_X) && (p.x <= BTNS_OFF_X + BTN_W) && (p.y >= (BTNS_OFF_Y + BTN_H + BTN_GAP)) && (p.y <= (BTNS_OFF_Y + BTN_H + BTN_GAP + BTN_H))) {
  380. // 2
  381. if (ui_page == UI_LIVINGROOM1) {
  382. INVERT_BOOL(ui_status.light_workspace);
  383. } else if (ui_page == UI_LIVINGROOM2) {
  384. INVERT_BOOL(ui_status.light_bench);
  385. } else if (ui_page == UI_BATHROOM) {
  386. ui_status.bathroom_lights = BATH_LIGHT_BIG;
  387. }
  388. writeMQTT_UI();
  389. } else if ((p.x >= BTNS_OFF_X) && (p.x <= BTNS_OFF_X + BTN_W) && (p.y >= (BTNS_OFF_Y + BTN_H * 2 + BTN_GAP * 2)) && (p.y <= (BTNS_OFF_Y + BTN_H * 2 + BTN_GAP * 2 + BTN_H))) {
  390. // 3
  391. if (ui_page == UI_LIVINGROOM1) {
  392. INVERT_BOOL(ui_status.light_sink);
  393. } else if (ui_page == UI_LIVINGROOM2) {
  394. INVERT_BOOL(ui_status.light_kitchen);
  395. }
  396. writeMQTT_UI();
  397. } else if ((p.x >= BTNS_OFF_X + BTN_W + BTN_GAP) && (p.x <= BTNS_OFF_X + BTN_W + BTN_GAP + BTN_W) && (p.y >= BTNS_OFF_Y) && (p.y <= BTNS_OFF_Y + BTN_H)) {
  398. // 4
  399. if (ui_page == UI_LIVINGROOM1) {
  400. INVERT_BOOL(ui_status.sound_amplifier);
  401. } else if (ui_page == UI_LIVINGROOM2) {
  402. INVERT_BOOL(ui_status.light_amp);
  403. } else if (ui_page == UI_BATHROOM) {
  404. ui_status.bathroom_lights = BATH_LIGHT_OFF;
  405. }
  406. writeMQTT_UI();
  407. } else if ((p.x >= BTNS_OFF_X + BTN_W + BTN_GAP) && (p.x <= BTNS_OFF_X + BTN_W + BTN_GAP + BTN_W) && (p.y >= (BTNS_OFF_Y + BTN_H + BTN_GAP)) && (p.y <= (BTNS_OFF_Y + BTN_H + BTN_GAP + BTN_H))) {
  408. // 5
  409. if (ui_page == UI_LIVINGROOM1) {
  410. bool on = ui_status.light_corner || ui_status.light_sink || ui_status.light_workspace
  411. || ui_status.light_amp || ui_status.light_bench || ui_status.light_box
  412. || ui_status.light_kitchen || ui_status.light_pc;
  413. if (on) {
  414. ui_status.light_amp = false;
  415. ui_status.light_kitchen = false;
  416. ui_status.light_bench= false;
  417. ui_status.light_workspace = false;
  418. ui_status.light_pc = false;
  419. ui_status.light_corner = false;
  420. ui_status.light_box = false;
  421. ui_status.light_sink = false;
  422. } else {
  423. ui_status.light_corner = true;
  424. ui_status.light_sink = true;
  425. }
  426. } else if (ui_page == UI_LIVINGROOM2) {
  427. INVERT_BOOL(ui_status.light_box);
  428. } else if (ui_page == UI_BATHROOM) {
  429. ui_status.bathroom_lights = BATH_LIGHT_SMALL;
  430. }
  431. writeMQTT_UI();
  432. } else if ((p.x >= BTNS_OFF_X + BTN_W + BTN_GAP) && (p.x <= BTNS_OFF_X + BTN_W + BTN_GAP + BTN_W) && (p.y >= (BTNS_OFF_Y + BTN_H * 2 + BTN_GAP * 2)) && (p.y <= (BTNS_OFF_Y + BTN_H * 2 + BTN_GAP * 2 + BTN_H))) {
  433. // switch to next page, skip init and info screen
  434. do {
  435. ui_page = (enum ui_pages)((ui_page + 1) % UI_NUM_PAGES);
  436. } while ((ui_page == UI_START) || (ui_page == UI_INFO));
  437. tft.fillScreen(TFT_BLACK);
  438. }
  439. ui_draw_menu();
  440. } else if ((!touched) && is_touched && ((now - last_touch_time) >= MIN_TOUCH_DELAY_MS)) {
  441. is_touched = false;
  442. }
  443. }
  444. #endif // FEATURE_UI