ESPHome Component¶
The NSPanel is operating on a ESP32. To provide access to the NSPanel via HomeAssistant, ESPHome is being used.
- Requires ESPHome 2026.10 or later (enforced by
min_versioninesphome/nspanel_haui/base.yaml) - Ships with a temporary vendored patch of the stock
nextioncomponent: the numeric-response delivery fix (esphome PR #17744) the firmware'sread_responsepath relies on was merged two days after the 2026.10.0 release tag was cut, so it only ships in ESPHome 2026.11.0.esphome/nextion_patch/carries the 2026.10.0 component plus the cherry-picked fix, and the configs override the stock component with it viaexternal_components— a git source for installed setups (shipped in thenspanel_haui.yamlpackage, zero configuration action), a local source in the universal/dev configs so CI and the release build validate the actual checkout. Remove the patch and its override blocks once a released ESPHome contains #17744 and bumpmin_versiontogether (seeesphome/nextion_patch/README.md). - Provides scripts and actions for communication with the display
- Responsible for handling the device functionality
For the communication between the device and the server ESPHome native API events are used.
Installation¶
See esphome/install.yaml for the installation configuration. This file is going to be installed on the device.
See esphome/nspanel_haui.yaml for the esphome configuration file. This file contains all ESPHome functionality.
Copy the secrets from esphome/secrets.yaml.example and add them in ESPHome by pressing the Secrets Link in the right corner. This step is not necessary if you add the secrets directly in the config file.
When building locally (e.g. with esphome/nspanel_haui_dev.yaml, which uses !secret), copy esphome/secrets.yaml.example to esphome/secrets.yaml and fill in real values. esphome/secrets.yaml is git-ignored.
The reference build (esphome/install.yaml) compiles without editing YAML:
the device name and friendly name are substitutions with usable defaults
(nspanel-haui / NSPanel HAUI) and every credential comes from
secrets.yaml. Per-device builds only need to change the name substitution
(ESPHome uses it for the hostname and default entity IDs) before flashing.
The discovery identity is fixed: esphome.project.name is
happydasch.nspanel_haui (split by Home Assistant into
manufacturer happydasch, model nspanel_haui), which is what the
integration's config flow and Device Manager match on. Keep that value
unchanged when forking.
To install ESPHome on the device use the config from esphome/install.yaml. Edit the substitution values and install the device.
Universal prebuilt firmware: every release also carries nspanel_haui.bin,
compiled from esphome/nspanel_haui_universal.yaml. That config bakes in
documented default credentials (no secrets.yaml needed) and no Wi-Fi network,
so the device boots its open access point and is provisioned over the captive
portal. The Wi-Fi entered there survives firmware updates. See
Installation → Universal image (no YAML).
name_add_mac_suffix: true appends the last six MAC digits at runtime, so one
binary installs any number of panels with unique names and access points.
If the device shows up in Home Assistant you can manually install the TFT file by pressing the Update Display button. Otherwise the Hub app installs the TFT version the firmware needs about a minute after the device connects (see Installation).
See Nextion for more details about the display.
Config¶
An overview of all configuration variables defined in the ESPHome yaml file.
heartbeat_interval(5)name(nspanel-haui)ota_password(!secret ota_password)api_encryption_key(!secret api_encryption_key)wifi_ssid(!secret wifi_ssid)wifi_password(!secret wifi_password)ap_password(!secret ap_password)tft_update_url(default TFT URL of the display component, which enables TFT upload support; the hub always sends the URL of the TFT it installs)
Web server (optional)¶
The ESPHome web server is not enabled in the shipped firmware. It costs
flash and RAM that the HTTP OTA path (TLS, JSON, http_request) needs, so
esphome/nspanel_haui/base.yaml leaves it out. Home Assistant only shows a
Visit link for a device whose firmware runs the web server.
Enable the web UI in your own device YAML — the file that includes
nspanel_haui.yaml as a package, not a package file itself:
Then flash that build. esphome/nspanel_haui_dev.yaml enables the web server
for local development and reads web_username / web_password from
secrets.yaml.
Communication¶
All communication between the ESPHome device and the Hub app uses ESPHome Native API events. The configuration for events is in esphome/nspanel_haui/scripts_event.yaml. The connection scripts are in esphome/nspanel_haui/scripts_connection.yaml.
See docs/communication.md for the full protocol description.
Connection Globals¶
The device maintains these globals for connection and display state (defined in esphome/nspanel_haui/globals.yaml):
hub_availability(bool) - Whether the ESPHome Native API is connected to Home Assistant. Set to true byon_client_connected; reset to false byon_client_disconnectedonce the last API client is gone (it stays true while other API clients remain). While false, the device does not sendreq_connectionrequests and drops an active hub connection.hub_heartbeat(int) - Last timestamp (epoch seconds) when a hub heartbeat was received. Set by thehub_heartbeataction; used bycheck_hub_connectionfor timeout detection.hub_connection(bool) - Whether the connection handshake with the Hub app has completed. Set byset_hub_connectedscript.haui_init(bool) - Whether the device has been initialized by the Hub app.haui_display_state(int) - Display state: 0 off, 1 dim, 2 on.
Connection Scripts¶
Defined in esphome/nspanel_haui/scripts_connection.yaml:
set_hub_connected(connected: int, force: int)- A disconnect (connected = 0) is skipped while other API clients remain, unlessforce = 1(used when the device itself detects a heartbeat timeout or hub unavailability). Updateshub_connection, anchors/resetshub_heartbeat, wakes the display on disconnect (check_displaythen pins the device to the system page 0 while the hub is disconnected), writessystem.firstRun.txt="0"on connect, publishesclient_statussensor.check_hub_connection- Runs every 100ms. Detects timeout (hub heartbeat not received withinheartbeat_interval × 2) or hub unavailability.check_connection- Runs every 100ms. Sendsreq_connectionwhen disconnected but hub is available, with 10s cooldown between attempts.
Events¶
The primary events used for connection management:
esphome.heartbeat- Device→Hub heartbeat. Published everyheartbeat_intervalseconds (5s by default) whenhub_connection = true. Value: empty string.esphome.req_connection- Device→Hub connection request. Published when disconnected and hub is available. Value: JSON withyaml_version,name,friendly_name,ip,ssid,rssianduptime.esphome.res_connection- Device→Hub connection response. Published after receivinghub_connection_response. Value: JSON withheartbeat_interval.esphome.res_device_state- Device→Hub state response. Published after receivingreq_device_state. Value: JSON withpage,brightness,button_left,button_right,relay_left,relay_right,use_relay_left,use_relay_right,display_state,yaml_version,tft_version,ssidandrssi.
Actions¶
Actions that the Hub app can call on the device (via ESPHome Native API service calls):
hub_heartbeat- Hub→Device heartbeat. Resetshub_heartbeattimestamp on the device.hub_connection_response- Hub acknowledges connection request. Device transitions to sendingres_connection.hub_connection_initialized- Handshake complete. Setshub_connection = trueon the device.hub_connection_closed- Hub disconnects the device. Setshub_connection = false.haui_discover- Device discovery. Device publishesesphome.haui_discoverwithname,friendly_name,ip,yaml_version,device_version,ssidandrssi.req_device_state- Hub requests device state. Device responds withres_device_stateevent.req_device_info- Hub requests device info. Device responds withres_device_infoevent.req_reconnect- Hub requests device to reconnect. Callsset_hub_connected(false)withoutforce, so it is skipped while other API clients remain.reset_last_interaction- Resets the device's display inactivity timer (dim/display-off).
Requests¶
These events will get a response.
req_device_infoDevice Info Requestreq_device_stateDevice State Requestreq_reconnectReconnect Requestreq_valNumber Requestreq_txtText Request
Responses¶
These responses will be sent after a request.
res_device_stateDevice State Event:
Will be published after sending request req_device_state
Value: json encoded string
res_device_infoDevice Info Event:
Will be published after sending request req_device_info
Value: json encoded string
read_responseComponent Read Response
Unified response for component reads (number or text). Published after
sending req_val or req_txt. Value: JSON encoded string with the
keys name (component name), type ("number" or "text"), and
value (the read value).
Commands¶
These are commands that are being executed on the ESP.
send_commandSends a command to the nextion displaysend_commandsSends multiple commands to the nextion displaygoto_pageSets the active page on displaysend_notificationSends a notification to the display
Parameters: title (string), message (string), icon (string - optional; when empty, the notification uses its severity's default icon — these actions default to info, so the info icon is used). Optional: persistent (bool; loops the alert sound and re-opens the popup about every 60 seconds until dismissed, default false) and text_size ("auto" or a font size 1-3, default "auto").
The action does not expose severity or force-show fields; persistent loops the sound and re-opens the popup periodically without force-showing it. The first re-open comes about a second after the action; if a notification popup or list is already open, the re-open waits until it is dismissed. Because the action cannot set a severity, its alerts default to info and never re-open over the idle page.
Events¶
Different ESP device events.
connectedConnected Event:
Will be sent after connection was established
display_offDisplay-Off Event:
The display entered hardware sleep (no-hub path only). With a hub connected the display is never hardware-slept — it only dims/turns off its brightness.
display_onDisplay-On Event:
The display woke up from hardware sleep (no-hub path only). With a hub the touch itself drives the wake and the hub's idle-page exit.
pagePage Change Event:
A new page was opened Value: page_id
componentComponent Press Event:
A Nextion component was touched
Value: page_id,component_id,touch_event
touch_startTouch Pressed Event:
The touch screen was pressed (also published with value 1 on a Nextion touch press)
Value: empty string
touch_endTouch Released Event:
The touch screen was released Value: start_x,start_y,end_x,end_y
gestureTouch Gesture Event:
The gesture was recognized on the screen
Value: swipe_left, swipe_right, swipe_up or swipe_down
button_leftLeft Physical Button Pressed Event:
The left physical button was pressed or released
Value: 1 on press, 0 on release
button_rightRight Physical Button Pressed Event:
The right physical button was pressed or released
Value: 1 on press, 0 on release
relay_leftRelay Switched Event:
The left relay was switched Value: state (1 or 0)
relay_rightRelay Switched Event:
The right relay was switched Value: state (1 or 0)
timeoutTimeout Event:
An inactivity milestone was crossed (dim, off, page) Value: timeout type (string)
The hub reacts to page only (automatic page switching opens the idle page);
dim and off are brightness milestones handled by the firmware itself.
use_relay_left/use_relay_rightRelay Mode Event:
The "use relay" switch was changed Value: state (1 or 0)
send_notificationNotification Event:
Published by the send_notification script
Value: JSON with title, message, icon (only if set), persistent and text_size (only if set)
buffer_overflowNextion Buffer Overflow Event:
The Nextion display reported a buffer overflow
Value: 1
display_stateDisplay State Event:
Current display state (on, off, dim) Value: state type (string)
Actions¶
This actions below are defined on the ESP. The communication between the Hub server and the ESP is using these.
send_commandAction to send a command
This will send a command to the nextion display
-
send_commandsAction to send multiple commands (commands: string array) to the nextion display -
display_off/display_onActions to put the display into / out of hardware sleep (publishesphome.display_off/esphome.display_on) -
install_firmware_urlAction to flash the firmware OTA image aturlwith itsmd5sum
The hub sends it to install another release or a custom build
(nspanel_haui.update_firmware, the editor's Install firmware picker).
upload_tft_urlAction to update a TFT file from URL
The hub's TFT install sends it. Use nspanel_haui.update_tft (optionally
with url) or the HAUI Display update entity instead, they track the
install.
-
set_brightnessAction to change the brightness of the display -
goto_pageAction to change the page of the display -
play_rtttlAction to play a song for RTTTL strings
The song string is a standard RTTTL string (name:d=...,o=...,b=...:notes).
Built-in sounds played by play_sound (see esphome/nspanel_haui/scripts_common.yaml):
startup:d=4,o=5,b=200:8c7,8g7,8d7,8g7
tone:d=8,o=6,b=200:16e6
tone_up:d=8,o=6,b=200:16d6
tone_down:d=8,o=6,b=200:16g6
seq_up:d=16,o=5,b=160:16e6,16g6
seq_down:d=16,o=5,b=160:16g6,16d6
notification:d=8,o=6,b=200:16e,16c6,16g6,8c6,8g6
alert_fast:d=8,o=6,b=200:16c7,16d7,16c7,16d7,16c7,16d7,16c7,16d7,16e7
alert:d=16,o=6,b=400:16c7,16d7,16c7,16d7,16c7,16d7,16c7,16d7,16e7
elise:d=8,o=5,b=125:32p,e6,d#6,e6,d#6,e6,b,d6,c6,4a.,32p,c,e,a,4b.,32p,e,g#,b,4c.6,32p,e,e6,d#6,e6,d#6,e6,b,d6,c6,4a.,32p,c,e,a,4b.,32p,d,c6,b,2a
Other sounds from different sources:
startup_long:d=16,o=5,b=180:c,e,g,c6,8p,c,16p,e,c
startup:d=16,o=5,b=180:c,e,g,c6
two short:d=4,o=5,b=100:16e6,16e6
short:d=4,o=5,b=100:16e6
scale_up:d=32,o=5,b=100:c,c#,d#,e,f#,g#,a#,b
chirp6:d=4,o=5,b=400:c6
doodle-dah:d=8,o=5,b=250:d#6,a5#,d#6
doodle-duh:d=8,o=5,b=250:d#6,a5#,d#5
play_soundAction to play a named sound
A playing tune (from either action) is stopped before a TFT upload, an OTA firmware update or a reboot: those block the main loop the buzzer's notes are switched off from, so the current note would otherwise keep sounding.
reset_last_interactionAction to reset the last interaction value
Takes an offset (int); the last interaction value is set to now with that offset applied
send_notificationAction to send a notification to a display
Parameters: title, message, icon; optional persistent (bool; loops the alert sound and re-opens the popup about every 60 seconds until dismissed) and text_size ("auto" or a font size 1-3). persistent does not force-show the popup; the first re-open comes about a second after the action.
-
req_val/req_txtActions to read a number / text value from the display (parametername) -
haui_discoverAction to publish thehaui_discoverevent
Page¶
These events will be sent out when the device have some page related events.
-
page(0 - 255) The page that is being displayed -
timeout_page(0 - 3600)
Page switch timeout, will be restored on reboot
Value > 0 activates the timeout
- After the page timeout the firmware publishes
esphome.timeout "page"and the hub (when connected anduse_auto_pageis on) opens the configured idle page (idle_panel). Page switching is separate from the display brightness: the page timeout neither dims nor turns the display off.
Interaction¶
The configuration for this functionality is in esphome/nspanel_haui/scripts_interaction.yaml
When tracking the last interaction with the device, two input methods are possible: Touchscreen and Buttons. It's possible to disable button as an interaction source by switching off use_button_interaction.
Interaction Sensors¶
The ESP provides some interaction related events. See below for a overview of all sensors. Some sensors are set to internal to not pollute HA.
touch(binary sensor)
Touch State, the state is being set based on the touch_x and touch_y values
touch_x(0 - 480=display width, only 0..450 is visible; the rightmost 30px are behind the bezel)
Current touch x coordinates (0 when released)
touch_y(0 - 320=display height)
Current touch y coordinates (0 when released)
button_left(binary sensor, GPIO 14)
Physical button left state
button_right(binary sensor, GPIO 27)
Physical button right state
use_button_interaction(switch)
Should buttons be used for updating last interaction time.
use_auto_dimming(switch)
Should display be automatically dimmed.
use_auto_page(switch)
Should the hub automatically switch to the configured idle page after the page timeout.
use_auto_display_off(switch)
Should the display automatically turn off (brightness to zero) after the display-off timeout. This is a brightness behavior only — it never switches pages and never requires an idle page.
-
relay_left(relay switch) -
relay_right(relay switch) -
use_relay_left(switch)
Defines if the physical button left should use the internal relay
use_relay_right(switch)
Defines if the physical button right should use the internal relay
last_interaction(millis()/1000)
Last time when a interaction happened with the display
Brightness and Dimming¶
The configuration for this functionality is in esphome/nspanel_haui/scripts_display.yaml
The display has 3 states: on, dimmed and off
The whole brightness and dimming functionality is implemented on the ESPHome device.
Turning the display off (use_auto_display_off) and dimming (use_auto_dimming) never
require a page switch - automatic page switching is a separate hub behavior.
The brightness change process is processed this way:
-
Within Dimming Timeout: full brightness is being used (State on)
-
After Dimming Timeout: dimmed brightness is being used, duration is defined by dimming duration (State dimmed)
-
After Display-Off Timeout: brightness goes to 0 if display-off mode is activated, duration is defined by dimming duration (State off)
stateDiagram-v2
[*] --> On: Touch / Button
state On {
[*] --> FullBrightness: dimming_timeout not expired
FullBrightness --> FullBrightness: touch resets timer
}
On --> Dimmed: dimming_timeout expires
On --> On: touch resets timer
state Dimmed {
[*] --> DimBrightness: dim_brightness applied
}
Dimmed --> On: touch / button
Dimmed --> Off: display-off_timeout expires
state Off {
[*] --> BlackScreen: brightness = 0
}
Off --> On: touch / button
Off --> Dimmed: interaction (if display-off timeout is disabled)
note right of On
Display at full brightness
Duration: dimming_timeout
end note
note right of Dimmed
Display at dim_brightness
Duration: display-off_timeout - dimming_timeout
end note
note right of Off
Display off
Only if display-off mode activated
end note
Brightness Sensors¶
Brightness related sensors.
brightness(0 - 100)
Current brightness (last value set by the device, not read back from the Nextion)
brightness_full(0 - 100)
Full brightness, will be restored on reboot
brightness_dim(0 - 100)
Dimmed brightness, will be restored on reboot
duration_dim(0.0 - 5.0)
Dimming duration, will be restored on reboot
timeout_dim(0 - 3600)
Dimming timeout after last interaction, will be restored on reboot Value > 0 activates the timeout
timeout_display_off(0 - 3600)
Display-off timeout, restored on reboot Value > 0 activates the timeout
Temperature¶
Temperature related sensor.
temperature(in celsius)
Temperature sensor reading
temperature_correction(-10.0 - 10.0)
Correction for sensor reading
Display Model¶
display_model(0 - 1)
Display orientation synced by the hub from the device's display_model
setting (0 = landscape, 1 = portrait). The gesture script reads it to
detect edge swipes on the correct screen dimensions.
Buttons¶
Buttons provided by the ESP.
Restart Display(restart_display, disabled by default)
Restarts the display
Diagnostics¶
Diagnostics related sensors. The production package and universal release image
expose the diagnostics used by the integration. The local development config
also includes debug.yaml, which adds uptime and ESPHome debug sensors for
troubleshooting; these are not included in release firmware. Your own device
YAML must not duplicate entities that its selected packages already include.
- TFT Version: Version of the TFT file (read from the display)
- Client Status:
online/offline, the hub connection state - WIFI Info: IP, SSID, BSSID and RSSI
Exposed sensors (curated set)¶
Only these ESPHome entities are exposed to Home Assistant. Everything else
(page, touch_x, touch_y, last_interaction, tft_version,
req_val_component, req_txt_component, res_txt, res_val) stays
internal: true — it exists only for the firmware ↔ hub pipeline and would
only clutter the entity registry.
Entity (device nspanel_kitchen) |
Purpose |
|---|---|
binary_sensor.nspanel_kitchen_touch |
A touch is currently active on the display |
binary_sensor.nspanel_kitchen_button_left / ..._button_right |
Physical relay buttons (diagnostic) |
sensor.nspanel_kitchen_ip / ..._ssid / ..._rssi |
WiFi/network info; also read by the editor's Device Manager |
text_sensor.nspanel_kitchen_client_status |
online / offline, the hub connection state |
sensor.nspanel_kitchen_brightness |
Current display brightness (the last value the hub set, 0–100) |
sensor.nspanel_kitchen_temperature |
Internal panel temperature (NTC, °C) |
The temperature calculation's adc and resistance sensors remain
disabled_by_default: true; only the final temperature sensor is exposed.
The hub looks the temperature sensor up through the entity registry by its
ESPHome-authored name (Temperature), so the example id above is only a
fallback: a renamed device or HA's area-prefixed entity ids (e.g.
sensor.nspanel_bedroom_temperature on a device called "NSPanel Sypialnia")
do not break the clock and weather panels reading it.
Other¶
For number and text retrieval the internal sensors req_txt_component,
req_val_component, res_txt, and res_val are used. These feed the
unified esphome.read_response event and make it possible to get values
from the display without adding a sensor for every single value to retrieve.