diff --git a/vendor/rnode_firmware/Display.h b/vendor/rnode_firmware/Display.h index ea85578..bfb6165 100644 --- a/vendor/rnode_firmware/Display.h +++ b/vendor/rnode_firmware/Display.h @@ -1746,6 +1746,10 @@ void cp_render_frame() { } #endif +#if BOARD_MODEL == BOARD_TDECK + #include "TDeckUI.h" +#endif + bool epd_blanked = false; #if BOARD_MODEL == BOARD_TECHO void epd_blank(bool full_update = true) { @@ -1763,6 +1767,9 @@ bool epd_blanked = false; void update_display(bool blank = false) { display_updating = true; + #if BOARD_MODEL == BOARD_TDECK + if (td_ui_ready) td_poll_touch(); + #endif if (blank == true) { last_disp_update = millis()-disp_update_interval-1; } else { @@ -1811,11 +1818,11 @@ void update_display(bool blank = false) { #elif BOARD_MODEL == BOARD_CARDPUTER_ADV m5canvas.fillSprite(SSD1306_BLACK); m5canvas.pushSprite(0, 0); - #elif BOARD_MODEL != BOARD_TDECK && BOARD_MODEL != BOARD_TECHO + #elif BOARD_MODEL == BOARD_TDECK + display.fillScreen(SSD1306_BLACK); + #elif BOARD_MODEL != BOARD_TECHO display.clearDisplay(); display.display(); - #else - // TODO: Clear screen #endif display_blank_frame_drawn = true; @@ -1856,6 +1863,8 @@ void update_display(bool blank = false) { #if BOARD_MODEL == BOARD_CARDPUTER_ADV cp_render_frame(); + #elif BOARD_MODEL == BOARD_TDECK + td_render_frame(); #else update_stat_area(); update_disp_area(); @@ -1871,6 +1880,8 @@ void update_display(bool blank = false) { } #elif BOARD_MODEL == BOARD_CARDPUTER_ADV m5canvas.pushSprite(0, 0); + #elif BOARD_MODEL == BOARD_TDECK + td_push_frame(); #elif BOARD_MODEL != BOARD_TDECK display.display(); #endif diff --git a/vendor/rnode_firmware/Makefile b/vendor/rnode_firmware/Makefile index 40c3067..913f1cc 100644 --- a/vendor/rnode_firmware/Makefile +++ b/vendor/rnode_firmware/Makefile @@ -35,7 +35,7 @@ CARDPUTER_ADV_EXTRA_FLAGS = \"-DBOARD_MODEL=0x43\" \"-DCARDPUTER_ADV_DEFAULT_FRE # T-Deck (Plus) dual-boot build — same staging pattern as the Cardputer Adv. # Defaults reuse the CARDPUTER_ADV_DEFAULT_* fallbacks in Utilities.h. -TDECK_FQBN = esp32:esp32:esp32s3:CDCOnBoot=cdc,USBMode=hwcdc,FlashSize=16M,FlashMode=dio,PartitionScheme=no_ota +TDECK_FQBN = esp32:esp32:esp32s3:CDCOnBoot=cdc,USBMode=hwcdc,FlashSize=16M,FlashMode=dio,PartitionScheme=no_ota,PSRAM=opi TDECK_SKETCH = build/tdeck_sketch/RNode_Firmware TDECK_BUILD = build/tdeck_build TDECK_OUTPUT = build/tdeck.esp32.esp32s3 diff --git a/vendor/rnode_firmware/TDeckUI.h b/vendor/rnode_firmware/TDeckUI.h new file mode 100644 index 0000000..5a27199 --- /dev/null +++ b/vendor/rnode_firmware/TDeckUI.h @@ -0,0 +1,508 @@ +// Full-screen RNode UI for the LilyGo T-Deck (rsDeck dual-boot build). +// Renders into a PSRAM-backed 320x240 canvas pushed to the ST7789, replacing +// the generic 64x128 OLED layout. Included by Display.h for BOARD_TDECK only. +// Touch (GT911) toggles the display for battery savings; buttons and BT +// events wake it via the standard display_unblank() path. + +#ifndef TDECK_UI_H +#define TDECK_UI_H + +void display_unblank(); + +// Ratspeak-style dark palette (RGB565) +#define TD_CLR_BG 0x0041 +#define TD_CLR_BG_PANEL 0x1082 +#define TD_CLR_BG_STATUSBAR 0x0861 +#define TD_CLR_BORDER 0x2945 +#define TD_CLR_TEXT_PRIMARY 0xE73C +#define TD_CLR_TEXT_SECONDARY 0xAD75 +#define TD_CLR_TEXT_DIM 0x630C +#define TD_CLR_ACCENT 0x4EBF +#define TD_CLR_GREEN 0x070D +#define TD_CLR_WARN 0xFD20 +#define TD_CLR_ERROR 0xF8A2 + +#define TD_W 320 +#define TD_H 240 +#define TD_BAR_H 24 +#define TD_FOOT_Y 226 +#define TD_CONTENT_Y 30 +#define TD_LEFT_W 206 +#define TD_WF_X 212 +#define TD_WF_W 107 +#define TD_WF_Y 30 +#define TD_WF_H 192 +#define TD_WF_SIZE TD_WF_H + +static const uint16_t td_wf_palette[9] = { + 0x0000, 0x0011, 0x001F, 0x07FF, + 0x07E0, 0xFFE0, 0xFBE0, 0xF800, 0xFFFF +}; + +GFXcanvas16* td_canvas = nullptr; +bool td_ui_ready = false; +int td_waterfall[TD_WF_SIZE]; +int td_waterfall_head = 0; +uint8_t td_charge_tick = 0; + +// --- GT911 touch (poll-only, tap toggles display power) --- +#define TD_TOUCH_SDA 18 +#define TD_TOUCH_SCL 8 +uint8_t td_touch_addr = 0; +bool td_touch_down = false; +uint32_t td_last_touch_poll = 0; + +void td_touch_init() { + Wire.begin(TD_TOUCH_SDA, TD_TOUCH_SCL, 400000); + Wire.beginTransmission(0x5D); + if (Wire.endTransmission() == 0) { td_touch_addr = 0x5D; return; } + Wire.beginTransmission(0x14); + if (Wire.endTransmission() == 0) { td_touch_addr = 0x14; } +} + +bool td_touch_read_down() { + if (td_touch_addr == 0) return false; + Wire.beginTransmission(td_touch_addr); + Wire.write(0x81); Wire.write(0x4E); + if (Wire.endTransmission(false) != 0) return false; + Wire.requestFrom(td_touch_addr, (uint8_t)1); + if (!Wire.available()) return false; + uint8_t status = Wire.read(); + if (!(status & 0x80)) return false; + // Clear the buffer-ready flag so the controller keeps updating + Wire.beginTransmission(td_touch_addr); + Wire.write(0x81); Wire.write(0x4E); Wire.write(0x00); + Wire.endTransmission(); + return (status & 0x0F) > 0; +} + +void td_poll_touch() { + if (millis() - td_last_touch_poll < 50) return; + td_last_touch_poll = millis(); + + bool down = td_touch_read_down(); + if (down && !td_touch_down) { + td_touch_down = true; + } else if (!down && td_touch_down) { + td_touch_down = false; + // Tap released: toggle display power + if (display_blanked) { + display_unblank(); + } else { + last_unblank_event = millis() - display_blanking_timeout - 1; + } + last_disp_update = 0; + } +} + +void td_ui_init() { + if (td_ui_ready) return; + display.setRotation(1); + display.fillScreen(TD_CLR_BG); + td_canvas = new GFXcanvas16(TD_W, TD_H); + for (int i = 0; i < TD_WF_SIZE; i++) td_waterfall[i] = 0; + td_touch_init(); + display_blanking_enabled = true; + td_ui_ready = true; +} + +void td_push_frame() { + if (td_canvas && td_canvas->getBuffer()) { + display.drawRGBBitmap(0, 0, td_canvas->getBuffer(), TD_W, TD_H); + } +} + +uint16_t td_load_color(float percent) { + if (percent < 33.0f) return TD_CLR_GREEN; + if (percent < 66.0f) return 0xFBE0; + return 0xF800; +} + +void td_draw_gradient_bar(int x, int y, int w, int h, float percent) { + if (percent > 100.0f) percent = 100.0f; + if (percent < 0.0f) percent = 0.0f; + int fill_w = (int)((percent / 100.0f) * (w - 2)); + uint16_t fill_clr = TD_CLR_GREEN; + if (percent < 33.0f) fill_clr = 0xF800; + else if (percent < 66.0f) fill_clr = 0xFBE0; + td_canvas->drawRect(x, y, w, h, TD_CLR_BORDER); + if (fill_w > 0) td_canvas->fillRect(x + 1, y + 1, fill_w, h - 2, fill_clr); +} + +void td_draw_usb_icon(int x, int y, uint16_t colour) { + td_canvas->fillRect(x + 3, y, 7, 10, colour); + td_canvas->fillRect(x + 4, y + 10, 5, 4, colour); + td_canvas->drawPixel(x + 1, y + 3, colour); + td_canvas->drawPixel(x + 11, y + 3, colour); +} + +void td_draw_bt_icon(int x, int y, uint16_t colour) { + td_canvas->drawLine(x + 5, y, x + 5, y + 13, colour); + td_canvas->drawLine(x + 5, y, x + 9, y + 4, colour); + td_canvas->drawLine(x + 9, y + 4, x + 0, y + 10, colour); + td_canvas->drawLine(x + 0, y + 3, x + 9, y + 9, colour); + td_canvas->drawLine(x + 9, y + 9, x + 5, y + 13, colour); +} + +void td_draw_lora_icon(int x, int y, uint16_t colour) { + td_canvas->fillCircle(x + 5, y + 7, 2, colour); + td_canvas->drawCircle(x + 5, y + 7, 5, colour); + td_canvas->drawCircle(x + 5, y + 7, 8, colour); +} + +void td_draw_battery(int x, int y) { + if (!pmu_ready || !battery_ready || !battery_installed) { + td_canvas->setTextSize(1); + td_canvas->setTextColor(TD_CLR_TEXT_DIM); + td_canvas->setCursor(x, y + 1); + td_canvas->print("PWR"); + return; + } + + float bval = battery_percent; + bool known = !battery_indeterminate; + bool charging = known && (battery_state == BATTERY_STATE_CHARGING); + bool charged = known && (battery_state == BATTERY_STATE_CHARGED); + uint16_t fill_clr = TD_CLR_GREEN; + + if (charged) { fill_clr = TD_CLR_ACCENT; bval = 100.0f; } + else if (charging) { + fill_clr = TD_CLR_ACCENT; + td_charge_tick += 3; + if (td_charge_tick > 100) td_charge_tick = 0; + bval = td_charge_tick; + } else if (bval <= 20.0f) fill_clr = 0xF800; + else if (bval <= 60.0f) fill_clr = 0xFBE0; + + int bw = 22; int bh = 10; + td_canvas->drawRect(x, y, bw, bh, TD_CLR_TEXT_SECONDARY); + td_canvas->fillRect(x + bw, y + 3, 2, 4, TD_CLR_TEXT_SECONDARY); + int fill_w = (int)((bval / 100.0f) * (bw - 2)); + if (fill_w > 0) td_canvas->fillRect(x + 1, y + 1, fill_w, bh - 2, fill_clr); + + char bbuf[6]; + float pct = battery_percent; + if (pct > 100.0f) pct = 100.0f; + if (pct < 0.0f) pct = 0.0f; + snprintf(bbuf, sizeof(bbuf), "%d%%", (int)(pct + 0.5f)); + td_canvas->setTextSize(1); + td_canvas->setTextColor(TD_CLR_TEXT_SECONDARY); + td_canvas->setCursor(x + 28, y + 1); + td_canvas->print(bbuf); +} + +void td_draw_statusbar() { + td_canvas->fillRect(0, 0, TD_W, TD_BAR_H, TD_CLR_BG_STATUSBAR); + + td_draw_usb_icon(6, 5, (cable_state == CABLE_STATE_CONNECTED) ? TD_CLR_ACCENT : TD_CLR_TEXT_DIM); + + uint16_t bt_clr = TD_CLR_TEXT_DIM; + if (bt_state == BT_STATE_ON) bt_clr = TD_CLR_ACCENT; + else if (bt_state == BT_STATE_PAIRING) bt_clr = TD_CLR_WARN; + else if (bt_state == BT_STATE_CONNECTED) bt_clr = TD_CLR_GREEN; + td_draw_bt_icon(26, 5, bt_clr); + + td_draw_lora_icon(46, 5, radio_online ? TD_CLR_GREEN : TD_CLR_TEXT_DIM); + + td_canvas->setTextSize(2); + td_canvas->setTextColor(TD_CLR_TEXT_PRIMARY); + td_canvas->setCursor(106, 5); + td_canvas->print("rsDeck RNode"); + + td_draw_battery(264, 7); + td_canvas->drawFastHLine(0, TD_BAR_H, TD_W, TD_CLR_BORDER); +} + +void td_draw_waterfall() { + td_canvas->fillRect(TD_WF_X, TD_WF_Y, TD_WF_W, TD_WF_H + 2, TD_CLR_BG); + td_canvas->drawFastVLine(TD_WF_X - 2, TD_WF_Y, TD_WF_H, TD_CLR_BORDER); + + if (!radio_online) { + td_canvas->setTextSize(1); + td_canvas->setTextColor(TD_CLR_TEXT_DIM); + td_canvas->setCursor(TD_WF_X + 18, TD_WF_Y + TD_WF_H / 2); + td_canvas->print("radio off"); + return; + } + + int rssi_val = current_rssi; + if (rssi_val < WF_RSSI_MIN) rssi_val = WF_RSSI_MIN; + if (rssi_val > WF_RSSI_MAX) rssi_val = WF_RSSI_MAX; + int rssi_norm = ((rssi_val - WF_RSSI_MIN) * 8) / WF_RSSI_SPAN; + if (rssi_norm > 7) rssi_norm = 7; + if (rssi_norm < 0) rssi_norm = 0; + + if (display_tx) { + for (uint8_t i = 0; i < WF_TX_SIZE; i++) { + td_waterfall[td_waterfall_head] = -1; + td_waterfall_head = (td_waterfall_head + 1) % TD_WF_SIZE; + } + display_tx = false; + } else { + td_waterfall[td_waterfall_head] = interference_detected ? 8 : rssi_norm; + td_waterfall_head = (td_waterfall_head + 1) % TD_WF_SIZE; + } + + for (int i = 0; i < TD_WF_SIZE; i++) { + int wi = (td_waterfall_head + i) % TD_WF_SIZE; + int ws = td_waterfall[wi]; + int ypos = TD_WF_Y + i; + if (ws >= 0) { + int bar_w = (ws * (TD_WF_W - 4)) / 8; + if (bar_w > 0) td_canvas->drawFastHLine(TD_WF_X + 1, ypos, bar_w, td_wf_palette[ws]); + } else { + for (int tx = 0; tx < TD_WF_W - 4; tx += 2) { + td_canvas->drawPixel(TD_WF_X + 1 + tx, ypos, 0xFFFF); + } + } + } +} + +void td_draw_idle_content() { + int x = 8; + int y = TD_CONTENT_Y + 4; + + td_canvas->setTextSize(2); + td_canvas->setTextColor(TD_CLR_TEXT_PRIMARY); + td_canvas->setCursor(x, y); + if (bt_devname[0] != 0) td_canvas->print(bt_devname); + else td_canvas->print("RNode"); + y += 22; + + char idbuf[8]; + snprintf(idbuf, sizeof(idbuf), "%02X%02X", (uint8_t)bt_dh[14], (uint8_t)bt_dh[15]); + td_canvas->setTextSize(5); + td_canvas->setTextColor(TD_CLR_ACCENT); + td_canvas->setCursor(x, y); + td_canvas->print(idbuf); + y += 46; + + uint32_t freq_hz = (radio_online && LoRa != nullptr) ? LoRa->getFrequency() : lora_freq; + char fbuf[20]; + snprintf(fbuf, sizeof(fbuf), "%.3f MHz", (float)freq_hz / 1000000.0f); + td_canvas->setTextSize(2); + td_canvas->setTextColor(TD_CLR_TEXT_PRIMARY); + td_canvas->setCursor(x, y); + td_canvas->print(fbuf); + y += 20; + + uint32_t bw_hz = lora_bw; + if (bw_hz == 0 && radio_online && LoRa != nullptr) bw_hz = LoRa->getSignalBandwidth(); + char bwstr[10]; + if (bw_hz >= 1000) snprintf(bwstr, sizeof(bwstr), "%luK", (unsigned long)(bw_hz / 1000)); + else snprintf(bwstr, sizeof(bwstr), "%lu", (unsigned long)bw_hz); + char modbuf[36]; + int txp = (radio_online && LoRa != nullptr) ? (int)LoRa->getTxPower() : (int)lora_txp; + snprintf(modbuf, sizeof(modbuf), "SF%d %s CR4/%d %+ddBm", lora_sf, bwstr, lora_cr, txp); + td_canvas->setTextSize(1); + td_canvas->setTextColor(TD_CLR_TEXT_SECONDARY); + td_canvas->setCursor(x, y); + td_canvas->print(modbuf); + y += 12; + + char brbuf[24]; + snprintf(brbuf, sizeof(brbuf), "%.2f Kbps", (float)lora_bitrate / 1000.0f); + td_canvas->setCursor(x, y); + td_canvas->print(brbuf); + y += 14; + + td_canvas->setCursor(x, y); + if (radio_online) { + td_canvas->setTextColor(TD_CLR_GREEN); + td_canvas->print("Radio Online"); + } else if (!device_signatures_ok()) { + td_canvas->setTextColor(TD_CLR_TEXT_SECONDARY); + td_canvas->print("Provisioned, host config pending"); + } else { + td_canvas->setTextColor(TD_CLR_TEXT_DIM); + td_canvas->print("Waiting for host"); + } + y += 16; + + td_canvas->drawFastHLine(x, y, TD_LEFT_W - 16, TD_CLR_BORDER); + y += 5; + + float at_st = airtime * 100.0f; + float at_lt = longterm_airtime * 100.0f; + float cl_st = total_channel_util * 100.0f; + float cl_lt = longterm_channel_util * 100.0f; + char abuf[40]; + td_canvas->setTextColor(td_load_color(at_st)); + snprintf(abuf, sizeof(abuf), "Air ST %.1f%% LT %.1f%%", at_st, at_lt); + td_canvas->setCursor(x, y); + td_canvas->print(abuf); + y += 12; + td_canvas->setTextColor(td_load_color(cl_st)); + snprintf(abuf, sizeof(abuf), "Chan ST %.1f%% LT %.1f%%", cl_st, cl_lt); + td_canvas->setCursor(x, y); + td_canvas->print(abuf); + y += 16; + + signed char t_snr = (signed char)last_snr_raw; + float snr = ((int)t_snr) * 0.25f; + char sigbuf[32]; + snprintf(sigbuf, sizeof(sigbuf), "RSSI %d SNR %.1f", last_rssi, snr); + td_canvas->setTextColor(TD_CLR_TEXT_PRIMARY); + td_canvas->setCursor(x, y); + td_canvas->print(sigbuf); + y += 12; + + float rssi_pct = ((float)(last_rssi - S_RSSI_MIN) / S_RSSI_SPAN) * 100.0f; + td_draw_gradient_bar(x, y, TD_LEFT_W - 16, 10, rssi_pct); +} + +void td_draw_pairing_screen() { + int x = 8; + int y = TD_CONTENT_Y + 10; + + td_canvas->setTextSize(2); + td_canvas->setTextColor(TD_CLR_WARN); + td_canvas->setCursor(x, y); + td_canvas->print("BLE Pairing"); + y += 30; + + if (bt_ssp_pin != 0) { + char pin_str[7]; + snprintf(pin_str, sizeof(pin_str), "%06d", (int)bt_ssp_pin); + int dx = x; + for (int i = 0; i < 6; i++) { + td_canvas->fillRoundRect(dx, y, 28, 44, 5, TD_CLR_BG_PANEL); + td_canvas->drawRoundRect(dx, y, 28, 44, 5, TD_CLR_ACCENT); + td_canvas->setTextSize(4); + td_canvas->setTextColor(TD_CLR_ACCENT); + td_canvas->setCursor(dx + 4, y + 8); + td_canvas->print(pin_str[i]); + dx += 33; + } + y += 58; + } + + td_canvas->setTextSize(1); + td_canvas->setTextColor(TD_CLR_TEXT_SECONDARY); + td_canvas->setCursor(x, y); + td_canvas->print("Confirm this code on the connecting device."); +} + +void td_draw_error_screen() { + int x = 8; + int y = TD_CONTENT_Y + 10; + + td_canvas->setTextSize(2); + td_canvas->setTextColor(TD_CLR_ERROR); + td_canvas->setCursor(x, y); + + if (!device_firmware_ok()) { + td_canvas->print("FW Corrupt"); + y += 26; + td_canvas->setTextSize(1); + td_canvas->setTextColor(TD_CLR_TEXT_SECONDARY); + td_canvas->setCursor(x, y); + td_canvas->print("Reflash firmware to fix"); + } else if (!modem_installed) { + td_canvas->print("No Radio"); + y += 26; + td_canvas->setTextSize(1); + td_canvas->setTextColor(TD_CLR_TEXT_SECONDARY); + td_canvas->setCursor(x, y); + td_canvas->print("Check radio module"); + } else { + td_canvas->print("Config Error"); + y += 26; + td_canvas->setTextSize(1); + td_canvas->setTextColor(TD_CLR_TEXT_SECONDARY); + td_canvas->setCursor(x, y); + td_canvas->print("Run rnodeconf"); + } +} + +void td_draw_external_framebuffer() { + int scale = 3; + int ox = (TD_LEFT_W - 64 * scale) / 2; + int oy = TD_CONTENT_Y + 2; + td_canvas->drawRect(ox - 2, oy - 2, 64 * scale + 4, 64 * scale + 4, TD_CLR_BORDER); + for (int row = 0; row < 64; row++) { + for (int col = 0; col < 64; col++) { + int index = row * 8 + (col / 8); + uint8_t bitmask = 1 << (7 - (col % 8)); + if (fb[index] & bitmask) { + td_canvas->fillRect(ox + col * scale, oy + row * scale, scale, scale, 0xFFFF); + } + } + } +} + +void td_draw_boot_screen() { + td_canvas->fillScreen(TD_CLR_BG); + td_canvas->setTextSize(4); + td_canvas->setTextColor(TD_CLR_ACCENT); + td_canvas->setCursor(100, 80); + td_canvas->print("RNode"); + td_canvas->setTextSize(1); + td_canvas->setTextColor(TD_CLR_TEXT_SECONDARY); + td_canvas->setCursor(122, 122); + td_canvas->print("rsDeck T-Deck"); + + int bar_w = 200; + int bar_x = (TD_W - bar_w) / 2; + int bar_y = 150; + td_canvas->drawRect(bar_x, bar_y, bar_w, 8, TD_CLR_BORDER); + int pulse = (millis() / 15) % bar_w; + int pw = 50; + int p_start = pulse - pw; if (p_start < 0) p_start = 0; + int p_end = pulse; if (p_end > bar_w - 2) p_end = bar_w - 2; + if (p_end > p_start) { + td_canvas->fillRect(bar_x + 1 + p_start, bar_y + 1, p_end - p_start, 6, TD_CLR_ACCENT); + } +} + +void td_draw_footer() { + td_canvas->drawFastHLine(0, TD_FOOT_Y, TD_W, TD_CLR_BORDER); + td_canvas->setTextSize(1); + td_canvas->setTextColor(TD_CLR_TEXT_DIM); + td_canvas->setCursor(8, TD_FOOT_Y + 4); + char vbuf[20]; + snprintf(vbuf, sizeof(vbuf), "Firmware v%d.%02d", MAJ_VERS, MIN_VERS); + td_canvas->print(vbuf); + td_canvas->setCursor(170, TD_FOOT_Y + 4); + td_canvas->print("click: pair touch: screen"); +} + +void td_render_frame() { + if (!td_ui_ready) td_ui_init(); + if (!td_canvas || !td_canvas->getBuffer()) return; + + if (!device_init_done) { + td_draw_boot_screen(); + return; + } + + if (firmware_update_mode) { + td_canvas->fillScreen(TD_CLR_BG); + td_canvas->setTextSize(2); + td_canvas->setTextColor(TD_CLR_WARN); + td_canvas->setCursor(40, 100); + td_canvas->print("Firmware Update"); + td_canvas->setTextSize(1); + td_canvas->setTextColor(TD_CLR_ERROR); + td_canvas->setCursor(100, 130); + td_canvas->print("Do not power off"); + return; + } + + td_canvas->fillScreen(TD_CLR_BG); + td_draw_statusbar(); + td_draw_waterfall(); + td_draw_footer(); + + if (disp_ext_fb && bt_ssp_pin == 0) { + td_draw_external_framebuffer(); + } else if (!hw_ready || radio_error || !device_firmware_ok()) { + td_draw_error_screen(); + } else if (bt_state == BT_STATE_PAIRING && bt_ssp_pin != 0) { + td_draw_pairing_screen(); + } else { + td_draw_idle_content(); + } +} + +#endif // TDECK_UI_H