Sonoff NSPanel et ESPHome

Bonjour,

Est-ce que ce nspanel continu de fonctionner en tant qu’interrupteur « physique » si on se retrouve sans internet ?

Oui tant que HA est joignable (avec le réglage par défaut sinon le NSPanel reboot)

1 « J'aime »

Au vu des avis sur ITEAD, qui indique le contraire, vous confirmer que ça rentre dans un boîte d’encastrement ronde ?
Merci

Pas eu de souci chez moi. En France les boîtes font normalement 67mm de diamètre.
Faut avoir le neutre et ne pas avoir trop de câble ou domino dans le fond de la boîte si elle est de profondeur standard

Merci pour le retour, pour la profondeur pas de souci :wink:

Bonsoir à tous,
J’ai reçu mon nspanel vendredi. Après quelques heures, je suis arrivé à qqchose de pas mal mais pas aussi beau que le travail de @Childebert :wink:
J’ai une question, pour faire basculer le relai depuis HA, je suppose que c’est un appel au service ESPHome: nspanel_send_command mais quelle commande ?
Au passage, d’autres commandes utiles ?
Merci d’avance.

[Edit] : Je viens de voir l’entité switch … Désolé … Je débute sur HA après de nombreuses années sur Jeedom.

Salut

binary_sensor:
  - platform: nextion
    name: $device_name Prise 1 button
    page_id: 2
    component_id: 12
    on_click:
      - homeassistant.service:
          service: switch.toggle
          data:
            entity_id: switch.multiprise_jardin_l1 #piscine

le choix de l’appareil a piloter est fait par entity_id et le service c’est switch.toggle

A+ Fred

Merci bcp ! Je garde de côté.

En gros vous allez mettre tous ces interrupteurs dans chaque pièce ?

dommage qu’il existe pas en blanc …

Je n’en ai mis qu’un seul, au RDC facilement accessible. Il est surtout là pour les invités afin qu’ils puissent allumer/éteindre les lumières et qu’ils puissent contrôler les volets roulants sans toucher aux boutons physiques.
Pour nous tout est automatique et on se soucis de rien mais les invités même si on leur explique, cherchent toujours à éteindre la lumière ou à vouloir gérer les VR alors que tout se fait tout seul…
Je regarde pour le thermostat, qu’ils puissent régler la température à leur convenance (pas de commande de réglages manuelle disponible tout est géré tout seul par HA)

j’en ai acheté 2, un pour une utilisation a peu prêt similaire à @Makai et un deuxième que je voudrais programmer spécialement pour la gestion et surveillance de mes fourmilières.
Et si j’arrive a faire ce que je veux pour mes fourmilières, j’en verrai bien un troisième pour la gestion et surveillance d’un aquaterrarium.

Ok merci pour vos retours effectivement je ne pense pas que ce sois utilise d’en avoir partout pour que cela sois homogène dans la maison au niveau des interrupteurs

tu peux m’en dire plus …?

Je contrôle le chauffage et la clim avec HA. Aucune commande murale et je n’ai jamais sortie les télécommandes de leur emballage donc aucun moyen pour un visiteur de régler quoi que ce soit

je reprend un peu de temps pour me remettre sur le nspanel.

@Makai : est ce que tu utilises toujours le FW standard du nextion?
perso même en pointant vers le git pour le composant Nspanel, cela ne fonctionne plus…

Salut tu pourrais peut être créer ton propre sujet afin d’expliquer par exemple ce que tu veux faire, ce sera plus simple pour t’aider à avancer.

tu peux donner ton code ??? car mes 2 nspanel fonctionnent parfaitement

arf…
autant pour moi, l’erreur est que j’utilisais des widgets qui ne sont pas encore supportés.
d’ailleurs toujours pas de news pour les autres widgets? on est toujours que avec le ‹ scene › ?

Oui et non. On peut tout utiliser avec les dernières fonctionnalités ajoutées au PR

Je me rends compte que j’ai jamais posté mon dernier code qui utilise toutes les fonctionnalités du NSPanel avec l’interface par défaut (sauf le thermostat pas encore eu le temps d’y regarder).

Mon code ici
substitutions:
  devicename: NSPanel

