/ RNode_Firmware_CE_G2 / Display.h
Display.h
1 // Copyright (C) 2024, Mark Qvist 2 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU General Public License as published by 5 // the Free Software Foundation, either version 3 of the License, or 6 // (at your option) any later version. 7 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU General Public License for more details. 12 13 // You should have received a copy of the GNU General Public License 14 // along with this program. If not, see <https://www.gnu.org/licenses/>. 15 16 #include "Graphics.h" 17 #include <Adafruit_GFX.h> 18 19 #if BOARD_MODEL != BOARD_TECHO 20 #if BOARD_MODEL == BOARD_TDECK 21 #include <Adafruit_ST7789.h> 22 #elif BOARD_MODEL == BOARD_HELTEC_T114 23 #include "ST7789.h" 24 #define COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3)) 25 #elif BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_STATION_G2 26 #include <Adafruit_SH110X.h> 27 #else 28 #include <Wire.h> 29 #include <Adafruit_SSD1306.h> 30 #endif 31 32 #else 33 void (*display_callback)(); 34 void display_add_callback(void (*callback)()) { display_callback = callback; } 35 void busyCallback(const void* p) { display_callback(); } 36 #define SSD1306_BLACK GxEPD_BLACK 37 #define SSD1306_WHITE GxEPD_WHITE 38 #include <GxEPD2_BW.h> 39 #include <SPI.h> 40 #endif 41 42 #include "Fonts/Org_01.h" 43 #define DISP_W 128 44 #define DISP_H 64 45 46 #if BOARD_MODEL == BOARD_RNODE_NG_20 || BOARD_MODEL == BOARD_LORA32_V2_0 47 #define DISP_RST -1 48 #define DISP_ADDR 0x3C 49 #elif BOARD_MODEL == BOARD_TBEAM 50 #define DISP_RST 13 51 #define DISP_ADDR 0x3C 52 #define DISP_CUSTOM_ADDR true 53 #elif BOARD_MODEL == BOARD_HELTEC32_V2 || BOARD_MODEL == BOARD_LORA32_V1_0 54 #define DISP_RST 16 55 #define DISP_ADDR 0x3C 56 #define SCL_OLED 15 57 #define SDA_OLED 4 58 #elif BOARD_MODEL == BOARD_HELTEC32_V3 59 #define DISP_RST 21 60 #define DISP_ADDR 0x3C 61 #define SCL_OLED 18 62 #define SDA_OLED 17 63 #elif BOARD_MODEL == BOARD_HELTEC32_V4 64 #define DISP_RST 21 65 #define DISP_ADDR 0x3C 66 #define SCL_OLED 18 67 #define SDA_OLED 17 68 #elif BOARD_MODEL == BOARD_RAK4631 69 // RAK1921/SSD1306 70 #define DISP_RST -1 71 #define DISP_ADDR 0x3C 72 #define SCL_OLED 14 73 #define SDA_OLED 13 74 #elif BOARD_MODEL == BOARD_RNODE_NG_21 75 #define DISP_RST -1 76 #define DISP_ADDR 0x3C 77 #elif BOARD_MODEL == BOARD_T3S3 78 #define DISP_RST 21 79 #define DISP_ADDR 0x3C 80 #define SCL_OLED 17 81 #define SDA_OLED 18 82 #elif BOARD_MODEL == BOARD_STATION_G2 83 #define DISP_RST -1 84 #define DISP_ADDR 0x3C 85 #define SCL_OLED 6 86 #define SDA_OLED 5 87 #define DISP_CUSTOM_ADDR false 88 #elif BOARD_MODEL == BOARD_TECHO 89 SPIClass displaySPI = SPIClass(NRF_SPIM0, pin_disp_miso, pin_disp_sck, pin_disp_mosi); 90 #define DISP_W 128 91 #define DISP_H 64 92 #define DISP_ADDR -1 93 #elif BOARD_MODEL == BOARD_TBEAM_S_V1 94 #define DISP_RST -1 95 #define DISP_ADDR 0x3C 96 #define SCL_OLED 18 97 #define SDA_OLED 17 98 #define DISP_CUSTOM_ADDR false 99 #elif BOARD_MODEL == BOARD_XIAO_S3 100 #define DISP_RST -1 101 #define DISP_ADDR 0x3C 102 #define SCL_OLED 6 103 #define SDA_OLED 5 104 #define DISP_CUSTOM_ADDR true 105 #else 106 #define DISP_RST -1 107 #define DISP_ADDR 0x3C 108 #define DISP_CUSTOM_ADDR true 109 #endif 110 111 #define SMALL_FONT &Org_01 112 113 #if BOARD_MODEL == BOARD_TDECK 114 Adafruit_ST7789 display = Adafruit_ST7789(DISPLAY_CS, DISPLAY_DC, -1); 115 #define SSD1306_WHITE ST77XX_WHITE 116 #define SSD1306_BLACK ST77XX_BLACK 117 #elif BOARD_MODEL == BOARD_HELTEC_T114 118 ST7789Spi display(&SPI1, DISPLAY_RST, DISPLAY_DC, DISPLAY_CS); 119 #define SSD1306_WHITE ST77XX_WHITE 120 #define SSD1306_BLACK ST77XX_BLACK 121 #elif BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_STATION_G2 122 Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire, -1); 123 #define SSD1306_WHITE SH110X_WHITE 124 #define SSD1306_BLACK SH110X_BLACK 125 #elif BOARD_MODEL == BOARD_TECHO 126 GxEPD2_BW<GxEPD2_154_D67, GxEPD2_154_D67::HEIGHT> display(GxEPD2_154_D67(pin_disp_cs, pin_disp_dc, pin_disp_reset, pin_disp_busy)); 127 uint32_t last_epd_refresh = 0; 128 uint32_t last_epd_full_refresh = 0; 129 #define REFRESH_PERIOD 300000 130 #else 131 Adafruit_SSD1306 display(DISP_W, DISP_H, &Wire, DISP_RST); 132 #endif 133 134 #if BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4 135 float disp_target_fps = 5; 136 #else 137 float disp_target_fps = 7; 138 #endif 139 float epd_update_fps = 0.5; 140 141 #define DISP_MODE_UNKNOWN 0x00 142 #define DISP_MODE_LANDSCAPE 0x01 143 #define DISP_MODE_PORTRAIT 0x02 144 #define DISP_PIN_SIZE 6 145 #define DISPLAY_BLANKING_TIMEOUT 15*1000 146 uint8_t disp_mode = DISP_MODE_UNKNOWN; 147 uint8_t disp_ext_fb = false; 148 unsigned char fb[512]; 149 uint32_t last_disp_update = 0; 150 uint32_t last_unblank_event = 0; 151 uint32_t display_blanking_timeout = DISPLAY_BLANKING_TIMEOUT; 152 uint8_t display_unblank_intensity = display_intensity; 153 bool display_blanked = false; 154 bool display_tx = false; 155 bool recondition_display = false; 156 int disp_update_interval = 1000/disp_target_fps; 157 int epd_update_interval = 1000/disp_target_fps; 158 // While blanked, refresh far less often (OLED is off/dim); big idle save vs full FPS. 159 #define DISP_BLANK_UPDATE_INTERVAL_MS 2000 160 int disp_blank_update_interval = DISP_BLANK_UPDATE_INTERVAL_MS; 161 uint32_t last_page_flip = 0; 162 int page_interval = 4000; 163 bool device_signatures_ok(); 164 bool device_firmware_ok(); 165 166 #define WATERFALL_SIZE 46 167 int waterfall[WATERFALL_SIZE]; 168 int waterfall_head = 0; 169 170 int p_ad_x = 0; 171 int p_ad_y = 0; 172 int p_as_x = 0; 173 int p_as_y = 0; 174 175 GFXcanvas1 stat_area(64, 64); 176 GFXcanvas1 disp_area(64, 64); 177 178 static const uint8_t one_counts[256] = { 179 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 1, 1, 1, 180 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 181 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 182 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 183 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 184 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 185 0, 0, 0, 0, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 186 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 187 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 188 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 189 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 190 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 191 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 192 0, 0, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 193 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 194 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 195 }; 196 197 void fillRect(int16_t x, int16_t y, int16_t width, int16_t height, uint16_t colour); 198 199 void update_area_positions() { 200 #if BOARD_MODEL == BOARD_HELTEC_T114 201 if (disp_mode == DISP_MODE_PORTRAIT) { 202 p_ad_x = 16; 203 p_ad_y = 64; 204 p_as_x = 16; 205 p_as_y = p_ad_y+126; 206 } else if (disp_mode == DISP_MODE_LANDSCAPE) { 207 p_ad_x = 0; 208 p_ad_y = 96; 209 p_as_x = 126; 210 p_as_y = p_ad_y; 211 } 212 #elif BOARD_MODEL == BOARD_TECHO 213 if (disp_mode == DISP_MODE_PORTRAIT) { 214 p_ad_x = 61; 215 p_ad_y = 36; 216 p_as_x = 64; 217 p_as_y = 64+36; 218 } else if (disp_mode == DISP_MODE_LANDSCAPE) { 219 p_ad_x = 0; 220 p_ad_y = 0; 221 p_as_x = 64; 222 p_as_y = 0; 223 } 224 #else 225 if (disp_mode == DISP_MODE_PORTRAIT) { 226 p_ad_x = 0 * DISPLAY_SCALE; 227 p_ad_y = 0 * DISPLAY_SCALE; 228 p_as_x = 0 * DISPLAY_SCALE; 229 p_as_y = 64 * DISPLAY_SCALE; 230 } else if (disp_mode == DISP_MODE_LANDSCAPE) { 231 p_ad_x = 0 * DISPLAY_SCALE; 232 p_ad_y = 0 * DISPLAY_SCALE; 233 p_as_x = 64 * DISPLAY_SCALE; 234 p_as_y = 0 * DISPLAY_SCALE; 235 } 236 #endif 237 } 238 239 uint8_t display_contrast = 0x00; 240 #if BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_STATION_G2 241 void set_contrast(Adafruit_SH1106G *display, uint8_t value) { 242 } 243 #elif BOARD_MODEL == BOARD_HELTEC_T114 244 void set_contrast(ST7789Spi *display, uint8_t value) { } 245 #elif BOARD_MODEL == BOARD_TECHO 246 void set_contrast(void *display, uint8_t value) { 247 if (value == 0) { analogWrite(pin_backlight, 0); } 248 else { analogWrite(pin_backlight, value); } 249 } 250 #elif BOARD_MODEL == BOARD_TDECK 251 void set_contrast(Adafruit_ST7789 *display, uint8_t value) { 252 static uint8_t level = 0; 253 static uint8_t steps = 16; 254 if (value > 15) value = 15; 255 if (value == 0) { 256 digitalWrite(DISPLAY_BL_PIN, 0); 257 delay(3); 258 level = 0; 259 return; 260 } 261 if (level == 0) { 262 digitalWrite(DISPLAY_BL_PIN, 1); 263 level = steps; 264 delayMicroseconds(30); 265 } 266 int from = steps - level; 267 int to = steps - value; 268 int num = (steps + to - from) % steps; 269 for (int i = 0; i < num; i++) { 270 digitalWrite(DISPLAY_BL_PIN, 0); 271 digitalWrite(DISPLAY_BL_PIN, 1); 272 } 273 level = value; 274 } 275 #else 276 void set_contrast(Adafruit_SSD1306 *display, uint8_t contrast) { 277 display->ssd1306_command(SSD1306_SETCONTRAST); 278 display->ssd1306_command(contrast); 279 } 280 #endif 281 282 bool display_init() { 283 #if HAS_DISPLAY 284 #if BOARD_MODEL == BOARD_RNODE_NG_20 || BOARD_MODEL == BOARD_LORA32_V2_0 285 int pin_display_en = 16; 286 digitalWrite(pin_display_en, LOW); 287 delay(50); 288 digitalWrite(pin_display_en, HIGH); 289 #elif BOARD_MODEL == BOARD_T3S3 290 Wire.begin(SDA_OLED, SCL_OLED); 291 #elif BOARD_MODEL == BOARD_HELTEC32_V2 292 Wire.begin(SDA_OLED, SCL_OLED); 293 #elif BOARD_MODEL == BOARD_HELTEC32_V3 294 // enable vext / pin 36 295 pinMode(Vext, OUTPUT); 296 digitalWrite(Vext, LOW); 297 delay(50); 298 int pin_display_en = 21; 299 pinMode(pin_display_en, OUTPUT); 300 digitalWrite(pin_display_en, LOW); 301 delay(50); 302 digitalWrite(pin_display_en, HIGH); 303 delay(50); 304 Wire.begin(SDA_OLED, SCL_OLED); 305 #elif BOARD_MODEL == BOARD_HELTEC32_V4 306 // enable vext / pin 36 307 pinMode(Vext, OUTPUT); 308 digitalWrite(Vext, LOW); 309 delay(50); 310 int pin_display_en = 21; 311 pinMode(pin_display_en, OUTPUT); 312 digitalWrite(pin_display_en, LOW); 313 delay(50); 314 digitalWrite(pin_display_en, HIGH); 315 delay(50); 316 Wire.begin(SDA_OLED, SCL_OLED); 317 #elif BOARD_MODEL == BOARD_LORA32_V1_0 318 int pin_display_en = 16; 319 digitalWrite(pin_display_en, LOW); 320 delay(50); 321 digitalWrite(pin_display_en, HIGH); 322 Wire.begin(SDA_OLED, SCL_OLED); 323 #elif BOARD_MODEL == BOARD_HELTEC_T114 324 pinMode(PIN_T114_TFT_EN, OUTPUT); 325 digitalWrite(PIN_T114_TFT_EN, LOW); 326 #elif BOARD_MODEL == BOARD_TECHO 327 display.init(0, true, 10, false, displaySPI, SPISettings(4000000, MSBFIRST, SPI_MODE0)); 328 display.setPartialWindow(0, 0, DISP_W, DISP_H); 329 display.epd2.setBusyCallback(busyCallback); 330 #if HAS_BACKLIGHT 331 pinMode(pin_backlight, OUTPUT); 332 analogWrite(pin_backlight, 0); 333 #endif 334 #elif BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_STATION_G2 335 Wire.begin(SDA_OLED, SCL_OLED); 336 #elif BOARD_MODEL == BOARD_XIAO_S3 337 Wire.begin(SDA_OLED, SCL_OLED); 338 #endif 339 340 #if HAS_EEPROM 341 uint8_t display_rotation = EEPROM.read(eeprom_addr(ADDR_CONF_DROT)); 342 #elif MCU_VARIANT == MCU_NRF52 343 uint8_t display_rotation = eeprom_read(eeprom_addr(ADDR_CONF_DROT)); 344 #endif 345 if (display_rotation < 0 or display_rotation > 3) display_rotation = 0xFF; 346 347 #if DISP_CUSTOM_ADDR == true 348 #if HAS_EEPROM 349 uint8_t display_address = EEPROM.read(eeprom_addr(ADDR_CONF_DADR)); 350 #elif MCU_VARIANT == MCU_NRF52 351 uint8_t display_address = eeprom_read(eeprom_addr(ADDR_CONF_DADR)); 352 #endif 353 if (display_address == 0xFF) display_address = DISP_ADDR; 354 #else 355 uint8_t display_address = DISP_ADDR; 356 #endif 357 358 #if HAS_EEPROM 359 if (EEPROM.read(eeprom_addr(ADDR_CONF_BSET)) == CONF_OK_BYTE) { 360 uint8_t db_timeout = EEPROM.read(eeprom_addr(ADDR_CONF_DBLK)); 361 if (db_timeout == 0x00) { 362 display_blanking_enabled = false; 363 } else { 364 display_blanking_enabled = true; 365 display_blanking_timeout = db_timeout*1000; 366 } 367 } 368 #elif MCU_VARIANT == MCU_NRF52 369 if (eeprom_read(eeprom_addr(ADDR_CONF_BSET)) == CONF_OK_BYTE) { 370 uint8_t db_timeout = eeprom_read(eeprom_addr(ADDR_CONF_DBLK)); 371 if (db_timeout == 0x00) { 372 display_blanking_enabled = false; 373 } else { 374 display_blanking_enabled = true; 375 display_blanking_timeout = db_timeout*1000; 376 } 377 } 378 #endif 379 380 #if HAS_EEPROM && (BOARD_MODEL == BOARD_HELTEC32_V3 || BOARD_MODEL == BOARD_HELTEC32_V4) 381 // If blanking was never stored in EEPROM, default to 15s idle blank (see DISPLAY_BLANKING_TIMEOUT). 382 if (EEPROM.read(eeprom_addr(ADDR_CONF_BSET)) != CONF_OK_BYTE) { 383 display_blanking_enabled = true; 384 display_blanking_timeout = DISPLAY_BLANKING_TIMEOUT; 385 } 386 #endif 387 388 #if BOARD_MODEL == BOARD_TECHO 389 // Don't check if display is actually connected 390 if(false) { 391 #elif BOARD_MODEL == BOARD_TDECK 392 display.init(240, 320); 393 display.setSPISpeed(80e6); 394 #elif BOARD_MODEL == BOARD_HELTEC_T114 395 display.init(); 396 // set white as default pixel colour for Heltec T114 397 display.setRGB(COLOR565(0xFF, 0xFF, 0xFF)); 398 if (false) { 399 #elif BOARD_MODEL == BOARD_TBEAM_S_V1 || BOARD_MODEL == BOARD_STATION_G2 400 if (!display.begin(display_address, true)) { 401 #else 402 if (!display.begin(SSD1306_SWITCHCAPVCC, display_address)) { 403 #endif 404 return false; 405 } else { 406 set_contrast(&display, display_contrast); 407 if (display_rotation != 0xFF) { 408 if (display_rotation == 0 || display_rotation == 2) { 409 disp_mode = DISP_MODE_LANDSCAPE; 410 } else { 411 disp_mode = DISP_MODE_PORTRAIT; 412 } 413 display.setRotation(display_rotation); 414 } else { 415 #if BOARD_MODEL == BOARD_RNODE_NG_20 416 disp_mode = DISP_MODE_PORTRAIT; 417 display.setRotation(3); 418 #elif BOARD_MODEL == BOARD_RNODE_NG_21 419 disp_mode = DISP_MODE_PORTRAIT; 420 display.setRotation(3); 421 #elif BOARD_MODEL == BOARD_LORA32_V1_0 422 disp_mode = DISP_MODE_PORTRAIT; 423 display.setRotation(3); 424 #elif BOARD_MODEL == BOARD_LORA32_V2_0 425 disp_mode = DISP_MODE_PORTRAIT; 426 display.setRotation(3); 427 #elif BOARD_MODEL == BOARD_LORA32_V2_1 428 disp_mode = DISP_MODE_LANDSCAPE; 429 display.setRotation(0); 430 #elif BOARD_MODEL == BOARD_TBEAM 431 disp_mode = DISP_MODE_LANDSCAPE; 432 display.setRotation(0); 433 #elif BOARD_MODEL == BOARD_TBEAM_S_V1 434 disp_mode = DISP_MODE_PORTRAIT; 435 display.setRotation(1); 436 #elif BOARD_MODEL == BOARD_STATION_G2 437 disp_mode = DISP_MODE_LANDSCAPE; 438 display.setRotation(0); 439 #elif BOARD_MODEL == BOARD_HELTEC32_V2 440 disp_mode = DISP_MODE_PORTRAIT; 441 display.setRotation(1); 442 #elif BOARD_MODEL == BOARD_HELTEC32_V3 443 disp_mode = DISP_MODE_PORTRAIT; 444 display.setRotation(1); 445 #elif BOARD_MODEL == BOARD_HELTEC32_V4 446 disp_mode = DISP_MODE_PORTRAIT; 447 display.setRotation(1); 448 #elif BOARD_MODEL == BOARD_HELTEC_T114 449 disp_mode = DISP_MODE_PORTRAIT; 450 display.setRotation(1); 451 #elif BOARD_MODEL == BOARD_RAK4631 452 disp_mode = DISP_MODE_LANDSCAPE; 453 display.setRotation(0); 454 #elif BOARD_MODEL == BOARD_TDECK 455 disp_mode = DISP_MODE_PORTRAIT; 456 display.setRotation(3); 457 #elif BOARD_MODEL == BOARD_TECHO 458 disp_mode = DISP_MODE_PORTRAIT; 459 display.setRotation(3); 460 #else 461 disp_mode = DISP_MODE_PORTRAIT; 462 display.setRotation(3); 463 #endif 464 } 465 466 update_area_positions(); 467 468 for (int i = 0; i < WATERFALL_SIZE; i++) { waterfall[i] = 0; } 469 470 last_page_flip = millis(); 471 472 stat_area.cp437(true); 473 disp_area.cp437(true); 474 475 #if BOARD_MODEL != BOARD_HELTEC_T114 476 display.cp437(true); 477 #endif 478 479 #if HAS_EEPROM 480 display_intensity = EEPROM.read(eeprom_addr(ADDR_CONF_DINT)); 481 #elif MCU_VARIANT == MCU_NRF52 482 display_intensity = eeprom_read(eeprom_addr(ADDR_CONF_DINT)); 483 #endif 484 display_unblank_intensity = display_intensity; 485 486 #if BOARD_MODEL == BOARD_TECHO 487 #if HAS_BACKLIGHT 488 if (display_intensity == 0) { analogWrite(pin_backlight, 0); } 489 else { analogWrite(pin_backlight, display_intensity); } 490 #endif 491 #endif 492 493 #if BOARD_MODEL == BOARD_TDECK 494 display.fillScreen(SSD1306_BLACK); 495 #endif 496 497 #if BOARD_MODEL == BOARD_HELTEC_T114 498 // Enable backlight led (display is always black without this) 499 fillRect(p_ad_x, p_ad_y, 128, 128, SSD1306_BLACK); 500 fillRect(p_as_x, p_as_y, 128, 128, SSD1306_BLACK); 501 pinMode(PIN_T114_TFT_BLGT, OUTPUT); 502 digitalWrite(PIN_T114_TFT_BLGT, LOW); 503 #endif 504 505 return true; 506 } 507 #else 508 return false; 509 #endif 510 } 511 512 // Draws a line on the screen 513 void drawLine(int16_t x, int16_t y, int16_t width, int16_t height, uint16_t colour) { 514 #if BOARD_MODEL == BOARD_HELTEC_T114 515 if(colour == SSD1306_WHITE){ 516 display.setColor(WHITE); 517 } else if(colour == SSD1306_BLACK) { 518 display.setColor(BLACK); 519 } 520 display.drawLine(x, y, width, height); 521 #else 522 display.drawLine(x, y, width, height, colour); 523 #endif 524 } 525 526 // Draws a filled rectangle on the screen 527 void fillRect(int16_t x, int16_t y, int16_t width, int16_t height, uint16_t colour) { 528 #if BOARD_MODEL == BOARD_HELTEC_T114 529 if(colour == SSD1306_WHITE){ 530 display.setColor(WHITE); 531 } else if(colour == SSD1306_BLACK) { 532 display.setColor(BLACK); 533 } 534 display.fillRect(x, y, width, height); 535 #else 536 display.fillRect(x, y, width, height, colour); 537 #endif 538 } 539 540 // Draws a bitmap to the display and auto scales it based on the boards configured DISPLAY_SCALE 541 void drawBitmap(int16_t startX, int16_t startY, const uint8_t* bitmap, int16_t bitmapWidth, int16_t bitmapHeight, uint16_t foregroundColour, uint16_t backgroundColour) { 542 #if DISPLAY_SCALE == 1 543 display.drawBitmap(startX, startY, bitmap, bitmapWidth, bitmapHeight, foregroundColour, backgroundColour); 544 #else 545 for(int16_t row = 0; row < bitmapHeight; row++){ 546 for(int16_t col = 0; col < bitmapWidth; col++){ 547 548 // determine index and bitmask 549 int16_t index = row * ((bitmapWidth + 7) / 8) + (col / 8); 550 uint8_t bitmask = 1 << (7 - (col % 8)); 551 552 // check if the current pixel is set in the bitmap 553 if(bitmap[index] & bitmask){ 554 // draw a scaled rectangle for the foreground pixel 555 fillRect(startX + col * DISPLAY_SCALE, startY + row * DISPLAY_SCALE, DISPLAY_SCALE, DISPLAY_SCALE, foregroundColour); 556 } else { 557 // draw a scaled rectangle for the background pixel 558 fillRect(startX + col * DISPLAY_SCALE, startY + row * DISPLAY_SCALE, DISPLAY_SCALE, DISPLAY_SCALE, backgroundColour); 559 } 560 561 } 562 } 563 #endif 564 } 565 566 extern uint8_t wifi_mode; 567 extern bool wifi_is_connected(); 568 extern bool wifi_host_is_connected(); 569 void draw_cable_icon(int px, int py) { 570 #if HAS_WIFI 571 if (wifi_mode == WR_WIFI_OFF) { 572 if (cable_state == CABLE_STATE_DISCONNECTED) { stat_area.drawBitmap(px, py, bm_cable+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } 573 else if (cable_state == CABLE_STATE_CONNECTED) { stat_area.drawBitmap(px, py, bm_cable+1*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } 574 } else { 575 if (wifi_mode == WR_WIFI_STA) { 576 if (wifi_is_connected()) { 577 stat_area.drawBitmap(px, py, bm_wifi+3*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); 578 if (!wifi_host_is_connected()) { stat_area.fillRect(px+5, py+12, 6, 3, SSD1306_BLACK); } 579 } else { stat_area.drawBitmap(px, py, bm_wifi+2*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } 580 581 } else if (wifi_mode == WR_WIFI_AP) { 582 if (wifi_host_is_connected()) { stat_area.drawBitmap(px, py, bm_wifi+1*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } 583 else { stat_area.drawBitmap(px, py, bm_wifi+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } 584 585 } else { 586 if (cable_state == CABLE_STATE_DISCONNECTED) { stat_area.drawBitmap(px, py, bm_cable+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } 587 else if (cable_state == CABLE_STATE_CONNECTED) { stat_area.drawBitmap(px, py, bm_cable+1*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } 588 } 589 } 590 591 #else 592 if (cable_state == CABLE_STATE_DISCONNECTED) { stat_area.drawBitmap(px, py, bm_cable+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } 593 else if (cable_state == CABLE_STATE_CONNECTED) { stat_area.drawBitmap(px, py, bm_cable+1*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); } 594 #endif 595 } 596 597 void draw_bt_icon(int px, int py) { 598 if (bt_state == BT_STATE_OFF) { 599 stat_area.drawBitmap(px, py, bm_bt+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); 600 } else if (bt_state == BT_STATE_ON) { 601 stat_area.drawBitmap(px, py, bm_bt+1*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); 602 } else if (bt_state == BT_STATE_PAIRING) { 603 stat_area.drawBitmap(px, py, bm_bt+2*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); 604 } else if (bt_state == BT_STATE_CONNECTED) { 605 stat_area.drawBitmap(px, py, bm_bt+3*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); 606 } else { 607 stat_area.drawBitmap(px, py, bm_bt+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); 608 } 609 } 610 611 void draw_lora_icon(int px, int py) { 612 if (radio_online) { 613 stat_area.drawBitmap(px, py, bm_rf+1*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); 614 } else { 615 stat_area.drawBitmap(px, py, bm_rf+0*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); 616 } 617 } 618 619 void draw_mw_icon(int px, int py) { 620 if (mw_radio_online) { 621 stat_area.drawBitmap(px, py, bm_rf+3*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); 622 } else { 623 stat_area.drawBitmap(px, py, bm_rf+2*32, 16, 16, SSD1306_WHITE, SSD1306_BLACK); 624 } 625 } 626 627 uint8_t charge_tick = 0; 628 void draw_battery_bars(int px, int py) { 629 if (pmu_ready) { 630 if (battery_ready) { 631 if (battery_installed) { 632 float battery_value = battery_percent; 633 634 // Disable charging state display for now, since 635 // boards without dedicated PMU are completely 636 // unreliable for determining actual charging state. 637 bool disable_charge_status = false; 638 if (battery_indeterminate && battery_state == BATTERY_STATE_CHARGING) { 639 disable_charge_status = true; 640 } 641 642 if (battery_state == BATTERY_STATE_CHARGING && !disable_charge_status) { 643 float battery_prog = battery_percent; 644 if (battery_prog > 85) { battery_prog = 84; } 645 if (charge_tick < battery_prog ) { charge_tick = battery_prog; } 646 battery_value = charge_tick; 647 charge_tick += 3; 648 if (charge_tick > 100) charge_tick = 0; 649 } 650 651 if (battery_indeterminate && battery_state == BATTERY_STATE_CHARGING && !disable_charge_status) { 652 stat_area.fillRect(px-2, py-2, 18, 7, SSD1306_BLACK); 653 stat_area.drawBitmap(px-2, py-2, bm_plug, 17, 7, SSD1306_WHITE, SSD1306_BLACK); 654 } else { 655 if (battery_state == BATTERY_STATE_CHARGED) { 656 stat_area.fillRect(px-2, py-2, 18, 7, SSD1306_BLACK); 657 stat_area.drawBitmap(px-2, py-2, bm_plug, 17, 7, SSD1306_WHITE, SSD1306_BLACK); 658 } else { 659 // stat_area.fillRect(px, py, 14, 3, SSD1306_BLACK); 660 stat_area.fillRect(px-2, py-2, 18, 7, SSD1306_BLACK); 661 stat_area.drawRect(px-2, py-2, 17, 7, SSD1306_WHITE); 662 stat_area.drawLine(px+15, py, px+15, py+3, SSD1306_WHITE); 663 if (battery_value > 7) stat_area.drawLine(px, py, px, py+2, SSD1306_WHITE); 664 if (battery_value > 20) stat_area.drawLine(px+1*2, py, px+1*2, py+2, SSD1306_WHITE); 665 if (battery_value > 33) stat_area.drawLine(px+2*2, py, px+2*2, py+2, SSD1306_WHITE); 666 if (battery_value > 46) stat_area.drawLine(px+3*2, py, px+3*2, py+2, SSD1306_WHITE); 667 if (battery_value > 59) stat_area.drawLine(px+4*2, py, px+4*2, py+2, SSD1306_WHITE); 668 if (battery_value > 72) stat_area.drawLine(px+5*2, py, px+5*2, py+2, SSD1306_WHITE); 669 if (battery_value > 85) stat_area.drawLine(px+6*2, py, px+6*2, py+2, SSD1306_WHITE); 670 } 671 } 672 } else { 673 stat_area.fillRect(px-2, py-2, 18, 7, SSD1306_BLACK); 674 stat_area.drawBitmap(px-2, py-2, bm_plug, 17, 7, SSD1306_WHITE, SSD1306_BLACK); 675 } 676 } 677 } else { 678 stat_area.fillRect(px-2, py-2, 18, 7, SSD1306_BLACK); 679 stat_area.drawBitmap(px-2, py-2, bm_plug, 17, 7, SSD1306_WHITE, SSD1306_BLACK); 680 } 681 } 682 683 #define Q_SNR_STEP 2.0 684 #define Q_SNR_MIN_BASE -9.0 685 #define Q_SNR_MAX 6.0 686 void draw_quality_bars(int px, int py) { 687 stat_area.fillRect(px, py, 13, 7, SSD1306_BLACK); 688 if (radio_online) { 689 signed char t_snr = (signed int)last_snr_raw; 690 int snr_int = (int)t_snr; 691 float snr_min = Q_SNR_MIN_BASE-(int)lora_sf*Q_SNR_STEP; 692 float snr_span = (Q_SNR_MAX-snr_min); 693 float snr = ((int)snr_int) * 0.25; 694 float quality = ((snr-snr_min)/(snr_span))*100; 695 if (quality > 100.0) quality = 100.0; 696 if (quality < 0.0) quality = 0.0; 697 698 // Serial.printf("Last SNR: %.2f\n, quality: %.2f\n", snr, quality); 699 if (quality > 0) stat_area.drawLine(px+0*2, py+7, px+0*2, py+6, SSD1306_WHITE); 700 if (quality > 15) stat_area.drawLine(px+1*2, py+7, px+1*2, py+5, SSD1306_WHITE); 701 if (quality > 30) stat_area.drawLine(px+2*2, py+7, px+2*2, py+4, SSD1306_WHITE); 702 if (quality > 45) stat_area.drawLine(px+3*2, py+7, px+3*2, py+3, SSD1306_WHITE); 703 if (quality > 60) stat_area.drawLine(px+4*2, py+7, px+4*2, py+2, SSD1306_WHITE); 704 if (quality > 75) stat_area.drawLine(px+5*2, py+7, px+5*2, py+1, SSD1306_WHITE); 705 if (quality > 90) stat_area.drawLine(px+6*2, py+7, px+6*2, py+0, SSD1306_WHITE); 706 } 707 } 708 709 #if MODEM == SX1280 710 #define S_RSSI_MIN -105.0 711 #define S_RSSI_MAX -65.0 712 #else 713 #define S_RSSI_MIN -135.0 714 #define S_RSSI_MAX -75.0 715 #endif 716 #define S_RSSI_SPAN (S_RSSI_MAX-S_RSSI_MIN) 717 void draw_signal_bars(int px, int py) { 718 stat_area.fillRect(px, py, 13, 7, SSD1306_BLACK); 719 720 if (radio_online) { 721 int rssi_val = last_rssi; 722 if (rssi_val < S_RSSI_MIN) rssi_val = S_RSSI_MIN; 723 if (rssi_val > S_RSSI_MAX) rssi_val = S_RSSI_MAX; 724 int signal = ((rssi_val - S_RSSI_MIN)*(1.0/S_RSSI_SPAN))*100.0; 725 726 if (signal > 100.0) signal = 100.0; 727 if (signal < 0.0) signal = 0.0; 728 729 // Serial.printf("Last SNR: %.2f\n, quality: %.2f\n", snr, quality); 730 if (signal > 85) stat_area.drawLine(px+0*2, py+7, px+0*2, py+0, SSD1306_WHITE); 731 if (signal > 72) stat_area.drawLine(px+1*2, py+7, px+1*2, py+1, SSD1306_WHITE); 732 if (signal > 59) stat_area.drawLine(px+2*2, py+7, px+2*2, py+2, SSD1306_WHITE); 733 if (signal > 46) stat_area.drawLine(px+3*2, py+7, px+3*2, py+3, SSD1306_WHITE); 734 if (signal > 33) stat_area.drawLine(px+4*2, py+7, px+4*2, py+4, SSD1306_WHITE); 735 if (signal > 20) stat_area.drawLine(px+5*2, py+7, px+5*2, py+5, SSD1306_WHITE); 736 if (signal > 7) stat_area.drawLine(px+6*2, py+7, px+6*2, py+6, SSD1306_WHITE); 737 } 738 } 739 740 #if MODEM == SX1280 741 #define WF_TX_SIZE 5 742 #else 743 #define WF_TX_SIZE 5 744 #endif 745 #define WF_RSSI_MAX -60 746 #define WF_RSSI_MIN -135 747 #define WF_RSSI_SPAN (WF_RSSI_MAX-WF_RSSI_MIN) 748 #define WF_PIXEL_WIDTH 10 749 void draw_waterfall(int px, int py) { 750 int rssi_val = current_rssi; 751 if (rssi_val < WF_RSSI_MIN) rssi_val = WF_RSSI_MIN; 752 if (rssi_val > WF_RSSI_MAX) rssi_val = WF_RSSI_MAX; 753 int rssi_normalised = ((rssi_val - WF_RSSI_MIN)*(1.0/WF_RSSI_SPAN))*WF_PIXEL_WIDTH; 754 if (display_tx) { 755 for (uint8_t i = 0; i < WF_TX_SIZE; i++) { 756 waterfall[waterfall_head++] = -1; 757 if (waterfall_head >= WATERFALL_SIZE) waterfall_head = 0; 758 } 759 display_tx = false; 760 } else { 761 waterfall[waterfall_head++] = rssi_normalised; 762 if (waterfall_head >= WATERFALL_SIZE) waterfall_head = 0; 763 } 764 765 stat_area.fillRect(px,py,WF_PIXEL_WIDTH, WATERFALL_SIZE, SSD1306_BLACK); 766 for (int i = 0; i < WATERFALL_SIZE; i++){ 767 int wi = (waterfall_head+i)%WATERFALL_SIZE; 768 int ws = waterfall[wi]; 769 if (ws > 0) { 770 stat_area.drawLine(px, py+i, px+ws-1, py+i, SSD1306_WHITE); 771 } else if (ws == -1) { 772 uint8_t o = i%2; 773 for (uint8_t ti = 0; ti < WF_PIXEL_WIDTH/2; ti++) { 774 stat_area.drawPixel(px+ti*2+o, py+i, SSD1306_WHITE); 775 } 776 } 777 } 778 } 779 780 bool stat_area_intialised = false; 781 void draw_stat_area() { 782 if (device_init_done) { 783 if (!stat_area_intialised) { 784 stat_area.drawBitmap(0, 0, bm_frame, 64, 64, SSD1306_WHITE, SSD1306_BLACK); 785 stat_area_intialised = true; 786 } 787 788 draw_cable_icon(3, 8); 789 draw_bt_icon(3, 30); 790 draw_lora_icon(45, 8); 791 draw_mw_icon(45, 30); 792 draw_battery_bars(4, 58); 793 draw_quality_bars(28, 56); 794 draw_signal_bars(44, 56); 795 if (radio_online) { 796 draw_waterfall(27, 4); 797 } 798 } 799 } 800 801 void update_stat_area() { 802 if (eeprom_ok && !firmware_update_mode && !console_active) { 803 804 draw_stat_area(); 805 if (disp_mode == DISP_MODE_PORTRAIT) { 806 drawBitmap(p_as_x, p_as_y, stat_area.getBuffer(), stat_area.width(), stat_area.height(), SSD1306_WHITE, SSD1306_BLACK); 807 } else if (disp_mode == DISP_MODE_LANDSCAPE) { 808 drawBitmap(p_as_x+2, p_as_y, stat_area.getBuffer(), stat_area.width(), stat_area.height(), SSD1306_WHITE, SSD1306_BLACK); 809 if (device_init_done && !disp_ext_fb) drawLine(p_as_x, 0, p_as_x, 64, SSD1306_WHITE); 810 } 811 812 } else { 813 if (firmware_update_mode) { 814 drawBitmap(p_as_x, p_as_y, bm_updating, stat_area.width(), stat_area.height(), SSD1306_BLACK, SSD1306_WHITE); 815 } else if (console_active && device_init_done) { 816 drawBitmap(p_as_x, p_as_y, bm_console, stat_area.width(), stat_area.height(), SSD1306_BLACK, SSD1306_WHITE); 817 if (disp_mode == DISP_MODE_LANDSCAPE) { 818 drawLine(p_as_x, 0, p_as_x, 64, SSD1306_WHITE); 819 } 820 } 821 } 822 } 823 824 #define START_PAGE 0 825 const uint8_t pages = 3; 826 uint8_t disp_page = START_PAGE; 827 extern char bt_devname[11]; 828 extern char bt_dh[16]; 829 #if HAS_WIFI 830 extern IPAddress wr_device_ip; 831 #endif 832 void draw_disp_area() { 833 if (!device_init_done || firmware_update_mode) { 834 uint8_t p_by = 37; 835 if (disp_mode == DISP_MODE_LANDSCAPE || firmware_update_mode) { 836 p_by = 18; 837 disp_area.fillRect(0, 0, disp_area.width(), disp_area.height(), SSD1306_BLACK); 838 } 839 if (!device_init_done) disp_area.drawBitmap(0, p_by, bm_boot, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); 840 if (firmware_update_mode) disp_area.drawBitmap(0, p_by, bm_fw_update, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); 841 } else { 842 if (!disp_ext_fb or bt_ssp_pin != 0) { 843 if (radio_online && display_diagnostics) { 844 disp_area.fillRect(0,8,disp_area.width(),37, SSD1306_BLACK); disp_area.fillRect(0,37,disp_area.width(),27, SSD1306_WHITE); 845 disp_area.setFont(SMALL_FONT); disp_area.setTextWrap(false); disp_area.setTextColor(SSD1306_WHITE); disp_area.setTextSize(1); 846 847 disp_area.setCursor(2, 13); 848 disp_area.print("On"); 849 disp_area.setCursor(14, 13); 850 disp_area.print("@"); 851 disp_area.setCursor(21, 13); 852 disp_area.printf("%.1fKbps", (float)lora_bitrate/1000.0); 853 854 //disp_area.setCursor(31, 23-1); 855 disp_area.setCursor(2, 23-1); 856 disp_area.print("Airtime:"); 857 858 disp_area.setCursor(11, 33-1); 859 if (total_channel_util < 0.099) { 860 //disp_area.printf("%.1f%%", total_channel_util*100.0); 861 disp_area.printf("%.1f%%", airtime*100.0); 862 } else { 863 //disp_area.printf("%.0f%%", total_channel_util*100.0); 864 disp_area.printf("%.0f%%", airtime*100.0); 865 } 866 disp_area.drawBitmap(2, 26-1, bm_hg_low, 5, 9, SSD1306_WHITE, SSD1306_BLACK); 867 868 disp_area.setCursor(32+11, 33-1); 869 if (longterm_channel_util < 0.099) { 870 //disp_area.printf("%.1f%%", longterm_channel_util*100.0); 871 disp_area.printf("%.1f%%", longterm_airtime*100.0); 872 } else { 873 //disp_area.printf("%.0f%%", longterm_channel_util*100.0); 874 disp_area.printf("%.0f%%", longterm_airtime*100.0); 875 } 876 disp_area.drawBitmap(32+2, 26-1, bm_hg_high, 5, 9, SSD1306_WHITE, SSD1306_BLACK); 877 878 879 disp_area.setTextColor(SSD1306_BLACK); 880 disp_area.setCursor(2, 46); 881 disp_area.print("Channel"); 882 disp_area.setCursor(38, 46); 883 disp_area.print("Load:"); 884 885 disp_area.setCursor(11, 57); 886 if (total_channel_util < 0.099) { 887 //disp_area.printf("%.1f%%", airtime*100.0); 888 disp_area.printf("%.1f%%", total_channel_util*100.0); 889 } else { 890 //disp_area.printf("%.0f%%", airtime*100.0); 891 disp_area.printf("%.0f%%", total_channel_util*100.0); 892 } 893 disp_area.drawBitmap(2, 50, bm_hg_low, 5, 9, SSD1306_BLACK, SSD1306_WHITE); 894 895 disp_area.setCursor(32+11, 57); 896 if (longterm_channel_util < 0.099) { 897 //disp_area.printf("%.1f%%", longterm_airtime*100.0); 898 disp_area.printf("%.1f%%", longterm_channel_util*100.0); 899 } else { 900 //disp_area.printf("%.0f%%", longterm_airtime*100.0); 901 disp_area.printf("%.0f%%", longterm_channel_util*100.0); 902 } 903 disp_area.drawBitmap(32+2, 50, bm_hg_high, 5, 9, SSD1306_BLACK, SSD1306_WHITE); 904 905 } else { 906 if (device_signatures_ok()) { disp_area.drawBitmap(0, 0, bm_def_lc, disp_area.width(), 23, SSD1306_WHITE, SSD1306_BLACK); } 907 else { disp_area.drawBitmap(0, 0, bm_def, disp_area.width(), 23, SSD1306_WHITE, SSD1306_BLACK); } 908 909 bool display_ip = false; 910 #if HAS_WIFI 911 if (wifi_is_connected() && disp_page%2 == 1) { display_ip = true; } 912 #endif 913 if (display_ip) { 914 #if HAS_WIFI 915 uint8_t ones = 3+one_counts[wr_device_ip[0]]+one_counts[wr_device_ip[1]]+one_counts[wr_device_ip[2]]+one_counts[wr_device_ip[3]]; 916 uint8_t chars = 7; 917 for (uint8_t i = 0; i<4; i++) { if (wr_device_ip[i] > 9) { chars++; } if (wr_device_ip[i] > 99) { chars++; } } 918 uint8_t width = chars*6-(ones*4); 919 int alignment_offset = disp_area.width()-width; 920 int ipxpos = alignment_offset; 921 disp_area.setFont(SMALL_FONT); disp_area.setTextWrap(false); disp_area.setTextColor(SSD1306_WHITE); disp_area.setTextSize(1); 922 disp_area.fillRect(0, 20, disp_area.width(), 17, SSD1306_BLACK); 923 disp_area.setCursor(3, 34-8); disp_area.print("WiFi IP:"); 924 disp_area.setCursor(ipxpos, 34); disp_area.print(wr_device_ip); 925 #endif 926 } else { 927 disp_area.setFont(SMALL_FONT); disp_area.setTextWrap(false); disp_area.setTextColor(SSD1306_WHITE); disp_area.setTextSize(2); 928 disp_area.fillRect(0, 20, disp_area.width(), 17, SSD1306_BLACK); uint8_t ofsc = 0; 929 if ((bt_dh[14] & 0b00001111) == 0x01) { ofsc += 8; } 930 if ((bt_dh[14] >> 4) == 0x01) { ofsc += 8; } 931 if ((bt_dh[15] & 0b00001111) == 0x01) { ofsc += 8; } 932 if ((bt_dh[15] >> 4) == 0x01) { ofsc += 8; } 933 disp_area.setCursor(17+ofsc, 32); disp_area.printf("%02X%02X", bt_dh[14], bt_dh[15]); 934 } 935 } 936 937 if (!hw_ready || radio_error || !device_firmware_ok()) { 938 if (!device_firmware_ok()) { 939 disp_area.drawBitmap(0, 37, bm_fw_corrupt, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); 940 } else { 941 if (!modem_installed) { 942 disp_area.drawBitmap(0, 37, bm_no_radio, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); 943 } else { 944 disp_area.drawBitmap(0, 37, bm_conf_missing, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); 945 } 946 } 947 } else if (bt_state == BT_STATE_PAIRING and bt_ssp_pin != 0) { 948 char *pin_str = (char*)malloc(DISP_PIN_SIZE+1); 949 sprintf(pin_str, "%06d", bt_ssp_pin); 950 951 disp_area.drawBitmap(0, 37, bm_pairing, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); 952 for (int i = 0; i < DISP_PIN_SIZE; i++) { 953 uint8_t numeric = pin_str[i]-48; 954 uint8_t offset = numeric*5; 955 disp_area.drawBitmap(7+9*i, 37+16, bm_n_uh+offset, 8, 5, SSD1306_WHITE, SSD1306_BLACK); 956 } 957 free(pin_str); 958 } else { 959 if (millis()-last_page_flip >= page_interval) { 960 disp_page = (++disp_page%pages); 961 last_page_flip = millis(); 962 if (not community_fw and disp_page == 0) disp_page = 1; 963 } 964 965 if (radio_online) { 966 if (!display_diagnostics) { 967 disp_area.drawBitmap(0, 37, bm_online, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); 968 } 969 } else { 970 if (disp_page == 0) { 971 if (true || device_signatures_ok()) { 972 disp_area.drawBitmap(0, 37, bm_checks, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); 973 } else { 974 disp_area.drawBitmap(0, 37, bm_nfr, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); 975 } 976 } else if (disp_page == 1) { 977 if (!console_active) { 978 disp_area.drawBitmap(0, 37, bm_hwok, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); 979 } else { 980 disp_area.drawBitmap(0, 37, bm_console_active, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); 981 } 982 } else if (disp_page == 2) { 983 disp_area.drawBitmap(0, 37, bm_version, disp_area.width(), 27, SSD1306_WHITE, SSD1306_BLACK); 984 char *v_str = (char*)malloc(3+1); 985 sprintf(v_str, "%01d%02d", MAJ_VERS, MIN_VERS); 986 for (int i = 0; i < 3; i++) { 987 uint8_t numeric = v_str[i]-48; uint8_t bm_offset = numeric*5; 988 uint8_t dxp = 20; 989 if (i == 1) dxp += 9*1+4; 990 if (i == 2) dxp += 9*2+4; 991 disp_area.drawBitmap(dxp, 37+16, bm_n_uh+bm_offset, 8, 5, SSD1306_WHITE, SSD1306_BLACK); 992 } 993 free(v_str); 994 disp_area.drawLine(27, 37+19, 28, 37+19, SSD1306_BLACK); 995 disp_area.drawLine(27, 37+20, 28, 37+20, SSD1306_BLACK); 996 } 997 } 998 } 999 } else { 1000 disp_area.drawBitmap(0, 0, fb, disp_area.width(), disp_area.height(), SSD1306_WHITE, SSD1306_BLACK); 1001 } 1002 } 1003 } 1004 1005 void update_disp_area() { 1006 draw_disp_area(); 1007 1008 drawBitmap(p_ad_x, p_ad_y, disp_area.getBuffer(), disp_area.width(), disp_area.height(), SSD1306_WHITE, SSD1306_BLACK); 1009 if (disp_mode == DISP_MODE_LANDSCAPE) { 1010 if (device_init_done && !firmware_update_mode && !disp_ext_fb) { 1011 drawLine(0, 0, 0, 63, SSD1306_WHITE); 1012 } 1013 } 1014 } 1015 1016 void display_recondition() { 1017 #if PLATFORM == PLATFORM_ESP32 1018 for (uint8_t iy = 0; iy < disp_area.height(); iy++) { 1019 unsigned char rand_seg [] = {random(0xFF),random(0xFF),random(0xFF),random(0xFF),random(0xFF),random(0xFF),random(0xFF),random(0xFF)}; 1020 stat_area.drawBitmap(0, iy, rand_seg, 64, 1, SSD1306_WHITE, SSD1306_BLACK); 1021 disp_area.drawBitmap(0, iy, rand_seg, 64, 1, SSD1306_WHITE, SSD1306_BLACK); 1022 } 1023 1024 drawBitmap(p_ad_x, p_ad_y, disp_area.getBuffer(), disp_area.width(), disp_area.height(), SSD1306_WHITE, SSD1306_BLACK); 1025 if (disp_mode == DISP_MODE_PORTRAIT) { 1026 drawBitmap(p_as_x, p_as_y, stat_area.getBuffer(), stat_area.width(), stat_area.height(), SSD1306_WHITE, SSD1306_BLACK); 1027 } else if (disp_mode == DISP_MODE_LANDSCAPE) { 1028 drawBitmap(p_as_x, p_as_y, stat_area.getBuffer(), stat_area.width(), stat_area.height(), SSD1306_WHITE, SSD1306_BLACK); 1029 } 1030 #endif 1031 } 1032 1033 bool epd_blanked = false; 1034 #if BOARD_MODEL == BOARD_TECHO 1035 void epd_blank(bool full_update = true) { 1036 display.setFullWindow(); 1037 display.fillScreen(SSD1306_WHITE); 1038 display.display(full_update); 1039 } 1040 1041 void epd_black(bool full_update = true) { 1042 display.setFullWindow(); 1043 display.fillScreen(SSD1306_BLACK); 1044 display.display(full_update); 1045 } 1046 #endif 1047 1048 void update_display(bool blank = false) { 1049 display_updating = true; 1050 if (blank == true) { 1051 last_disp_update = millis()-disp_update_interval-1; 1052 } else { 1053 if (display_blanking_enabled && millis()-last_unblank_event >= display_blanking_timeout) { 1054 blank = true; 1055 if (!display_blanked) { 1056 last_disp_update = 0; 1057 } 1058 display_blanked = true; 1059 if (display_intensity != 0) { 1060 display_unblank_intensity = display_intensity; 1061 } 1062 display_intensity = 0; 1063 } else { 1064 display_blanked = false; 1065 if (display_unblank_intensity != 0x00) { 1066 display_intensity = display_unblank_intensity; 1067 display_unblank_intensity = 0x00; 1068 } 1069 } 1070 } 1071 1072 if (blank) { 1073 int blank_iv = display_blanked ? disp_blank_update_interval : disp_update_interval; 1074 if (millis()-last_disp_update >= (uint32_t)blank_iv) { 1075 if (display_contrast != display_intensity) { 1076 display_contrast = display_intensity; 1077 set_contrast(&display, display_contrast); 1078 } 1079 1080 #if BOARD_MODEL == BOARD_TECHO 1081 if (!epd_blanked) { 1082 epd_blank(); 1083 epd_blanked = true; 1084 } 1085 #endif 1086 1087 #if BOARD_MODEL == BOARD_HELTEC_T114 1088 display.clear(); 1089 display.display(); 1090 #elif BOARD_MODEL != BOARD_TDECK && BOARD_MODEL != BOARD_TECHO 1091 display.clearDisplay(); 1092 display.display(); 1093 #else 1094 // TODO: Clear screen 1095 #endif 1096 1097 last_disp_update = millis(); 1098 } 1099 1100 } else { 1101 if (millis()-last_disp_update >= disp_update_interval) { 1102 uint32_t current = millis(); 1103 if (display_contrast != display_intensity) { 1104 display_contrast = display_intensity; 1105 set_contrast(&display, display_contrast); 1106 } 1107 1108 #if BOARD_MODEL == BOARD_HELTEC_T114 1109 display.clear(); 1110 #elif BOARD_MODEL != BOARD_TDECK && BOARD_MODEL != BOARD_TECHO 1111 display.clearDisplay(); 1112 #endif 1113 1114 if (recondition_display) { 1115 disp_target_fps = 30; 1116 disp_update_interval = 1000/disp_target_fps; 1117 display_recondition(); 1118 } else { 1119 #if BOARD_MODEL == BOARD_TECHO 1120 display.setFullWindow(); 1121 display.fillScreen(SSD1306_WHITE); 1122 #endif 1123 1124 update_stat_area(); 1125 update_disp_area(); 1126 } 1127 1128 #if BOARD_MODEL == BOARD_TECHO 1129 if (current-last_epd_refresh >= epd_update_interval) { 1130 if (current-last_epd_full_refresh >= REFRESH_PERIOD) { display.display(false); last_epd_full_refresh = millis(); } 1131 else { display.display(true); } 1132 last_epd_refresh = millis(); 1133 epd_blanked = false; 1134 } 1135 #elif BOARD_MODEL != BOARD_TDECK 1136 display.display(); 1137 #endif 1138 1139 last_disp_update = millis(); 1140 } 1141 } 1142 display_updating = false; 1143 } 1144 1145 void display_unblank() { 1146 last_unblank_event = millis(); 1147 } 1148 1149 void ext_fb_enable() { 1150 disp_ext_fb = true; 1151 } 1152 1153 void ext_fb_disable() { 1154 disp_ext_fb = false; 1155 }