Sonoff NSPanel by ITead - Smart Scene Wall Switch based on ESP32 and custom Nextion Touch Screen Panel Display (non-Pro variant)

Thank you! I think your reply crossed with my edit. I’ll give that a try for Q2 now.

This feels like a hacking session or design sprint at work :slight_smile:

I did see you did find the answer for the first question :+1:
I have been working on this all day good thing it was raining all day

1 Like

Yaml, HMI, tft and images available here

edit: update yaml to include @crlogic work on syncing states on display with home assistant

I think you could save a lot of time and effort. Might be too late. I’m using just 2 images of 480px x 320px for my panel. One has everything off, one has everything on.

Set the STA to “crop image”. When you set picc and picc2 on a button that is, say 100px x 100px then it will only visually change the 100 x 100 section like using the crop image. It would save you having to slice all your screens into little buttons and your code will always be the same. I’m just testing the theory so I could be wrong. That’s how it seems to be working for me though.

Off
Off

On
On


To read the input_booleans I use for the"house mode" in HA. I have 3 for day, evening and night which just change the behaviour of a load of Node-RED flows. Returns “on” of “off”:

#This is the first icon for sunrise
  - platform: homeassistant
    id: day_mode
    entity_id: input_boolean.day_mode

Then, under the display section:

# This section looks at the text sensor above (day_mode) and sets the images used for a button with the STA set to "crop image"  
    lambda: |-
      int status_day=0;
      if (id(day_mode).state == "off") {
        status_day=0;
      } else if (id(day_mode).state == "on") {
        status_day=1;
      }
      id(disp1).send_command_printf("page0.b0.picc=%i", status_day);
      id(disp1).send_command_printf("page0.b0.picc2=%i", status_day);

So, b0 is the button with the STA set to “crop image”. It’s 100px x 100px. I’m just using the 2 images above. off is image id 0. on is image id 1. When the state of an input_boolean changes to on, for example, the display just shows the 100px x 100px section from the “on” image making it look like only that image has changed.

I might be teaching everyone to suck eggs with this but, if I write it down, It’ll stick in my head. I’ve tested it on my live box and it seems to work. I’m loathe to do too much as it’s the middle of the night, If I mess up and the bedroom lights go on my other half will be in here VERY quickly to let me know :grinning_face_with_smiling_eyes:

1 Like

Has anyone gotten pages to work?

I have three pages defined in the Nextion Editor with page pagename defined in the “Touch Press Event”. This works in the debug simulator of the editor (gif below), but does nothing when uploaded to the NSPanel.
Nextion Pages

So I went looking through the docs and found it.goto_page("pagename");

Adding this to the yaml kinda works; it just goes to a phantom page, not the pages I defined. And only if I use a number, not a name.

  - platform: nextion
    name: $device_name page0_goto_alarm
    id: page0_goto_alarm
    page_id: 0
    component_id: 13
    internal: true
    on_press:
      then:
           # lambda: id(disp1).goto_page("pagealarm"); # does nothing
           lambda: id(disp1).goto_page("1"); # goes to phantom page

Here is the phantom page;
[edit] link https://photos.app.goo.gl/CWyp2HCRReHWZF16A

[edit] if you have read this far, disregard everything. I’m blind. Upload your tft to the correct path people. Or waste hours like me!

Hi Christopher,
I have received 2 weeks back several EU version of the NSPanels, as part of the kickstarter project.
I have followed in detail your [great] video, but I unfortunately cannot upload the TFT file into the NS-Panel…I am turning all possibilities since this morning and getting exhausted. The GPIO 16 and 17 (UART) seem to not connect the panel. Do you see anything wrong I could have done?
on 20:39:34 on this log, I am calling the upload_tft function.
The Nextion screen refuses to upload the new TFT from the internal UART of the Nspanel.

Esphome log extract is hereby:

