Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1dd885e
Remove US Land files
edwardtfn Apr 7, 2026
9ab1e33
Merge branch 'main' into v9999.99.9
edwardtfn Apr 7, 2026
f719cd9
Rebuild boot params csv contract
edwardtfn Apr 8, 2026
1bdc747
Review boot params csv contract
edwardtfn Apr 8, 2026
29ad4f2
New format for `packages` as a list
edwardtfn Apr 8, 2026
7cd6ac0
Updated display model selector
edwardtfn Apr 8, 2026
6ecef3b
Lint
edwardtfn Apr 8, 2026
24f1261
Convert to string before `prints`
edwardtfn Apr 8, 2026
d0adffe
Magic Flag with 4 bytes
edwardtfn Apr 8, 2026
447b3ec
Use new format for `packages`
edwardtfn Apr 8, 2026
896f8c7
Add page `canvas`
edwardtfn Apr 8, 2026
d3c7e46
Add page_canvas.yaml
edwardtfn Apr 8, 2026
fdd94d0
Use auxiliar `DISPLAY_MODES_BLANK_TEXT`
edwardtfn Apr 8, 2026
7aeec91
No need to update the blueprint this time
edwardtfn Apr 8, 2026
09ad95b
Keep `url: "default"` backward-compatible or update the action contract
edwardtfn Apr 9, 2026
b4fc5d9
The error path doesn't actually abort the script
edwardtfn Apr 9, 2026
5c69f31
Do not change initial option with upload package
edwardtfn Apr 9, 2026
5f8b0dc
Apply nextion_update_url before the auto-update gates
edwardtfn Apr 9, 2026
4439f3b
Resolve nested substitution before using: `DISPLAY_MODES_BLANK_TEXT`
edwardtfn Apr 9, 2026
c314ebf
Revert packages back to dict format
edwardtfn Apr 9, 2026
6d95877
Revert packages back to dict format
edwardtfn Apr 9, 2026
b767ca7
fix: Correct sentinel detection regex for min_blueprint_version in ve…
edwardtfn Apr 9, 2026
ca92103
Support 3 versions of contract
edwardtfn Apr 9, 2026
aa80864
Auto-adjust baud rate
edwardtfn Apr 9, 2026
5f58a76
Change baud rate to 921600
edwardtfn Apr 10, 2026
37ffd15
Trigger fallback flow with watchdog
edwardtfn Apr 10, 2026
23ed89c
Remove the duplicate post-probe success path
edwardtfn Apr 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/workflows/versioning.yml
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ jobs:
# If min_blueprint_version is set to the sentinel "next" or "${version}",
# replace it with the actual version being released, tying the compatibility
# requirement to this exact release.
if grep -qE "^ min_blueprint_version: (next|\$\{version\})$" "$ESPHOME_VERSION_FILE"; then
if grep -qE '^ min_blueprint_version: (next|\$\{version\})$' "$ESPHOME_VERSION_FILE"; then
sed -i \
"s/^ min_blueprint_version: .*/ min_blueprint_version: ${NEW_VERSION}/" \
"$ESPHOME_VERSION_FILE"
Expand Down
14 changes: 11 additions & 3 deletions components/nspanel_easy/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,17 @@ struct SystemFlags {
uint16_t version_check_ok : 1; ///< All component versions verified
uint16_t display_settings_received : 1; ///< All display settings received

// Runtime operation flags (bits 9-10)
// Runtime operation flags (bits 9-11)
uint16_t tft_upload_active : 1; ///< TFT firmware upload in progress
uint16_t ota_in_progress : 1; ///< Over-the-air update active
uint16_t display_sleep : 1; ///< Display is in sleep mode

// Reserved flags (bits 11-15)
uint16_t reserved : 5; ///< Reserved for future expansion
// Baud rate negotiation flags (bits 12-13)
uint16_t baud_fallback_active : 1; ///< UART is operating at fallback baud rate
uint16_t baud_negotiation_enabled : 1; ///< Baud rate negotiation is active (BAUD_RATE != BAUD_RATE_FALLBACK)

// Reserved flags (bits 14-15)
uint16_t reserved : 2; ///< Reserved for future expansion

// Default constructor - all flags start as false (zero-initialized)
SystemFlags()
Expand All @@ -50,6 +55,9 @@ struct SystemFlags {
display_settings_received(0),
tft_upload_active(0),
ota_in_progress(0),
display_sleep(0),
baud_fallback_active(0),
baud_negotiation_enabled(0),
reserved(0) {}
};

Expand Down
5 changes: 4 additions & 1 deletion components/nspanel_easy/hw_display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ static const char *TAG_COMPONENT_HW_DISPLAY = "nspanel.component.hw_display";
ThemeMode current_theme = ThemeMode::DARK; ///< Active display theme

uint8_t brightness_current = 100;
uint8_t display_mode = UINT8_MAX;
bool display_easy = false;
uint8_t display_mode_eeprom = UINT8_MAX; // Populated from boot report, UINT8_MAX = unknown
bool display_portrait = false;
bool display_valid = false;

} // namespace esphome::nspanel_easy

