ESP32 / ESP8266 & BME280 / SHT2x sensor with InfluxDB support
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

ui.cpp 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  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 "config.h"
  19. #include "mqtt.h"
  20. #include "ui.h"
  21. #ifdef FEATURE_UI
  22. #include <SPI.h>
  23. #include <XPT2046_Touchscreen.h>
  24. #include <TFT_eSPI.h>
  25. #define XPT2046_IRQ 36
  26. #define XPT2046_MOSI 32
  27. #define XPT2046_MISO 39
  28. #define XPT2046_CLK 25
  29. #define XPT2046_CS 33
  30. #define LEDC_CHANNEL_0 0
  31. #define LEDC_TIMER_12_BIT 12
  32. #define LEDC_BASE_FREQ 5000
  33. #define LDR_PIN 34
  34. #define BTN_PIN 0
  35. #define TOUCH_LEFT 180
  36. #define TOUCH_RIGHT 3750
  37. #define TOUCH_TOP 230
  38. #define TOUCH_BOTTOM 3800
  39. #define BTN_W 120
  40. #define BTN_H 60
  41. #define BTN_GAP 20
  42. #define BTNS_OFF_X ((LCD_WIDTH - (2 * BTN_W) - (1 * BTN_GAP)) / 2)
  43. #define BTNS_OFF_Y ((LCD_HEIGHT - (3 * BTN_H) - (2 * BTN_GAP)) / 2)
  44. #define INVERT_BOOL(x) (x) = !(x)
  45. #define LDR_CHECK_MS 1000
  46. #define MIN_TOUCH_DELAY_MS 200
  47. #define TOUCH_PRESSURE_MIN 200
  48. static SPIClass mySpi = SPIClass(HSPI);
  49. static XPT2046_Touchscreen ts(XPT2046_CS, XPT2046_IRQ);
  50. static TFT_eSPI tft = TFT_eSPI();
  51. struct ui_status ui_status = {0};
  52. enum ui_pages {
  53. UI_START = 0,
  54. UI_LIVINGROOM1,
  55. UI_LIVINGROOM2,
  56. UI_BATHROOM,
  57. UI_INFO,
  58. UI_NUM_PAGES
  59. };
  60. static enum ui_pages ui_page = UI_START;
  61. static bool is_touched = false;
  62. static unsigned long last_ldr = 0;
  63. static int ldr_value = 0;
  64. static unsigned long last_touch_time = 0;
  65. static TS_Point touchToScreen(TS_Point p) {
  66. p.x = map(p.x, TOUCH_LEFT, TOUCH_RIGHT, 0, LCD_WIDTH);
  67. p.y = map(p.y, TOUCH_TOP, TOUCH_BOTTOM, 0, LCD_HEIGHT);
  68. return p;
  69. }
  70. static void ledcAnalogWrite(uint8_t channel, uint32_t value, uint32_t valueMax = 255) {
  71. uint32_t duty = (4095 / valueMax) * min(value, valueMax);
  72. ledcWrite(channel, duty);
  73. }
  74. static void draw_button(const char *name, uint32_t x, uint32_t y, uint32_t color) {
  75. tft.fillRect(x - BTN_W / 2, y - BTN_H / 2, BTN_W, BTN_H, color);
  76. tft.setTextDatum(MC_DATUM); // middle center
  77. tft.drawString(name, x, y, 2);
  78. }
  79. static void draw_livingroom1(void) {
  80. // 1
  81. draw_button("Lights Corner",
  82. BTNS_OFF_X + BTN_W / 2,
  83. BTNS_OFF_Y + BTN_H / 2,
  84. ui_status.light_corner ? TFT_GREEN : TFT_RED);
  85. // 2
  86. draw_button("Lights Workspace",
  87. BTNS_OFF_X + BTN_W / 2,
  88. BTNS_OFF_Y + BTN_H / 2 + BTN_H + BTN_GAP,
  89. ui_status.light_workspace ? TFT_GREEN : TFT_RED);
  90. // 3
  91. draw_button("Lights Sink",
  92. BTNS_OFF_X + BTN_W / 2,
  93. BTNS_OFF_Y + BTN_H / 2 + (BTN_H + BTN_GAP) * 2,
  94. ui_status.light_sink ? TFT_GREEN : TFT_RED);
  95. // 4
  96. draw_button("Sound Amp.",
  97. BTNS_OFF_X + BTN_W / 2 + BTN_W + BTN_GAP,
  98. BTNS_OFF_Y + BTN_H / 2,
  99. ui_status.sound_amplifier ? TFT_GREEN : TFT_RED);
  100. // 5
  101. bool on = ui_status.light_corner || ui_status.light_sink || ui_status.light_workspace
  102. || ui_status.light_amp || ui_status.light_bench || ui_status.light_box
  103. || ui_status.light_kitchen || ui_status.light_pc;
  104. draw_button(on ? "All Lights Off" : "Wake Up Lights",
  105. BTNS_OFF_X + BTN_W / 2 + BTN_W + BTN_GAP,
  106. BTNS_OFF_Y + BTN_H / 2 + BTN_H + BTN_GAP,
  107. TFT_MAGENTA);
  108. }
  109. static void draw_livingroom2(void) {
  110. // 1
  111. draw_button("Lights PC",
  112. BTNS_OFF_X + BTN_W / 2,
  113. BTNS_OFF_Y + BTN_H / 2,
  114. ui_status.light_pc ? TFT_GREEN : TFT_RED);
  115. // 2
  116. draw_button("Lights Bench",
  117. BTNS_OFF_X + BTN_W / 2,
  118. BTNS_OFF_Y + BTN_H / 2 + BTN_H + BTN_GAP,
  119. ui_status.light_bench ? TFT_GREEN : TFT_RED);
  120. // 3
  121. draw_button("Lights Kitchen",
  122. BTNS_OFF_X + BTN_W / 2,
  123. BTNS_OFF_Y + BTN_H / 2 + (BTN_H + BTN_GAP) * 2,
  124. ui_status.light_kitchen ? TFT_GREEN : TFT_RED);
  125. // 4
  126. draw_button("Lights Amp.",
  127. BTNS_OFF_X + BTN_W / 2 + BTN_W + BTN_GAP,
  128. BTNS_OFF_Y + BTN_H / 2,
  129. ui_status.light_amp ? TFT_GREEN : TFT_RED);
  130. // 5
  131. draw_button("Lights Box",
  132. BTNS_OFF_X + BTN_W / 2 + BTN_W + BTN_GAP,
  133. BTNS_OFF_Y + BTN_H / 2 + BTN_H + BTN_GAP,
  134. ui_status.light_box ? TFT_GREEN : TFT_RED);
  135. }
  136. static void draw_bathroom(void) {
  137. // 1
  138. draw_button("Bath Lights Auto",
  139. BTNS_OFF_X + BTN_W / 2,
  140. BTNS_OFF_Y + BTN_H / 2,
  141. ui_status.bathroom_lights == BATH_LIGHT_NONE ? TFT_GREEN : TFT_RED);
  142. // 2
  143. draw_button("Bath Lights Big",
  144. BTNS_OFF_X + BTN_W / 2,
  145. BTNS_OFF_Y + BTN_H / 2 + BTN_H + BTN_GAP,
  146. ui_status.bathroom_lights == BATH_LIGHT_BIG ? TFT_GREEN : TFT_RED);
  147. // 4
  148. draw_button("Bath Lights Off",
  149. BTNS_OFF_X + BTN_W / 2 + BTN_W + BTN_GAP,
  150. BTNS_OFF_Y + BTN_H / 2,
  151. ui_status.bathroom_lights == BATH_LIGHT_OFF ? TFT_GREEN : TFT_RED);
  152. // 5
  153. draw_button("Bath Lights Small",
  154. BTNS_OFF_X + BTN_W / 2 + BTN_W + BTN_GAP,
  155. BTNS_OFF_Y + BTN_H / 2 + BTN_H + BTN_GAP,
  156. ui_status.bathroom_lights == BATH_LIGHT_SMALL ? TFT_GREEN : TFT_RED);
  157. }
  158. static void draw_info(void) {
  159. tft.fillScreen(TFT_BLACK);
  160. tft.setTextDatum(TC_DATUM); // top center
  161. tft.drawString(ESP_PLATFORM_NAME " " NAME_OF_FEATURE " V" ESP_ENV_VERSION, LCD_WIDTH / 2, 0, 2);
  162. tft.drawString("by xythobuz.de", LCD_WIDTH / 2, 16, 2);
  163. tft.setTextDatum(TL_DATUM); // top left
  164. tft.drawString("Build Date: " __DATE__, 0, 40 + 16 * 0, 1);
  165. tft.drawString("Build Time: " __TIME__, 0, 40 + 16 * 1, 1);
  166. tft.drawString("Location: " SENSOR_LOCATION, 0, 40 + 16 * 2, 1);
  167. tft.drawString("ID: " SENSOR_ID, 0, 40 + 16 * 3, 1);
  168. tft.drawString("MAC: " + String(WiFi.macAddress()), 0, 40 + 16 * 4, 1);
  169. tft.drawString("Free heap: " + String(ESP.getFreeHeap() / 1024.0f) + "k", 0, 40 + 16 * 5, 1);
  170. tft.drawString("Free sketch space: " + String(ESP.getFreeSketchSpace() / 1024.0f) + "k", 0, 40 + 16 * 6, 1);
  171. tft.drawString("Flash chip size: " + String(ESP.getFlashChipSize() / 1024.0f) + "k", 0, 40 + 16 * 7, 1);
  172. tft.drawString("Uptime: " + String(millis() / 1000) + "sec", 0, 40 + 16 * 8, 1);
  173. tft.drawString("IPv4: " + WiFi.localIP().toString(), 0, 40 + 16 * 9, 1);
  174. tft.drawString("IPv6: " + WiFi.localIPv6().toString(), 0, 40 + 16 * 10, 1);
  175. tft.drawString("Hostname: " + String(SENSOR_HOSTNAME_PREFIX) + String(SENSOR_ID), 0, 40 + 16 * 11, 1);
  176. tft.drawString("LDR: " + String(ldr_value), 0, 40 + 16 * 12, 1);
  177. }
  178. void ui_init(void) {
  179. mySpi.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
  180. ts.begin(mySpi);
  181. ts.setRotation(1);
  182. tft.init();
  183. tft.setRotation(1);
  184. ledcSetup(LEDC_CHANNEL_0, LEDC_BASE_FREQ, LEDC_TIMER_12_BIT);
  185. ledcAttachPin(TFT_BL, LEDC_CHANNEL_0);
  186. ledcAnalogWrite(LEDC_CHANNEL_0, 255);
  187. pinMode(BTN_PIN, INPUT);
  188. pinMode(LDR_PIN, ANALOG);
  189. analogSetAttenuation(ADC_0db);
  190. analogReadResolution(12);
  191. analogSetPinAttenuation(LDR_PIN, ADC_0db);
  192. ldr_value = analogRead(LDR_PIN);
  193. ui_progress(UI_INIT);
  194. }
  195. static void ui_draw_menu(void) {
  196. switch (ui_page) {
  197. case UI_START:
  198. tft.fillScreen(TFT_BLACK);
  199. ui_page = UI_LIVINGROOM1;
  200. // fall-through
  201. case UI_LIVINGROOM1:
  202. draw_livingroom1();
  203. break;
  204. case UI_LIVINGROOM2:
  205. draw_livingroom2();
  206. break;
  207. case UI_BATHROOM:
  208. draw_bathroom();
  209. break;
  210. case UI_INFO:
  211. draw_info();
  212. return; // no next button
  213. default:
  214. ui_page = UI_START;
  215. ui_draw_menu();
  216. return;
  217. }
  218. 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);
  219. }
  220. void ui_progress(enum ui_state state) {
  221. int x = LCD_WIDTH / 2;
  222. int y = LCD_HEIGHT / 2;
  223. int fontSize = 2;
  224. switch (state) {
  225. case UI_INIT: {
  226. tft.fillScreen(TFT_BLACK);
  227. tft.setTextDatum(MC_DATUM); // middle center
  228. tft.drawString("Initializing ESP-ENV", x, y - 32, fontSize);
  229. tft.drawString("xythobuz.de", x, y, fontSize);
  230. } break;
  231. case UI_WIFI_CONNECT: {
  232. tft.setTextDatum(MC_DATUM); // middle center
  233. tft.drawString("Connecting to '" WIFI_SSID "'", x, y + 32, fontSize);
  234. } break;
  235. case UI_WIFI_CONNECTING: {
  236. static int n = 0;
  237. const char anim[] = { '\\', '|', '/', '-' };
  238. n++;
  239. if (n >= sizeof(anim)) {
  240. n = 0;
  241. }
  242. char s[2] = { anim[n], '\0' };
  243. tft.drawCentreString(s, x, y + 64, fontSize);
  244. } break;
  245. case UI_WIFI_CONNECTED: {
  246. tft.setTextDatum(MC_DATUM); // middle center
  247. tft.drawString("Connected!", x, y + 64, fontSize);
  248. } break;
  249. case UI_READY: {
  250. ui_page = UI_START;
  251. ui_draw_menu();
  252. } break;
  253. case UI_UPDATE: {
  254. ui_draw_menu();
  255. } break;
  256. }
  257. }
  258. void ui_run(void) {
  259. unsigned long now = millis();
  260. if (!digitalRead(BTN_PIN)) {
  261. ui_page = UI_INFO;
  262. }
  263. if (now >= (last_ldr + LDR_CHECK_MS)) {
  264. last_ldr = now;
  265. int ldr = analogRead(LDR_PIN);
  266. // TODO lowpass?
  267. //ldr_value = (ldr_value * 0.9f) + (ldr * 0.1f);
  268. ldr_value = ldr;
  269. if (ui_page == UI_INFO) {
  270. ui_draw_menu();
  271. }
  272. }
  273. bool touched = ts.tirqTouched() && ts.touched();
  274. TS_Point p;
  275. if (touched) {
  276. p = touchToScreen(ts.getPoint());
  277. // minimum pressure
  278. if (p.z < TOUCH_PRESSURE_MIN) {
  279. touched = false;
  280. }
  281. }
  282. if (touched && (!is_touched)) {
  283. is_touched = true;
  284. last_touch_time = millis();
  285. if (ui_page == UI_INFO) {
  286. // switch to next page, skip init and info screen
  287. do {
  288. ui_page = (enum ui_pages)((ui_page + 1) % UI_NUM_PAGES);
  289. } while ((ui_page == UI_START) || (ui_page == UI_INFO));
  290. tft.fillScreen(TFT_BLACK);
  291. ui_draw_menu();
  292. return;
  293. }
  294. 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)) {
  295. // 1
  296. if (ui_page == UI_LIVINGROOM1) {
  297. INVERT_BOOL(ui_status.light_corner);
  298. } else if (ui_page == UI_LIVINGROOM2) {
  299. INVERT_BOOL(ui_status.light_pc);
  300. } else if (ui_page == UI_BATHROOM) {
  301. ui_status.bathroom_lights = BATH_LIGHT_NONE;
  302. }
  303. writeMQTT_UI();
  304. } 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))) {
  305. // 2
  306. if (ui_page == UI_LIVINGROOM1) {
  307. INVERT_BOOL(ui_status.light_workspace);
  308. } else if (ui_page == UI_LIVINGROOM2) {
  309. INVERT_BOOL(ui_status.light_bench);
  310. } else if (ui_page == UI_BATHROOM) {
  311. ui_status.bathroom_lights = BATH_LIGHT_BIG;
  312. }
  313. writeMQTT_UI();
  314. } 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))) {
  315. // 3
  316. if (ui_page == UI_LIVINGROOM1) {
  317. INVERT_BOOL(ui_status.light_sink);
  318. } else if (ui_page == UI_LIVINGROOM2) {
  319. INVERT_BOOL(ui_status.light_kitchen);
  320. }
  321. writeMQTT_UI();
  322. } 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)) {
  323. // 4
  324. if (ui_page == UI_LIVINGROOM1) {
  325. INVERT_BOOL(ui_status.sound_amplifier);
  326. } else if (ui_page == UI_LIVINGROOM2) {
  327. INVERT_BOOL(ui_status.light_amp);
  328. } else if (ui_page == UI_BATHROOM) {
  329. ui_status.bathroom_lights = BATH_LIGHT_OFF;
  330. }
  331. writeMQTT_UI();
  332. } 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))) {
  333. // 5
  334. if (ui_page == UI_LIVINGROOM1) {
  335. bool on = ui_status.light_corner || ui_status.light_sink || ui_status.light_workspace
  336. || ui_status.light_amp || ui_status.light_bench || ui_status.light_box
  337. || ui_status.light_kitchen || ui_status.light_pc;
  338. if (on) {
  339. ui_status.light_amp = false;
  340. ui_status.light_kitchen = false;
  341. ui_status.light_bench= false;
  342. ui_status.light_workspace = false;
  343. ui_status.light_pc = false;
  344. ui_status.light_corner = false;
  345. ui_status.light_box = false;
  346. ui_status.light_sink = false;
  347. } else {
  348. ui_status.light_corner = true;
  349. ui_status.light_sink = true;
  350. }
  351. } else if (ui_page == UI_LIVINGROOM2) {
  352. INVERT_BOOL(ui_status.light_box);
  353. } else if (ui_page == UI_BATHROOM) {
  354. ui_status.bathroom_lights = BATH_LIGHT_SMALL;
  355. }
  356. writeMQTT_UI();
  357. } 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))) {
  358. // switch to next page, skip init and info screen
  359. do {
  360. ui_page = (enum ui_pages)((ui_page + 1) % UI_NUM_PAGES);
  361. } while ((ui_page == UI_START) || (ui_page == UI_INFO));
  362. tft.fillScreen(TFT_BLACK);
  363. }
  364. ui_draw_menu();
  365. } else if ((!touched) && is_touched && ((now - last_touch_time) >= MIN_TOUCH_DELAY_MS)) {
  366. is_touched = false;
  367. }
  368. }
  369. #endif // FEATURE_UI