20:39:27][D][text_sensor:067]: 'weather_symbol': Sending state 'rainy'
[20:39:27][W][nextion:078]: Nextion is not connected! 
[20:39:27][W][nextion:078]: Nextion is not connected! 
[20:39:28][W][nextion:078]: Nextion is not connected! 
[20:39:28][W][nextion:078]: Nextion is not connected! 
[20:39:29][W][nextion:078]: Nextion is not connected! 
[20:39:29][W][nextion:078]: Nextion is not connected! 
[20:39:30][W][nextion:078]: Nextion is not connected! 
[20:39:30][W][nextion:078]: Nextion is not connected! 
[20:39:31][W][nextion:078]: Nextion is not connected! 
[20:39:32][W][nextion:078]: Nextion is not connected! 
[20:39:32][W][nextion:078]: Nextion is not connected! 
[20:39:33][W][nextion:078]: Nextion is not connected! 
[20:39:33][W][nextion:078]: Nextion is not connected! 
[20:39:34][D][nextion_upload:169]: Connected
[20:39:34][D][nextion_upload:175]: Requesting URL: http://10.0.0.120:8123/local/hmi.tft
[20:39:34][D][nextion_upload:209]: Updating Nextion ...
[20:39:34][D][nextion_upload:235]: Waiting for upgrade response
[20:39:34][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:34][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:34][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:34][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:35][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:35][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:35][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:35][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:35][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:35][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:35][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:35][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:35][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:36][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:36][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:36][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:36][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:36][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:36][E][uart:015]: Reading from UART timed out at byte 0!
[20:39:36][D][nextion_upload:239]: Upgrade response is  19
[20:39:36][D][nextion_upload:242]: Available 0 : 0x00
[20:39:36][D][nextion_upload:242]: Available 1 : 0x00
[20:39:36][D][nextion_upload:242]: Available 2 : 0x00
[20:39:36][D][nextion_upload:242]: Available 3 : 0x00
[20:39:36][D][nextion_upload:242]: Available 4 : 0x00
[20:39:36][D][nextion_upload:242]: Available 5 : 0x00
[20:39:36][D][nextion_upload:242]: Available 6 : 0x00
[20:39:36][D][nextion_upload:242]: Available 7 : 0x00
[20:39:36][D][nextion_upload:242]: Available 8 : 0x00
[20:39:36][D][nextion_upload:242]: Available 9 : 0x00
[20:39:36][D][nextion_upload:242]: Available 10 : 0x00
[20:39:36][D][nextion_upload:242]: Available 11 : 0x00
[20:39:36][D][nextion_upload:242]: Available 12 : 0x00
[20:39:36][D][nextion_upload:242]: Available 13 : 0x00
[20:39:36][D][nextion_upload:242]: Available 14 : 0x00
[20:39:36][D][nextion_upload:242]: Available 15 : 0x00
[20:39:36][D][nextion_upload:242]: Available 16 : 0x00
[20:39:36][D][nextion_upload:242]: Available 17 : 0x00
[20:39:36][D][nextion_upload:242]: Available 18 : 0x00
[20:39:36][D][nextion_upload:248]: preparation for tft update failed 0 ""
[20:39:36][D][nextion_upload:324]: Restarting Nextion
[20:39:38][D][nextion_upload:327]: Restarting esphome
INFO nspanel.local: Error while reading incoming messages: Error while reading data: [Errno 104] Connection reset by peer
INFO Disconnected from ESPHome API for nspanel.local
WARNING Disconnected from API
INFO nspanel.local: Ping Failed: Error while reading data: [Errno 104] Connection reset by peer
INFO Successfully connected to nspanel.local
[20:39:44][W][nextion:078]: Nextion is not connected! 
[20:39:44][W][nextion:078]: Nextion is not connected!  

Any idea of what I could have done wrong is welcome.

Best, Felix

Any chance you can post your YAML config somewhere? My guess is that you’re not importing the external component (e.g. here) needed to get the display out of Sonoff’s proprietary mode.