Expand Down
5 changes: 4 additions & 1 deletion components/nspanel_easy/hw_display.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ enum class ThemeMode : uint8_t {
extern ThemeMode current_theme; ///< Active display theme

extern uint8_t brightness_current;
extern uint8_t display_mode;
extern bool display_easy;
extern uint8_t display_mode_eeprom;
extern bool display_portrait;
extern bool display_valid;

} // namespace esphome::nspanel_easy

Expand Down
3 changes: 2 additions & 1 deletion components/nspanel_easy/pages.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ constexpr const char *const page_names[] = {
"climate", "settings", "screensaver", "light", "cover", "buttonpage01", "buttonpage02",
"buttonpage03", "buttonpage04", "notification", "qrcode", "entitypage01", "entitypage02", "entitypage03",
"entitypage04", "fan", "alarm", "keyb_num", "media_player", "confirm", "utilities",
"home_smpl", "debug", "water_heater", "theme_apply", "switch", "button"};
"home_smpl", "debug", "water_heater", "theme_apply", "switch", "button", "canvas"};

constexpr size_t PAGE_COUNT = sizeof(page_names) / sizeof(page_names[0]);
static_assert(PAGE_COUNT <= UINT8_MAX, "PAGE_COUNT exceeds uint8_t range");
Expand Down Expand Up @@ -98,6 +98,7 @@ static_assert(get_page_id("buttonpage01") != UINT8_MAX, "Missing required page:
static_assert(get_page_id("buttonpage02") != UINT8_MAX, "Missing required page: buttonpage02");
static_assert(get_page_id("buttonpage03") != UINT8_MAX, "Missing required page: buttonpage03");
static_assert(get_page_id("buttonpage04") != UINT8_MAX, "Missing required page: buttonpage04");
static_assert(get_page_id("canvas") != UINT8_MAX, "Missing required page: canvas");
static_assert(get_page_id("climate") != UINT8_MAX, "Missing required page: climate");
static_assert(get_page_id("confirm") != UINT8_MAX, "Missing required page: confirm");
static_assert(get_page_id("cover") != UINT8_MAX, "Missing required page: cover");
Expand Down
98 changes: 80 additions & 18 deletions docs/addon_upload_tft.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,17 @@ Depending on your configuration, the add-on will either:
1. **Automatically upload** the matching TFT file after a configurable wait period, or
2. **Wait for a manual trigger** via the "Update TFT display" button in Home Assistant.

The TFT file source is determined by the "Update TFT display - Model" selector
on the device's page in Home Assistant (**Settings** > **Devices & services** > **ESPHome**).
The TFT file URL is resolved in this order:

1. If `nextion_update_url` is set in your substitutions → use it as-is, bypassing all other logic
2. Otherwise → build the URL automatically from the **Display model** selector and the current
firmware version

> [!IMPORTANT]
> The **Display model** selector (under **Configuration** on your device's page in Home Assistant)
> serves two purposes: it determines which TFT file is downloaded during an upload, and it
> configures the panel's runtime behavior (touch calibration, button layout, display orientation).
> Always keep it set to the option matching your physical hardware.

## Installation

Expand Down Expand Up @@ -91,14 +100,40 @@ packages:

The following keys are available to be used in your `substitutions`:

<!-- markdownlint-disable MD013 MD033 -->
Key|Required|Supported values|Default|Description
:-|:-:|:-:|:-:|:-
upload_tft_automatically|Optional|`true` or `false`|`true`|When enabled, the device will automatically upload the TFT file when a version mismatch is detected after boot. When disabled, you must manually press the "Update TFT display" button in Home Assistant.
upload_tft_baud_rate|Optional|Positive integer (bps)|`115200`|Baud rate used for the serial transfer to the Nextion display. Lower values are more reliable on noisy setups; higher values are faster. Common values: `9600`, `115200`, `921600`.
upload_tft_wait_ms_after_boot|Optional|Positive integer (milliseconds)|`300000` (5 min)|Time to wait after the first NTP time sync before starting an automatic TFT upload. This delay allows the system to stabilize after boot. Reduce for faster updates; increase if you experience boot instability.
nextion_update_url|Optional|Valid HTTP/HTTPS URL|GitHub raw URL for EU TFT|The URL from which the TFT file is downloaded. This is used as a fallback when "Use nextion\_update\_url" is selected in the model selector. For most users, the model selector in Home Assistant is the preferred way to choose the TFT source.
<!-- markdownlint-enable MD013 MD033 -->
<!-- markdownlint-disable MD013 -->
| Key | Required | Supported values | Default | Description |
| :- | :-: | :-: | :-: | :- |
| `upload_tft_automatically` | Optional | `true` or `false` | `true` | When enabled, the device will automatically upload the TFT file when a version mismatch is detected after boot. When disabled, you must manually press the "Update TFT display" button in Home Assistant. |
| `upload_tft_baud_rate` | Optional | Positive integer (bps) | `115200` | Baud rate used for the serial transfer to the Nextion display. Lower values are more reliable on noisy setups; higher values are faster. Common values: `9600`, `115200`, `921600`. |
| `upload_tft_wait_ms_after_boot` | Optional | Positive integer (milliseconds) | `300000` (5 min) | Time to wait after the first NTP time sync before starting an automatic TFT upload. This delay allows the system to stabilize after boot. Reduce for faster updates; increase if you experience boot instability. |
| `nextion_update_base_url` | Optional | Valid HTTP/HTTPS base URL | `https://raw.githubusercontent.com/edwardtfn/NSPanel-Easy/v${version}/hmi` | Base URL used when building the TFT download URL automatically. Override this to host TFT files on a local server while still benefiting from automatic model and version selection. |
| `nextion_update_url` | Optional | Valid HTTP/HTTPS URL | _(empty)_ | Full URL override for the TFT file. When set, this takes absolute priority. The model selector and version logic are completely bypassed and this URL is used as-is. Use only when you need full control over the TFT source, such as for custom TFT files. See [important note below](#nextion_update_url-behaviour). |
<!-- markdownlint-enable MD013 -->

### `nextion_update_url` behaviour

> [!WARNING]
> Setting `nextion_update_url` disables all automatic URL management. The **Display model**
> selector is still used for runtime behavior (touch calibration, button layout), but the
> TFT file downloaded will always be the one at the URL you specified, regardless of which
> model is selected or which version is current. You are responsible for keeping this URL
> pointing to a compatible and up-to-date TFT file.

When `nextion_update_url` is left empty (the default), the add-on builds the download URL
automatically from the selected **Display model** and the current firmware version. This is
the recommended configuration for most users.

### Display model options

This add-on extends the **Display model** selector with additional options beyond the hardware
models defined in the base firmware:

| Option | TFT file | Notes |
| --- | --- | --- |
| NSPanel EU | `nspanel_landscape.tft` | Standard EU hardware |
| NSPanel US | `nspanel_portrait.tft` | Standard US hardware (portrait) |
| NSPanel US Landscape | `nspanel_landscape.tft` | US hardware mounted in landscape |
| NSPanel Blank | `nspanel_blank.tft` | First-time installation only. See [NSPanel Blank](nspanel_blank.md) |

### Example: Automatic updates with a shorter wait time

Expand Down Expand Up @@ -147,7 +182,11 @@ packages:
- nspanel_esphome.yaml
```

### Example: Using a local TFT file
### Example: Using a locally hosted TFT file

Use `nextion_update_url` only when you need to serve a custom or locally hosted TFT file.
For standard setups, the automatic URL resolution based on the **Display model** selector
is recommended instead.

```yaml
substitutions:
Expand All @@ -158,8 +197,8 @@ substitutions:
ota_password: "" # Optional: set OTA password, or use ${wifi_password} for backward compatibility (see migration guide)
language: en # Language code - see docs/localization.md for all supported codes

# Use a locally hosted TFT file
nextion_update_url: "http://homeassistant.local:8123/local/nspanel_eu.tft"
# Use a locally hosted TFT file - bypasses automatic model and version selection
nextion_update_url: "http://homeassistant.local:8123/local/nspanel_landscape.tft"

packages:
remote_package:
Expand All @@ -170,9 +209,31 @@ packages:
- nspanel_esphome.yaml
```

> [!NOTE]
> When using `nextion_update_url`, make sure to select "Use nextion\_update\_url"
> in the "Update TFT display - Model" selector on the device's page in Home Assistant.
### Example: Using a local base URL with automatic model selection

If your network cannot reach GitHub but you want to keep automatic model and version selection,
override `nextion_update_base_url` instead of `nextion_update_url`:

```yaml
substitutions:
device_name: "YOUR_NSPANEL_NAME"
friendly_name: "Your panel's friendly name"
wifi_ssid: !secret wifi_ssid
wifi_password: !secret wifi_password
ota_password: "" # Optional: set OTA password, or use ${wifi_password} for backward compatibility (see migration guide)
language: en # Language code - see docs/localization.md for all supported codes

# Mirror the TFT files locally and point to your server
nextion_update_base_url: "http://homeassistant.local:8123/local/nspanel-easy/"

packages:
remote_package:
url: https://github.com/edwardtfn/NSPanel-Easy
ref: latest
refresh: 300s
files:
- nspanel_esphome.yaml
```

## Updating the TFT

Expand All @@ -183,7 +244,7 @@ When `upload_tft_automatically` is set to `true` (the default), the device will:
1. Boot and connect to Home Assistant.
2. Receive the current TFT version from the display.
3. Compare it with the expected version from the firmware.
4. If a mismatch is detected and a standard model is selected, wait for the configured delay
4. If a mismatch is detected, wait for the configured delay
(`upload_tft_wait_ms_after_boot`) after the first NTP time sync.
5. Automatically start the TFT upload.

Expand All @@ -201,7 +262,8 @@ When `upload_tft_automatically` is set to `true` (the default), the device will:
To manually trigger a TFT upload:

1. Go to **Settings** > **Devices & services** > **ESPHome** and select your panel.
2. Under **Configuration**, select the appropriate model in "Update TFT display - Model".
2. Under **Configuration**, verify that **Display model** is set to the option matching
your physical hardware.
3. Press the "Update TFT display" button.
4. The display will start the update process and the device will restart when complete.

Expand Down
31 changes: 30 additions & 1 deletion docs/migration_from_blackymas.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,28 @@ For the full list of supported language codes, see [Localization](localization.m
> If you skip this step your panel will fall back to English after migration,
> regardless of what language you had selected in the old Blueprint.

#### `nextion_update_url`

If you had `nextion_update_url` defined in your substitutions, read this carefully
before updating.

In the Blackymas project, `nextion_update_url` was the primary way to specify the TFT
file source, and a dedicated selector option ("Use nextion\_update\_url") was required
to activate it. In NSPanel Easy, this substitution now acts as a **full override**.
When set, it bypasses the model selector and version logic entirely, and the specified
URL is used as-is for every upload.

**If you defined `nextion_update_url` only to select your panel model**, remove it from
your substitutions. The **Display model** selector in Home Assistant now handles model
selection automatically, including building the correct versioned download URL.

**If you defined `nextion_update_url` to host a custom or local TFT file**, you can
keep it, but be aware that automatic version management is disabled and you are responsible
for keeping the URL pointing to a compatible and up-to-date TFT file.

For full details on the new TFT upload behavior and available substitutions,
see the [TFT Upload Add-on documentation](addon_upload_tft.md).

#### Remote package reference

Find your `remote_package` block. It currently looks something like this:
Expand Down Expand Up @@ -125,6 +147,7 @@ packages:
| :------ | :------------------ | :------------------- |
| `ota_password` | It was set on the remote package to use your WiFi password | You have to add the substitution `ota_password: ${wifi_password}` for backward compatibility |
| `language` | Selected via Blueprint dropdown | Set as `language: xx` substitution in ESPHome YAML - see [Localization](localization.md) |
| `nextion_update_url` | Used to select the TFT model or override the URL | Now a full override only. Remove it unless you are hosting a custom TFT file locally |
| `url` | `https://github.com/Blackymas/NSPanel_HA_Blueprint` | `https://github.com/edwardtfn/NSPanel-Easy` |
| `ref` | `main` (or a specific version) | `latest` |
| Add-on file paths | Root level (e.g. `nspanel_esphome_addon_climate_heat.yaml`) | Inside `esphome/` folder (e.g. `esphome/nspanel_esphome_addon_climate_heat.yaml`) |
Expand Down Expand Up @@ -172,7 +195,7 @@ packages:
## CJK language support

CJK (Chinese, Japanese, Korean) language support is now built into the standard TFT files.
There are no separate CJK variants, simply select the model matching your hardware
There are no separate CJK variants. Simply select the model matching your hardware
(**NSPanel EU**, **NSPanel US**, or **NSPanel US Landscape**) regardless of the language you use.

---
Expand Down Expand Up @@ -382,3 +405,9 @@ A: No. You can migrate one panel at a time. Each panel is independent.
**Q: My panel is now showing English instead of my language after migration.**
A: Language is no longer configured in the Blueprint. Add `language: xx` to your
ESPHome `substitutions` block and reflash. See [Localization](localization.md) for supported codes.

**Q: I had `nextion_update_url` defined. Do I need to keep it?**
A: Only if you are hosting a custom or local TFT file. If you set it to select your panel
model in the Blackymas project, remove it. Model selection is now handled automatically
by the **Display model** selector in Home Assistant. See the
[TFT Upload Add-on documentation](addon_upload_tft.md) for details.
Loading
Loading