Nextion (ITEAD) panels are serial touch displays that run a self-contained
screen design. You create the screens - pages, text and number components,
images - in the Nextion Editor on a PC, flash the compiled .tft file to the
panel, and then drive the components from your device script over UART.
Platform Support
| Platform | Ports | Notes |
|---|---|---|
| ESP32 | UART1, UART2 | UART0 reserved for debug console |
| Pico W / Pico 2 W | UART0, UART1 | Both available |
| Simulator | ports 0-2 | uart_* mock + byte injector (UART panel) |
Wiring
Nextion panels run on 5V power but expose 3.3V logic levels on their serial pins.
| Nextion pin | Connect to |
|---|---|
| RX | MCU TX pin |
| TX | MCU RX pin |
| VIN / VCC | 5V supply (external for larger panels) |
| GND | common ground |
A common ESP32 DevKit setup uses UART2: TX=GPIO17, RX=GPIO16.
How the Driver Helps
Every Nextion instruction is ASCII text terminated by three mandatory
0xFF 0xFF 0xFF bytes, and the raw DEVICE.uartWrite API takes hex-string
byte arrays - so talking to a panel by hand means encoding every command. The
NextionDisplay driver handles the encoding, termination, and replies:
import { DeviceEntrypoint } from "@devicesdk/core";
import { NextionDisplay } from "@devicesdk/core/nextion";
export class MyDevice extends DeviceEntrypoint {
private display = new NextionDisplay(this.env.DEVICE, {
port: 2, // ESP32 UART2 (UART0 is the debug console)
txPin: 17,
rxPin: 16,
baudRate: 115200, // must match the panel's setting in the Editor
});
async onDeviceConnect() {
await this.display.connect();
await this.display.setPage(0);
await this.display.setText("tTitle", "Heater control");
await this.display.setNumber("nTemp", 215);
}
}The baud rate must match the one configured in the Nextion Editor (default 9600; 115200 is common).
API
Constructor options
| Option | Default | Description |
|---|---|---|
port |
- | UART port (ESP32: 1-2, Pico: 0-1) |
txPin |
- | UART TX pin to the panel’s RX |
rxPin |
- | UART RX pin to the panel’s TX |
baudRate |
9600 | Must match the panel’s configured speed |
getBufferSize |
64 | Max bytes read for a get reply |
getTimeoutMs |
500 | Timeout in ms for get replies |
Methods
| Method | Nextion instruction |
|---|---|
connect() |
configures the UART port |
setPage(page) |
page <n> |
setText(component, value) |
<comp>.txt="<value>" - quotes/control chars stripped, \\ and \r escapes encoded |
setNumber(component, value) |
<comp>.val=<value> |
setVisible(component, visible) |
vis <comp>,<0/1> |
setTextColor(component, color) |
pco <comp>,<color> (16-bit 565 RGB) |
setBackgroundColor(component, color) |
bco <comp>,<color> (16-bit 565 RGB) |
refresh(component) |
ref <comp> |
get(component, attribute?) |
get <comp>.<attr> (defaults to .val) - resolves with the reply |
getNumber(component) |
get <comp>.val parsed as a number (decimal or hex) |
sendRaw(instruction) |
any instruction not covered above, terminator appended |
Static helpers
| Helper | Description |
|---|---|
toBytes(text) |
UTF-8 encode text to "0x.." hex-string bytes |
bytesToAscii(bytes) |
decode "0x.." hex-string bytes back to text |
frameEnd() |
the mandatory ["0xFF", "0xFF", "0xFF"] terminator |
Reading Values
get is request/response - the panel answers with the value plus the
0xFF 0xFF 0xFF terminator, which the driver strips (any bkcmd ack bytes
after the terminator are ignored):
const reply = await this.display.get("nTemp"); // get nTemp.val
const title = await this.display.get("tTitle", "txt"); // get tTitle.txt
const temp = await this.display.getNumber("nTemp"); // parsed: 25The driver reads in short chunks until it sees the terminator, bounded by
getTimeoutMs (default 500 ms) and getBufferSize (default 64 bytes).
get throws instead of guessing: no reply (wrong component name,
wiring, or baud rate), a reply longer than getBufferSize without a
terminator (raise it for long .txt values), or the panel’s 0x00 error
byte. getNumber parses both decimal replies and the hex replies some panel
firmwares send.
For data the panel pushes on its own, read the port directly with
DEVICE.uartRead and decode with the helper:
const response = await this.env.DEVICE.uartRead(2, 64, 500);
if (response.type === "uart_read_result") {
console.log(NextionDisplay.bytesToAscii(response.payload.data));
}Touch Events - Not Yet Supported
:::caution
Touch events are not yet supported. Receiving them requires an
event-driven UART receive path on the firmware (a uart_monitor command that
pushes incoming bytes as uart_data events into onMessage) - this is
planned Phase 2 work; the design lives in
docs/designs/nextion-dev-experience.md.
Until it lands, scripts that need touch input must poll DEVICE.uartRead
from a cron.
:::
The devicesdk dev simulator’s UART panel can inject raw bytes into a port’s
receive buffer (with a touch-frame preset), which lets you exercise
request/response logic and frame parsing without hardware - but on real
devices the panel’s push-style touch frames have no event path yet.
Example Project
examples/nextion-dashboard
is a complete dashboard: ESP32 with UART2, updating tTitle, tTime,
tTemp, and tUptime text components on a cron.
Troubleshooting
- Garbage on the panel - baud mismatch: the driver’s
baudRatediffers from the panel’s setting. Non-ASCII characters (e.g.°) also render as garbage - Nextion panels use their own font codepage, keep text ASCII. - No
comokboot string on power-up - check wiring (RX/TX crossed), a common ground, and that the panel is powered. - Component stays empty - the Editor’s component length limit is smaller than the value you sent; text is silently truncated by the panel.
getthrows “no reply” - wrong component name, wiring, or baud rate; the driver never guesses, it errors.- Missing touch events - expected; see the touch callout above.