|
| 1 | +/** |
| 2 | + ************************************************** |
| 3 | + * |
| 4 | + * @file BluetoothSerial.ino |
| 5 | + * @brief Example showing how to use Bluetooth Serial on Inkplate 10 |
| 6 | + * with an LVGL-based UI that safely renders incoming text. |
| 7 | + * |
| 8 | + * HOW TO USE |
| 9 | + * - Upload this example to Inkplate 10. |
| 10 | + * - Pair your phone with the device in Bluetooth settings (device name: "Inkplate10"). |
| 11 | + * - Open a Serial Bluetooth Terminal app (or similar) and connect to "Inkplate10". |
| 12 | + * - Anything you type in the app will be shown on the Inkplate screen. |
| 13 | + * - Anything you type in the Arduino Serial Monitor will be sent to the phone. |
| 14 | + * |
| 15 | + * |
| 16 | + * For info on how to quickly get started with Inkplate 10 visit |
| 17 | + * https://soldered.com/documentation/inkplate/10/overview/ |
| 18 | + * |
| 19 | + * @authors Soldered |
| 20 | + * @date November 2025 |
| 21 | + ************************************************** |
| 22 | + */ |
| 23 | + |
| 24 | +#if !defined(ARDUINO_ESP32_DEV) && !defined(ARDUINO_INKPLATE10) |
| 25 | +#error "Wrong board selection for this example, please select e-radionica Inkplate10 or Soldered Inkplate10 in the boards menu." |
| 26 | +#endif |
| 27 | + |
| 28 | +#include <Inkplate-LVGL.h> |
| 29 | +#include <BluetoothSerial.h> |
| 30 | + |
| 31 | +static const char *btDeviceName = "Inkplate10"; |
| 32 | + |
| 33 | +Inkplate inkplate(INKPLATE_1BIT); |
| 34 | +BluetoothSerial SerialBT; |
| 35 | + |
| 36 | +// ---------- Font helpers (use LV_FONT_DEFAULT only) ---------- |
| 37 | +static const lv_font_t* font_big() { return LV_FONT_DEFAULT; } |
| 38 | +static const lv_font_t* font_small() { return LV_FONT_DEFAULT; } |
| 39 | + |
| 40 | +// ---------- LVGL helpers ---------- |
| 41 | +static inline int dispW() { |
| 42 | + lv_display_t *d = lv_display_get_default(); |
| 43 | + return lv_display_get_horizontal_resolution(d); |
| 44 | +} |
| 45 | +static inline int dispH() { |
| 46 | + lv_display_t *d = lv_display_get_default(); |
| 47 | + return lv_display_get_vertical_resolution(d); |
| 48 | +} |
| 49 | + |
| 50 | +static int lineHeight(const lv_font_t *f) { |
| 51 | + #if LVGL_VERSION_MAJOR >= 9 |
| 52 | + return lv_font_get_line_height(f); |
| 53 | + #else |
| 54 | + // Approximate default font line height for LVGL v8 if API is unavailable |
| 55 | + return 18; |
| 56 | + #endif |
| 57 | +} |
| 58 | + |
| 59 | +static void make_opaque(lv_obj_t *obj) { |
| 60 | + lv_obj_set_style_bg_color(obj, lv_color_hex(0xFFFFFF), 0); |
| 61 | + lv_obj_set_style_bg_opa(obj, LV_OPA_COVER, 0); |
| 62 | + lv_obj_set_style_border_width(obj, 0, 0); |
| 63 | +} |
| 64 | + |
| 65 | +// ---------- UI elements ---------- |
| 66 | +static lv_obj_t *label_title = nullptr; |
| 67 | +static lv_obj_t *label_info = nullptr; |
| 68 | +static lv_obj_t *panel_log = nullptr; |
| 69 | +static lv_obj_t *label_log = nullptr; |
| 70 | + |
| 71 | +// Spacing |
| 72 | +static const int INFO_LINE_SPACE = 4; |
| 73 | +static const int LOG_LINE_SPACE = 6; |
| 74 | +static const int topMargin = 8; |
| 75 | +static const int leftMargin = 8; |
| 76 | +static const int rightMargin = 8; |
| 77 | + |
| 78 | +// Log state |
| 79 | +static String logText; |
| 80 | +static int maxLines = 0; |
| 81 | +static int usedLines = 0; |
| 82 | + |
| 83 | +static void ui_create() { |
| 84 | + // Screen background |
| 85 | + lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0xFFFFFF), LV_PART_MAIN); |
| 86 | + |
| 87 | + // Title |
| 88 | + label_title = lv_label_create(lv_screen_active()); |
| 89 | + lv_obj_set_style_text_color(label_title, lv_color_hex(0x000000), 0); |
| 90 | + lv_obj_set_style_text_font(label_title, font_big(), 0); |
| 91 | + lv_label_set_text(label_title, "Bluetooth Serial Example"); |
| 92 | + lv_obj_set_pos(label_title, leftMargin, topMargin); |
| 93 | + make_opaque(label_title); |
| 94 | + |
| 95 | + // Instructions |
| 96 | + label_info = lv_label_create(lv_screen_active()); |
| 97 | + lv_obj_set_style_text_color(label_info, lv_color_hex(0x000000), 0); |
| 98 | + lv_obj_set_style_text_font(label_info, font_small(), 0); |
| 99 | + lv_label_set_long_mode(label_info, LV_LABEL_LONG_WRAP); |
| 100 | + lv_obj_set_style_text_line_space(label_info, INFO_LINE_SPACE, 0); |
| 101 | + int infoY = topMargin + lineHeight(font_big()) + 8; |
| 102 | + lv_obj_set_pos(label_info, leftMargin, infoY); |
| 103 | + lv_obj_set_width(label_info, dispW() - leftMargin - rightMargin); |
| 104 | + lv_label_set_text(label_info, |
| 105 | + "Pair your phone and open a Serial Bluetooth Terminal.\n" |
| 106 | + "Messages from phone appear below.\n" |
| 107 | + "USB Serial -> phone is also forwarded.\n"); |
| 108 | + make_opaque(label_info); |
| 109 | + |
| 110 | + // Ensure layout is computed before measuring info height |
| 111 | + lv_timer_handler(); |
| 112 | + #if LVGL_VERSION_MAJOR >= 9 |
| 113 | + lv_obj_update_layout(label_info); |
| 114 | + #endif |
| 115 | + int infoBottom = lv_obj_get_y(label_info) + lv_obj_get_height(label_info); |
| 116 | + |
| 117 | + // Opaque log panel below the instructions |
| 118 | + panel_log = lv_obj_create(lv_screen_active()); |
| 119 | + make_opaque(panel_log); |
| 120 | + lv_obj_set_style_pad_all(panel_log, 0, 0); |
| 121 | + lv_obj_set_style_border_width(panel_log, 0, 0); |
| 122 | + int panelX = leftMargin; |
| 123 | + int panelY = infoBottom + 10; |
| 124 | + int panelW = dispW() - leftMargin - rightMargin; |
| 125 | + int panelH = dispH() - panelY - topMargin; |
| 126 | + lv_obj_set_pos(panel_log, panelX, panelY); |
| 127 | + lv_obj_set_size(panel_log, panelW, panelH); |
| 128 | + |
| 129 | + // Log label inside the panel |
| 130 | + label_log = lv_label_create(panel_log); |
| 131 | + lv_obj_set_style_text_color(label_log, lv_color_hex(0x000000), 0); |
| 132 | + lv_obj_set_style_text_font(label_log, font_small(), 0); |
| 133 | + lv_label_set_long_mode(label_log, LV_LABEL_LONG_WRAP); |
| 134 | + lv_obj_set_style_text_line_space(label_log, LOG_LINE_SPACE, 0); |
| 135 | + lv_obj_set_pos(label_log, 0, 0); |
| 136 | + lv_obj_set_width(label_log, panelW); |
| 137 | + |
| 138 | + // Compute how many lines fit into the panel |
| 139 | + int effLineH = lineHeight(font_small()) + LOG_LINE_SPACE; |
| 140 | + maxLines = (effLineH > 0) ? (panelH / effLineH) : 0; |
| 141 | + if (maxLines < 3) maxLines = 3; // safety minimum |
| 142 | +} |
| 143 | + |
| 144 | +static void ui_reset_log_full_refresh() { |
| 145 | + // Rebuild the entire UI and perform a full refresh (avoids ghosting) |
| 146 | + lv_obj_clean(lv_screen_active()); |
| 147 | + logText = ""; |
| 148 | + usedLines = 0; |
| 149 | + ui_create(); |
| 150 | + |
| 151 | + lv_tick_inc(10); |
| 152 | + lv_timer_handler(); |
| 153 | + inkplate.display(); // FULL |
| 154 | +} |
| 155 | + |
| 156 | +static void log_append_char(char c) { |
| 157 | + if (c == '\r') return; |
| 158 | + |
| 159 | + if (c == '\n') { |
| 160 | + logText += '\n'; |
| 161 | + usedLines++; |
| 162 | + } else { |
| 163 | + logText += c; |
| 164 | + } |
| 165 | + |
| 166 | + if (usedLines >= maxLines) { |
| 167 | + ui_reset_log_full_refresh(); |
| 168 | + return; |
| 169 | + } |
| 170 | + |
| 171 | + lv_label_set_text(label_log, logText.c_str()); |
| 172 | + |
| 173 | + // Partial refresh for faster updates; opaque panel removes old pixels |
| 174 | + lv_tick_inc(5); |
| 175 | + lv_timer_handler(); |
| 176 | + inkplate.partialUpdate(); |
| 177 | +} |
| 178 | + |
| 179 | +void setup() { |
| 180 | + Serial.begin(115200); |
| 181 | + inkplate.begin(LV_DISP_RENDER_MODE_PARTIAL); |
| 182 | + |
| 183 | + ui_create(); |
| 184 | + |
| 185 | + // Initial full refresh to clear any boot artifacts |
| 186 | + lv_tick_inc(20); |
| 187 | + lv_timer_handler(); |
| 188 | + inkplate.display(); |
| 189 | + |
| 190 | + // Start Bluetooth |
| 191 | + if (!SerialBT.begin(btDeviceName)) { |
| 192 | + const char *err = "Bluetooth init ERROR"; |
| 193 | + for (const char *p = err; *p; ++p) log_append_char(*p); |
| 194 | + log_append_char('\n'); |
| 195 | + } else { |
| 196 | + const char *ok1 = "BT ready. Pair and connect."; |
| 197 | + for (const char *p = ok1; *p; ++p) log_append_char(*p); |
| 198 | + log_append_char('\n'); |
| 199 | + log_append_char('\n'); // extra spacing before user messages |
| 200 | + } |
| 201 | +} |
| 202 | + |
| 203 | +void loop() { |
| 204 | + // USB Serial -> Bluetooth |
| 205 | + while (Serial.available()) { |
| 206 | + char c = (char)Serial.read(); |
| 207 | + SerialBT.write((uint8_t)c); |
| 208 | + } |
| 209 | + |
| 210 | + // Bluetooth -> screen + echo to USB Serial |
| 211 | + while (SerialBT.available()) { |
| 212 | + char c = (char)SerialBT.read(); |
| 213 | + Serial.write((uint8_t)c); |
| 214 | + log_append_char(c); |
| 215 | + } |
| 216 | + |
| 217 | + lv_tick_inc(5); |
| 218 | + lv_timer_handler(); |
| 219 | + delay(20); |
| 220 | +} |
0 commit comments