esphome:
  name: nspanel
  comment: "NSPanel"

esp32:
  board: esp32dev

uart:
  tx_pin: 16
  rx_pin: 17
  baud_rate: 115200

external_components:
  - source: github://pr#2702
    components: ["nspanel"]

nspanel:
  id: nspanel_id
  time_id: current_time
  temperature: temperature
  screen_power_switch: screen_power
  eco_mode_switch: eco_mode
  relays:
    - relay_1
    - relay_2
  on_json_message:
    then:
      # Widget 1 (type:0x86 id:l_salon) 
      - if:
          condition:
            lambda: 'return (type == 0x86 && root.containsKey("id") && strcasecmp(root["id"], "l_salon") == 0);'
          then:
            - if:
                condition:
                  lambda: 'return (root["params"].containsKey("switch") && root["params"]["switch"] == "on");'
                then:
                  - homeassistant.service:
                      service: light.turn_on
                      data:
                        entity_id: light.salon_bulb
            - if:
                condition:
                  lambda: 'return (root["params"].containsKey("switch") && root["params"]["switch"] == "off");'
                then:
                  - homeassistant.service:
                      service: light.turn_off
                      data:
                        entity_id: light.salon_bulb
            - if:
                condition:
                  lambda: 'return (root["params"].containsKey("white"));'
                then:
                  - homeassistant.service:
                      service: light.turn_on
                      data:
                        entity_id: light.salon_bulb
                        brightness: !lambda 'return int(root["params"]["white"]["br"].as<float>() / 100 * 254);'
      # Widget 2 (type:0x86 id:l_entree) 
      - if:
          condition:
            lambda: 'return (type == 0x86 && root.containsKey("id") && strcasecmp(root["id"], "l_entree") == 0);'
          then:
            - if:
                condition:
                  lambda: 'return (root["params"].containsKey("switch") && root["params"]["switch"] == "on");'
                then:
                  - homeassistant.service:
                      service: light.turn_on
                      data:
                        entity_id: light.entree_bulb
            - if:
                condition:
                  lambda: 'return (root["params"].containsKey("switch") && root["params"]["switch"] == "off");'
                then:
                  - homeassistant.service:
                      service: light.turn_off
                      data:
                        entity_id: light.entree_bulb
            - if:
                condition:
                  lambda: 'return (root["params"].containsKey("white"));'
                then:
                  - sensor.template.publish:
                      id: l_entree_ct
                      state: !lambda 'return int(root["params"]["white"]["ct"].as<float>());'
                  - homeassistant.service:
                      service: light.turn_on
                      data:
                        entity_id: light.entree_bulb
                        brightness: !lambda 'return int(root["params"]["white"]["br"].as<float>() / 100 * 254);'
                        color_temp: !lambda 'return int(id(l_entree_ct).state);'
      # Widget 3 (type:0x86 id:vr_salon) 
      - if:
          condition:
            lambda: 'return (type == 0x86 && root.containsKey("id") && strcasecmp(root["id"], "vr_salon") == 0);'
          then:
            - if:
                condition:
                  lambda: 'return (root["params"].containsKey("switch") && root["params"]["switch"] == "on");'
                then:
                  - homeassistant.service:
                      service: cover.open_cover
                      data:
                        entity_id: cover.salon_volet_roulant
            - if:
                condition:
                  lambda: 'return (root["params"].containsKey("switch") && root["params"]["switch"] == "off");'
                then:
                  - homeassistant.service:
                      service: cover.close_cover
                      data:
                        entity_id: cover.salon_volet_roulant
            - if:
                condition:
                  lambda: 'return (root["params"].containsKey("switch") && root["params"]["switch"] == "pause");'
                then:
                  - homeassistant.service:
                      service: cover.stop_cover
                      data:
                        entity_id: cover.salon_volet_roulant
            - if:
                condition:
                  lambda: 'return (root["params"].containsKey("setclose"));'
                then:
                  - homeassistant.service:
                      service: cover.set_cover_position
                      data:
                        entity_id: cover.salon_volet_roulant
                        position: !lambda 'return 100 - root["params"]["setclose"].as<int>();'
      # Widget 4 (type:0x86 id:vr_cuisine) 
      - if:
          condition:
            lambda: 'return (type == 0x86 && root.containsKey("id") && strcasecmp(root["id"], "vr_cuisine") == 0);'
          then:
            - if:
                condition:
                  lambda: 'return (root["params"].containsKey("switch") && root["params"]["switch"] == "on");'
                then:
                  - homeassistant.service:
                      service: cover.open_cover
                      data:
                        entity_id: cover.cuisine_volet_roulant
            - if:
                condition:
                  lambda: 'return (root["params"].containsKey("switch") && root["params"]["switch"] == "off");'
                then:
                  - homeassistant.service:
                      service: cover.close_cover
                      data:
                        entity_id: cover.cuisine_volet_roulant
            - if:
                condition:
                  lambda: 'return (root["params"].containsKey("switch") && root["params"]["switch"] == "pause");'
                then:
                  - homeassistant.service:
                      service: cover.stop_cover
                      data:
                        entity_id: cover.cuisine_volet_roulant
            - if:
                condition:
                  lambda: 'return (root["params"].containsKey("setclose"));'
                then:
                  - homeassistant.service:
                      service: cover.set_cover_position
                      data:
                        entity_id: cover.cuisine_volet_roulant
                        position: !lambda 'return 100 - root["params"]["setclose"].as<int>();'
      # Widget 5 (type:0x86 id:p_terrasse) 
      - if:
          condition:
            lambda: 'return (type == 0x86 && root.containsKey("id") && strcasecmp(root["id"], "p_terrasse") == 0);'
          then:
            - if:
                condition:
                  lambda: 'return (root["params"].containsKey("switch") && root["params"]["switch"] == "on");'
                then:
                  - homeassistant.service:
                      service: switch.turn_on
                      data:
                        entity_id: switch.terrasse_plug
            - if:
                condition:
                  lambda: 'return (root["params"].containsKey("switch") && root["params"]["switch"] == "off");'
                then:
                  - homeassistant.service:
                      service: switch.turn_off
                      data:
                        entity_id: switch.terrasse_plug