Thank you for your time, Christopher.

I am not sure I understang what you mean by the external components. Do you mean the tag external_components refering to pr#2956? I am not sure how this works, but I guess it has to do with the Protocol Reparse Mode change of the Nextion TFT? The tag is in the code.
Here is the code below (I started copying your code, then I tried out this one which is added lately in your git page).

substitutions:
  device_name: nspanel-dev

# Example config.yaml
esphome:
  name: nspanel
  comment: $device_name

esp32:
  board: esp32dev

wifi:
  ssid: fha2
  password: '32323245'

time:
  - platform: homeassistant
    id: homeassistant_time
    on_time:
      - seconds: 0
        minutes: /1
        then:
          - lambda: id(disp1).set_component_text_printf("Home.time", "%02i:%02i", id(homeassistant_time).now().hour, id(homeassistant_time).now().minute);
          - lambda: id(disp1).set_component_text_printf("Home.date", "%i-%02i-%02i", id(homeassistant_time).now().year, id(homeassistant_time).now().month, id(homeassistant_time).now().day_of_month);
          - lambda: id(disp1).set_component_text_printf("Home.temp", "%.1f", id(current_temperature).state);
          - lambda: id(disp1).set_component_text_printf("Home.tempfl", "%.1f", id(temp_feelslike).state);

api:
  services:
    # Service to play a song
    - service: play_rtttl
      variables:
        song_str: string
      then:
        - rtttl.play:
            rtttl: !lambda 'return song_str;'
    - service: upload_tft
      then:
        - lambda: 'id(disp1)->upload_tft();'

logger:
  baud_rate: 0
  level: DEBUG

ota:

uart:
  tx_pin: 16
  rx_pin: 17
  baud_rate: 115200
  id: tf_uart
#  debug:
#    direction: BOTH
#    dummy_receiver: false
#    after:
#      delimiter: "\n"
#    sequence:
#      - lambda: UARTDebug::log_string(direction, bytes);

external_components:
  - source: github://pr#2956
    components: [nextion]
    refresh: 1h

# A reboot button is always useful
button:
  - platform: restart
    name: $device_name Restart    

binary_sensor:
  - platform: gpio
    name: $device_name Left Button
    pin:
      number: 14
      inverted: true
    on_click:
      - switch.toggle: relay_1

  - platform: gpio
    name: $device_name Right Button
    pin:
      number: 27
      inverted: true
    on_click:
      - switch.toggle: relay_2
      
  - platform: nextion
    name: $device_name Music previous
    page_id: 1
    component_id: 1

  - platform: nextion
    name: $device_name Music play pause
    page_id: 1
    component_id: 2

  - platform: nextion
    name: $device_name Music next
    page_id: 1
    component_id: 3

  - platform: nextion
    name: $device_name Music cast
    page_id: 1
    component_id: 4

  - platform: nextion
    name: $device_name Music voldown
    page_id: 1
    component_id: 9
    
  - platform: nextion
    name: $device_name Music volup
    page_id: 1
    component_id: 10
    
  - platform: nextion
    name: $device_name Playlist cocktail
    page_id: 1
    component_id: 11

  - platform: nextion
    name: $device_name Playlist food
    page_id: 1
    component_id: 12
    
  - platform: nextion
    name: $device_name Playlist dance
    page_id: 1
    component_id: 13

  - platform: nextion
    name: $device_name Playlist note
    page_id: 1
    component_id: 14    

output:
  - platform: ledc
    id: buzzer_out
    pin:
      number: 21

switch:
  - platform: gpio
    name: $device_name Relay 1
    id: relay_1
    pin:
      number: 22

  - platform: gpio
    name: $device_name Relay 2
    id: relay_2
    pin:
      number: 19

  - platform: gpio
    name: $device_name Screen Power
    id: screen_power
    entity_category: config
    pin:
      number: 4
      inverted: true
    restore_mode: ALWAYS_ON

  - platform: template
    name: $device_name Energy Saving Mode
    id: eco_mode
    entity_category: config
    restore_state: true
    optimistic: true