binary_sensor:
  - platform: status
    name: "$devicename Status"

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

  - platform: gpio
    name: "$devicename Right Button"
    pin:
      number: 27
      inverted: true
    on_multi_click:
#      - timing:
#          - ON for at most 1s
#          - OFF for at least 0.2s
#        then:
#          - switch.toggle: relay_2
      - timing:
          - ON for at least 5s
        then:
          - button.press: restart_button

  - platform: homeassistant
    id: salon_light
    entity_id: light.salon_bulb
    on_press:
      then:
        - lambda: 'id(nspanel_id).send_json_command(0x86,"{\"id\":\"l_salon\",\"params\":{\"switch\":\"on\"}}");'
    on_release:
      then:
        - lambda: 'id(nspanel_id).send_json_command(0x86,"{\"id\":\"l_salon\",\"params\":{\"switch\":\"off\"}}");'

  - platform: homeassistant
    id: entree_light
    entity_id: light.entree_bulb
    on_press:
      then:
        - lambda: 'id(nspanel_id).send_json_command(0x86,"{\"id\":\"l_entree\",\"params\":{\"switch\":\"on\"}}");'
    on_release:
      then:
        - lambda: 'id(nspanel_id).send_json_command(0x86,"{\"id\":\"l_entree\",\"params\":{\"switch\":\"off\"}}");'

  - platform: homeassistant
    id: terrasse_plug
    entity_id: switch.terrasse_plug
    on_press:
      then:
        - lambda: 'id(nspanel_id).send_json_command(0x86,"{\"id\":\"p_terrasse\",\"params\":{\"switch\":\"on\"}}");'
    on_release:
      then:
        - lambda: 'id(nspanel_id).send_json_command(0x86,"{\"id\":\"p_terrasse\",\"params\":{\"switch\":\"off\"}}");'

button:
  - platform: restart
    name: "$devicename Restart"
    id: restart_button

sensor:
  - platform: uptime
    id: uptime_sec

  - platform: wifi_signal
    name: "$devicename WiFi Signal"
    update_interval: 120s

  - platform: template
    id: uptime_timestamp
    name: "$devicename Uptime"
    device_class: timestamp
    entity_category: diagnostic
    accuracy_decimals: 0
    update_interval: never
    lambda: |-
      static float timestamp = (
        id(current_time).utcnow().timestamp - id(uptime_sec).state
      );
      return timestamp;

  - platform: adc
    id: ntc_source
    pin: 38
    attenuation: 11db

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

  - platform: ntc
    name: "$devicename Temperature"
    id: temperature
    sensor: resistance_sensor
    calibration:
      b_constant: 3950
      reference_temperature: 25°C
      reference_resistance: 10kOhm
    internal: true

  - platform: homeassistant
    id: entree_light_brightness
    entity_id: light.entree_bulb
    attribute: brightness
    filters:
      - calibrate_linear:
         - 1 -> 1
         - 254 -> 100
    on_value:
      then:
        - script.execute: ha_light_entree_update

  - platform: homeassistant
    id: entree_light_color_temp
    entity_id: light.entree_bulb
    attribute: color_temp
    filters:
      - calibrate_linear:
         - 250 -> 254
         - 454 -> 0
    on_value:
      then:
        - script.execute: ha_light_entree_update

  - platform: homeassistant
    id: salon_light_brightness
    entity_id: light.salon_bulb
    attribute: brightness
    filters:
      - calibrate_linear:
         - 1 -> 1
         - 254 -> 100
    on_value:
      then:
        - script.execute: ha_light_salon_update

  - platform: homeassistant
    id: salon_cover_position
    entity_id: cover.salon_volet_roulant
    attribute: current_position
    filters:
      - calibrate_linear:
         - 0 -> 100
         - 100 -> 0
    on_value:
      then:
        - script.execute: ha_curtain_salon_update_pos

  - platform: homeassistant
    id: cuisine_cover_position
    entity_id: cover.cuisine_volet_roulant
    attribute: current_position
    filters:
      - calibrate_linear:
         - 0 -> 100
         - 100 -> 0
    on_value:
      then:
        - script.execute: ha_curtain_cuisine_update_pos

  - platform: template
    id: l_entree_ct
    filters:
      - calibrate_linear:
         - 0 -> 454
         - 254 -> 250

switch:
  - platform: gpio
    name: "$devicename Relay 1"
    id: relay_1
    pin: 22

  - platform: gpio
    name: "$devicename Relay 2"
    id: relay_2
    pin: 19

  - platform: gpio
    name: "$devicename Screen Power"
    id: screen_power
    entity_category: config
    pin:
      number: 4
      inverted: true
    restore_mode: ALWAYS_OFF
    internal: true
    on_turn_on:
      then:
        - delay: 6s
        - script.execute: nspanel_init

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

text_sensor:
  - platform: version
    name: "$devicename Version"
    hide_timestamp: true

  - platform: wifi_info
    ip_address:
      name: "$devicename IPv4"
      icon: "mdi:server-network"
    ssid:
      name: "$devicename Connected SSID"
      icon: "mdi:wifi"

  - platform: homeassistant
    id: salon_cover
    entity_id: cover.salon_volet_roulant
    on_value:
      then:
        - script.execute: ha_curtain_salon_update_state

  - platform: homeassistant
    id: cuisine_cover
    entity_id: cover.cuisine_volet_roulant
    on_value:
      then:
        - script.execute: ha_curtain_cuisine_update_state