rtttl:
  id: buzzer
  output: buzzer_out

sensor:
  - platform: adc
    id: ntc_source
    pin: 38
    update_interval: 10s
    attenuation: 11db

  - platform: resistance
    id: resistance_sensor
    sensor: ntc_source
    configuration: DOWNSTREAM
    resistor: 11.2kOhm

  - platform: ntc
    id: temperature
    sensor: resistance_sensor
    calibration:
      b_constant: 3950
      reference_temperature: 25°C
      reference_resistance: 10kOhm
    name: $device_name Temperature
    
  - platform: uptime
    name: $device_name Uptime Sensor
    id: uptime_sensor
    update_interval: 60s
    on_raw_value:
      then:
        - text_sensor.template.publish:
            id: uptime_human
            state: !lambda |-
                int seconds = round(id(uptime_sensor).raw_state);
                int days = seconds / (24 * 3600);
                seconds = seconds % (24 * 3600);
                int hours = seconds / 3600;
                seconds = seconds % 3600;
                int minutes = seconds /  60;
                seconds = seconds % 60;
                return (
                  (days ? to_string(days) + "d " : "") +
                  (hours ? to_string(hours) + "h " : "") +
                  (minutes ? to_string(minutes) + "m " : "") +
                  (to_string(seconds) + "s")
                ).c_str();

  - platform: wifi_signal
    name: $device_name WiFi Signal Sensor
    update_interval: 60s
    
  - platform: nextion
    id: screen_current_page
    variable_name: dp
    
  - platform: homeassistant
    id: current_temperature
    entity_id: weather.home
    attribute: temperature
    on_value:
      # Push it to the display
      then:
        - lambda: id(disp1).set_component_text_printf("Home.temp", "%.1f", id(current_temperature).state);
        
  - platform: homeassistant
    id: temp_feelslike
    entity_id: sensor.home_temperature_feels_like
    on_value:
      # Push it to the display
      then:
        - lambda: id(disp1).set_component_text_printf("Home.tempfl", "%.1f", id(temp_feelslike).state);

text_sensor:
  - platform: template
    name: $device_name Uptime Human Readable
    id: uptime_human
    icon: mdi:clock-start

  - platform: version
    name: $device_name ESPHome Version

  - platform: homeassistant
    id: music_artist
    entity_id: media_player.sonos_vardagsrum
    attribute: media_artist
    on_value:
      then:
        - lambda: id(disp1).set_component_text_printf("Music.music_sn", "%s", id(music_artist).state.c_str());

  - platform: homeassistant
    id: music_title
    entity_id: media_player.sonos_vardagsrum
    attribute: media_title
    on_value:
      then:
        - lambda: id(disp1).set_component_text_printf("Music.music_an", "%s", id(music_title).state.c_str());

  - platform: homeassistant
    id: sun_sun
    entity_id: sun.sun

  - platform: homeassistant
    id: weather_symbol
    entity_id: weather.home
    on_value:
      then:
        - lambda: |-
            int symbol=5; // 5 is a empty box.
            if (id(weather_symbol).state == "clear-night") {
              symbol=6;
            } else if (id(weather_symbol).state == "cloudy") {
              symbol=7;
              if (id(sun_sun).state == "below_horizon") {
                symbol=8;
              }
            } else if (id(weather_symbol).state == "fog") {
              symbol=9;
            } else if (id(weather_symbol).state == "hail" || id(weather_symbol).state == "snowy-rainy") {
              symbol=10;
            } else if (id(weather_symbol).state == "lightning") {
              symbol=11;
            } else if (id(weather_symbol).state == "lightning-rainy" || id(weather_symbol).state == "exceptional") {
              symbol=12;
              if (id(sun_sun).state == "below_horizon") {
                symbol=13;
              }
            } else if (id(weather_symbol).state == "partlycloudy") {
              symbol=14;
              if (id(sun_sun).state == "below_horizon") {
                symbol=8;
              }
            } else if (id(weather_symbol).state == "pouring") {
              symbol=15;
            } else if (id(weather_symbol).state == "rainy") {
              symbol=16;
            } else if (id(weather_symbol).state == "snowy") {
              symbol=17;
            } else if (id(weather_symbol).state == "sunny") {
              symbol=18;
            } else if (id(weather_symbol).state == "windy" || id(weather_symbol).state == "windy-variant") {
              symbol=19;              
            }
            id(disp1).send_command_printf("Home.weather_symbol.pic=%i", symbol);
    
number:
  platform: template
  name: $device_name Brightness
  id: brightness
  entity_category: config
  unit_of_measurement: '%'
  min_value: 0
  max_value: 100
  step: 1
  initial_value: 30
  set_action:
    then:
      - lambda: 'id(disp1).set_backlight_brightness(x/100);'
      
# Configure the screen itself
display:
  - platform: nextion
    id: disp1
    uart_id: tf_uart
    tft_url: 'http://10.0.0.120:8123/local/hmi.tft'
    # A little fun...
    on_setup:
      then:
        - number.set:
            id: brightness
            value: 30

#        - rtttl.play: "twobits:d=4,o=5,b=220:c6,8g,8g,a,g,p,b,c6"

Do you spot any obvious error?

Best regards,
Felix

Hi Christopher,
You were right, I wasnt importing the http file. The mistake is that I omitted the quotes around the http link. In my last email I had added them, but hadnt pressed “OTA import” yet.
Now it works like a charm, I imported your video yaml (with the “GO red!” button), and it worked at first try…

I am very thankful that you spent some time on that issue…believe me I spent most of my day figuring out what I had done wrong. Now it is solved, I can focus on making my scheme.

All the best, and please continue doing videos!!
Felix

More progress

Working on a thermostat

Don’t have the coding done just the start of the TFT

Anyone have any idea how to best implement the esp side of it?

20220110_171538_1

3 Likes

You are right, there are also 68 mm electrical boxes around in the EU. But 60 mm are also pretty popular
A friend of mine also measured his box and it’s also 60 mm in diameter.

Maybe 60 mm is only popular in Germany?

I’m sure I will not be the only person with this problem and I just try to warn people with 60 mm boxes :slight_smile:

1 Like

@botts Cool! I have not tried this myself yet but have some thoughts. I assume the command set provided in ESPHome is not sufficient to read the component type you are using for this? If so, would it be possible to

  • Create a empty HMI page and add a text/number component to that.
  • Make the field above global
  • Add a code snippet to the graph and button components to push the thermostat graph’s value to the text/number field
  • Fetch that components value to ESPHome
    That would need to be polled which is not ideal. If it is possible (I have not tested) to push a button/touch area from the Nextion script, then the script mentioned earlier could push a component on the hidden page which would notify ESPHome to pull the data? On the other hand, a thermostat within a home is a rather slowly acting component, so even if it is polled once a minute, that might be sufficient?

Not the shortest path, but I would try something like that if it cannot be done directly.

On a general note I think it’s very nice to see that there is alot of progress with multiple components. I have refined my own template with further functions (light control, alarm mode and bootup screen) and will publish it on Github within shortly. As we are many persons working with basically the same, but with different specific functionality, I think it would be great to try to collect the different functions into one project (HMI project and ESPHome config). That way it is rather simple for everybody to pick the components they want to create their own system. :slight_smile:

2 Likes

My contribution today is investigating Nextion display objects pic/crop changes;

After adding pages to my HMI it looks like all HA icon states are lost on page change. So you end up needing to refresh these after numerous scenarios;

  • on_setup for tft updates
  • on_boot for reboots
  • on page change
  • maybe sleep? further testing needed