script:
  - id: nspanel_init
    # Script to initialise panel on full power on (or when screen is powered on from ESP)
    then:
      # Setup Widgets - must send all 8
      # Widget 1
      - lambda: |-
          id(nspanel_id).send_json_command(0x86,"{\"HMI_resources\":[{\"index\":1,\"ctype\":\"device\",\"id\":\"l_salon\",\"uiid\":52}]}"); 
          id(nspanel_id).send_json_command(0x86,"{\"relation\":[{\"ctype\":\"device\",\"id\":\"l_salon\",\"name\":\"Salon\",\"online\":1,\"params\":{\"switch\":\"off\",\"ltype\":\"white\",\"white\":{\"br\":0,\"ct\":0}}]}");
      # Widget 2
      - lambda: |-
          id(nspanel_id).send_json_command(0x86,"{\"HMI_resources\":[{\"index\":2,\"ctype\":\"device\",\"id\":\"l_entree\",\"uiid\":52}]}");
          id(nspanel_id).send_json_command(0x86,"{\"relation\":[{\"ctype\":\"device\",\"id\":\"l_entree\",\"name\":\"Entrée\",\"online\":1,\"params\":{\"switch\":\"off\",\"ltype\":\"white\",\"white\":{\"br\":0,\"ct\":0}}]}");
      # Widget 3
      - lambda: |-
          id(nspanel_id).send_json_command(0x86,"{\"HMI_resources\":[{\"index\":3,\"ctype\":\"device\",\"id\":\"vr_salon\",\"uiid\":11}]}");
          id(nspanel_id).send_json_command(0x86,"{\"relation\":[{\"ctype\":\"device\",\"id\":\"vr_salon\",\"name\":\"Salon\",\"online\":1,\"params\":{\"switch\":\"on\"}]}");
      # Widget 4
      - lambda: |-
          id(nspanel_id).send_json_command(0x86,"{\"HMI_resources\":[{\"index\":4,\"ctype\":\"device\",\"id\":\"vr_cuisine\",\"uiid\":11}]}");
          id(nspanel_id).send_json_command(0x86,"{\"relation\":[{\"ctype\":\"device\",\"id\":\"vr_cuisine\",\"name\":\"Cuisine\",\"online\":1,\"params\":{\"switch\":\"on\"}]}");
      # Widget 5
      - lambda: |-
          id(nspanel_id).send_json_command(0x86,"{\"HMI_resources\":[{\"index\":5,\"ctype\":\"device\",\"id\":\"p_terrasse\",\"uiid\":1}]}");
          id(nspanel_id).send_json_command(0x86,"{\"relation\":[{\"ctype\":\"device\",\"id\":\"p_terrasse\",\"name\":\"Pompe\",\"online\":1,\"params\":{\"switch\":\"off\"}]}");
      # Widget 6
      - lambda: 'id(nspanel_id).send_json_command(0x86,"{\"index\":6,\"type\":\"delete\"}");'
      # Widget 7
      - lambda: 'id(nspanel_id).send_json_command(0x86,"{\"index\":7,\"type\":\"delete\"}");'
      # Widget 8
      - lambda: 'id(nspanel_id).send_json_command(0x86,"{\"index\":8,\"type\":\"delete\"}");'
      # Update existing Entities / Widgets
      - script.execute: ha_light_salon_update
      - script.execute: ha_light_entree_update
      - script.execute: ha_curtain_salon_update_state
      - script.execute: ha_curtain_cuisine_update_state
      - script.execute: ha_plug_terrasse_update

  - id: ha_light_salon_update
    then:
      - lambda: |-
          if (id(salon_light).state) {
            int i_brightness = int(id(salon_light_brightness).state);
            id(nspanel_id).send_json_command(0x86,"{\"id\":\"l_salon\",\"params\":{\"switch\":\"on\",\"ltype\":\"white\",\"white\":{\"br\":" + to_string(i_brightness) + ",\"ct\":0}}}");
          } else {
            id(nspanel_id).send_json_command(0x86,"{\"id\":\"l_salon\",\"params\":{\"switch\":\"off\"}}");
          }

  - id: ha_light_entree_update
    then:
      - lambda: |-
          if (id(entree_light).state) {
            int i_brightness = int(id(entree_light_brightness).state);
            int i_color_temp = int(id(entree_light_color_temp).state);
            id(nspanel_id).send_json_command(0x86,"{\"id\":\"l_entree\",\"params\":{\"switch\":\"on\",\"ltype\":\"white\",\"white\":{\"br\":" + to_string(i_brightness) + ",\"ct\":" + to_string(i_color_temp) + "}}}");
          } else {
            id(nspanel_id).send_json_command(0x86,"{\"id\":\"l_entree\",\"params\":{\"switch\":\"off\"}}");
          }

  - id: ha_curtain_salon_update_state
    then:
      - lambda: |-
         if(id(salon_cover).state == "open") {
            id(nspanel_id).send_json_command(0x86,"{\"id\":\"vr_salon\",\"params\":{\"switch\":\"on\"}}");
          } else {
            id(nspanel_id).send_json_command(0x86,"{\"id\":\"vr_salon\",\"params\":{\"switch\":\"off\"}}");
          }
  - id: ha_curtain_salon_update_pos
    then:
      - lambda: |-
         int vr_pos = int(id(salon_cover_position).state);
         id(nspanel_id).send_json_command(0x86,"{\"id\":\"vr_salon\",\"params\":{\"setclose\":" + to_string(vr_pos) + "}}");

  - id: ha_curtain_cuisine_update_state
    then:
      - lambda: |-
         if(id(cuisine_cover).state == "open") {
            id(nspanel_id).send_json_command(0x86,"{\"id\":\"vr_cuisine\",\"params\":{\"switch\":\"on\"}}");
          } else {
            id(nspanel_id).send_json_command(0x86,"{\"id\":\"vr_cuisine\",\"params\":{\"switch\":\"off\"}}");
          }
  - id: ha_curtain_cuisine_update_pos
    then:
      - lambda: |-
         int vr_pos = int(id(cuisine_cover_position).state);
         id(nspanel_id).send_json_command(0x86,"{\"id\":\"vr_cuisine\",\"params\":{\"setclose\":" + to_string(vr_pos) + "}}");

  - id: ha_plug_terrasse_update
    then:
      - lambda: |-
          if(id(terrasse_plug).state) {
            id(nspanel_id).send_json_command(0x86,"{\"id\":\"p_terrasse\",\"params\":{\"switch\":\"on\"}}");
          } else {
            id(nspanel_id).send_json_command(0x86,"{\"id\":\"p_terrasse\",\"params\":{\"switch\":\"off\"}}");
          }

time:
  - platform: homeassistant
    timezone: Europe/Paris
    id: current_time
    on_time_sync:
      - component.update: uptime_timestamp

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

rtttl:
  output: buzzer_out
  id: buzzer

wifi:
  networks:
    - ssid: !secret ssid
      password: !secret password
    - ssid: !secret ssid_live
      password: !secret password_live
  ap:
    ssid: "$devicename Fallback Hotspot"
    password: !secret api_ota_pwd

captive_portal:

web_server:
  port: 80
  include_internal: true
  auth:
    username: admin
    password: !secret api_ota_pwd

logger:

api:
  password: !secret api_ota_pwd
  services:
    - service: weather_data
      variables:
        icon: int
        temperature: int
        min: int
        max: int
      then:
        - lambda: id(nspanel_id).send_weather_data((nspanel::WeatherIcon)icon, temperature, min, max);
    - service: play_rtttl
      variables:
        song_str: string
      then:
        - rtttl.play:
            rtttl: !lambda 'return song_str;'

ota:
  password: !secret api_ota_pwd

Les widgets 1 et 2 sont des ampoules blanches (la seconde avec réglage de la température), 3 et 4 sont des volets roulants, le 5 une prise connectée. 6, 7 et 8 sont vides.
Si vous avez des questions sur le code n’hésitez pas !

Je me suis en grande partie inspiré de ce code : HomeAssistantConfig/nspanel-1.yaml at master · peetereczek/HomeAssistantConfig · GitHub qui pour ceux que ça intéresse utilise aussi le thermostat.

1 « J'aime »

merci @makai pour le partage.
je constate que la gestion des widgets a bien évolué avec le json…
j’ai déployé ton code en changeant les cover. et Light. en mettant mes entités sans rien changer d’autres (pas touché au nom des script)
mais malheureusement pas de widget qui s’affiche sur mon panel.
est ce qu’il faut faire qq chose dans HA pour envoyer un JSON?

merci

Rien à faire dans HA, tout est dans ESPHome.
L’affichage des widgets se fait dans le script :

script:
  - id: nspanel_init
    # Script to initialise panel on full power on (or when screen is powered on from ESP)
    then:
      # Setup Widgets - must send all 8

Ensuite, l’envoi initial des statuts des entités se fait dans les autres scripts.