Code here

Have you set the HMI components to global? I think that is required to persist data on page changes.

1 Like

@marcfager on the Up and down arrows i have added the below code to the Touch Press Events

if(j0.val<89)
{
  j0.val++
  j0.val++
  j0.val++
}
if(n0.val<30)
{
  n0.val++
}

which updates the value of the set temp displayed number

Just cant find away to push this the home assistant.

So…

I changed it to a text box and can update my home assistant climate by adding

binary_sensor:
  - platform: nextion
    name: $device_name Temp Up
    id: temp_up_button    
    page_id: 8
    component_id: 3  
    on_press:
      then:
        if:
          condition:
            lambda: 'return id(temp_up_binary).state;'
          then:
            - lambda: id(disp1).set_component_text_printf("ThermSlide.t2", "%.1f", id(set_temp).state);   
            - component.update: disp1                 
    
  - platform: nextion
    name: $device_name Temp Down
    id: temp_down_button    
    page_id: 8
    component_id: 4     
    on_press:
      then:
        if:
          condition:
            lambda: 'return id(temp_down_binary).state;'
          then:
            - lambda: id(disp1).set_component_text_printf("ThermSlide.t2", "%.1f", id(set_temp).state);  
            - component.update: disp1 
sensor:
  - platform: homeassistant
    id: set_temp
    entity_id: climate.climate
    attribute: temperature
    on_value:
      # Push it to the display
      then:
        - lambda: id(disp1).set_component_text_printf("ThermSlide.t2", "%.1f", id(set_temp).state);  

This has the up and down arrows controlling the temp on the climate and displaying the set temp on the NSPanel.

Just dont know how to sync the slider with the up and down arrow or sync the slider with the value of the set temp in home assistant

Any ideas on using the number box instead?20220111_004953_1

to make the climate increase on each button press i had to create 2 scripts and automation to trigger them

alias: NSpanel - Increase Temp by 1
description: ''
trigger:
  - platform: state
    entity_id: binary_sensor.nspanel_1_temp_up
    to: 'on'
condition: []
action:
  - service: script.turn_on
    target:
      entity_id: script.climate_heat_up_by1
mode: single

alias: NSpanel - Decrease Temp by 1
description: ''
trigger:
  - platform: state
    entity_id: binary_sensor.nspanel_1_temp_down
    to: 'on'
condition: []
action:
  - service: script.turn_on
    target:
      entity_id: script.climate_down_by_1
mode: single
alias: Climate Heat Down by 1
sequence:
  - service: climate.set_temperature
    data_template:
      entity_id: climate.climate
      temperature: '{{(state_attr(''climate.climate'' , ''temperature'')|round(0)) - 1 }}'
mode: single
icon: mdi:download

alias: 'Climate Heat Up by 1 '
sequence:
  - service: climate.set_temperature
    data_template:
      entity_id: climate.climate
      temperature: '{{(state_attr(''climate.climate'' , ''temperature'')|round(0)) + 1 }}'
mode: single
icon: mdi:upload

Not sure how to do this is esphome ???

As we are many persons working with basically the same, but with different specific functionality, I think it would be great to try to collect the different functions into one project (HMI project and ESPHome config). That way it is rather simple for everybody to pick the components they want to create their own system. :slight_smile:

Any ideas how we can do this?

I think I played with global but forget the outcome. Will test more.

[edit] lol, that was easy. global solves it. Thanks!

I have been trying to minimize HA scripts/automations needed. What about the nextion up/down button change the HA climate temperature directly (not the Nextion object). Then also have the HA climate temp as a sensor in ESPHome and that ESPHome sensor perform the set_component_text_printf("ThermSlide.t2"... on_state: … ?

Not sure if I made sense or not…

The most confusing part for is understanding what yaml is ESPHome and what yaml is Home Assistant script/automation. And what object is ESPHome vs HA vs Nextion. I am sure others are less cross-eyed than me.