NxPanel - Replacement Firmware for Sonoff NSPanel

NxPanel

A replacement firmware for the Sonoff NSPanel. This is a full featured replacement which supports many more features than the stock firmware. It’s designed to be very configurable at runtime. You need no Nextion knowledge to use it. You control it via MQTT, using OH rules or such.

This is really only ideal for the EU version. If you have questions arounf US version compatibilty, I’d start with this post US Version

This first post will be for showing current supported screen and download links;

image

image

image

image

image

image

image

image

image

image

image

image

image

image

Current Development Version

3 Likes

NxPanel Installation

To install, firstly flash tasmota as described here;

This will allow you to access the stock screen firmware only

Update: Install has been made much easier

Instead of using the nspanel.be described above. Use the nxpanel.be below;

Once you’ve done that, simple type this into the tasmota console

InstallNxPanel

This will download the current version, flash it, then reboot the device in NxPanel

NxPanel will update to the current version automatically without you having to flash anything, if you enable this setting in the configuration page.

You can install it manually if you prefer;

FlashNextion http://<your-openhab-server>:8080/blah/nxpanel.tft

Going back to Stock Screen firmware

You can easily revert back to stock HMI firmware at any time, if you want. Simply use the same process as above. You just need to use the original TFT file. A link to that is here;

Original Stock HMI Firmware

This will revert the Nextion HMI to stock, not the Tasmota side. This process would involve re-flashing the backup taking during the flash of Tasmota. This is not in scope here.

How is works

Before reading on how to configure openhab to work with NxPanel, you might find it useful to jump to this post for a short explanation of how it works. As the following rules are the things to make it happend.

Configuration in OpenHab

You’ll need a channel trigger for the MQTT thing;

  - id: nxpanel_command
    channelTypeUID: mqtt:string
    label: NxPanel Command
    description: ""
    configuration:
      commandTopic: cmnd/nspanel/nxpanel
  - id: nxpanel_page_trigger
    channelTypeUID: mqtt:trigger
    label: NxPanel Page Trigger
    description: ""
    configuration:
      stateTopic: tele/nspanel/RESULT

And you’ll need a rule something like this configure your screens;

This is a groovy script. Ensure you have install the Groovy Script binding and choose Groovy when you create the rule. This is a template for you to start with. It works with a couple of components on my system and should give you an idea what you need to do for yours.

Note: There is also a javascript version of the following rule here which many might find easier.

import org.slf4j.LoggerFactory

def PAGE_HOME            = 1
def PAGE_2_BUTTON        = 2
def PAGE_3_BUTTON        = 3
def PAGE_4_BUTTON        = 4
def PAGE_6_BUTTON        = 5
def PAGE_8_BUTTON        = 6
def PAGE_DIMMER          = 7
def PAGE_DIMMER_COLOR    = 8
def PAGE_THERMOSTAT      = 9
def PAGE_ALERT_1         = 10
def PAGE_ALERT_2         = 11
def PAGE_ALARM           = 12
def PAGE_MEDIA           = 13
def PAGE_PLAYLIST        = 14
def PAGE_STATUS          = 15

def BUTTON_UNUSED        = 0
def BUTTON_TOGGLE        = 1
def BUTTON_PUSH          = 2
def BUTTON_DIMMER        = 3
def BUTTON_DIMMER_COLOR  = 4
def BUTTON_PAGE          = 10

def ICON_BLANK           = 0 
def ICON_BULB            = 1 
def ICON_DIMMER          = 2 
def ICON_DIMMER_COLOR    = 3
def ICON_VACUUM          = 4 
def ICON_BED             = 5 
def ICON_HOUSE           = 6 
def ICON_SOFA            = 7 
def ICON_BELL            = 8 
def ICON_HEAT            = 9
def ICON_CURTAINS        = 10 
def ICON_MUSIC           = 11
def ICON_BINARY          = 12 
def ICON_FAN             = 13
def ICON_SWITCH          = 14
def ICON_TALK            = 15
def ICON_INFO            = 16

def NONE                 = 0

def logger = LoggerFactory.getLogger("org.openhab.core.automation.nspanel")

def mqtt = actions.get("mqtt","mqtt:broker:mqtt_broker")

def str = event.getEvent()

logger.info("Demo page rules called")

if (str.indexOf('{"page":')!=0) {
  return
}

/*
 * Utility functions - start
 */

def makeButton(bid,label,type,icon=null,state=null,next=null) {
  var str = ""<<((bid==1)?"":",")
  str<<'{"bid":'<<bid<<',"label":"'<<label<<'","type":'<<type
  if (next!=null) {
    str<<',"next":'<<next
  }
  if (state!=null) {
    str<<',"state":'<<state
  }
  if (icon!=null) {
    str<<',"icon":'<<icon
  }
  str<<'}'
  return str
}

def makePage(pid,name) {
  var str = new StringBuilder('{"refresh":')
  str<<'{"pid":'<<pid<<',"name":"'<<name<<'",'
  return str
}

def makeEmptySync(pid) {
  var str = new StringBuilder('{"sync":')
  str<<'{"pid":'<<pid<<'}}'
  return str
}

def makeEmptyRefresh(pid) {
  var str = new StringBuilder('{"refresh":')
  str<<'{"pid":'<<pid<<'}}'
  return str
}

def makeSyncButtonStart(pid,bid,state) {
  var str = new StringBuilder('{"sync":')
  str<<'{"pid":'<<pid
  str<<',buttons:[{"bid":'<<bid<<',"state":'<<state<<'}'
  return str
}

def addSyncButton(bid,state) {
  var str = ',{"bid":'<<bid<<',"state":'<<state<<'}'
  return str
}

/*
 * Utility functions - end
 */

/*
 * Get data from the page message
 * (would be good to use JsonSluper here but currently can't access)
 */

var i = str.indexOf("\"pid\"")
var i2 = str.indexOf(",",i+7)
var id = str.substring(i+7,i2)
i = str.indexOf("\"format\"")
i2 = str.indexOf(",",i+10)
var format = str.substring(i+10,i2)

// check if a full refresh or just a status update
var refresh = str.indexOf("refresh")>0

var json

def PANEL_MAIN          = 10
def PANEL_BEDROOM_1     = 11
def PANEL_BEDROOM_2     = 12
def PANEL_LOUNGE        = 13
def PANEL_CABIN         = 14
def PANEL_CABIN_THERMO  = 15
def PANEL_CABIN_LIGHTS  = 16
def PANEL_LOUNGE_FAN    = 17
def PANEL_LOUNGE_LIGHT  = 18
def PANEL_STATUS        = 19
def PANEL_MUSIC         = 20

def TOPIC = "cmnd/nspanel/nxpanel"

logger.info("updating page ... "+id)

switch (id as int) {  
  
  case PANEL_MAIN :
    logger.info("main panel")
    // set these from your own items
    movie_state = 1
    lounge_state = 0
    cabin_state = 0
    hall_light_state = 1
    if (refresh) {
      json = makePage(id,'Lounge')
      json<<format<<'buttons:['
      json<<makeButton(1,"Movie",BUTTON_TOGGLE,ICON_BULB,movie_state)
      json<<makeButton(2,"Lounge",BUTTON_TOGGLE,ICON_BULB,lounge_state)
      json<<makeButton(3,"Hall",BUTTON_PUSH,ICON_HOUSE)
      json<<makeButton(4,"Bedroom",BUTTON_PAGE,ICON_BED,PAGE_6_BUTTON,PANEL_BEDROOM_1)
      json<<makeButton(5,"Temp",BUTTON_PAGE,ICON_HEAT,PAGE_THERMOSTAT,PANEL_CABIN_THERMO)
      json<<makeButton(6,"Light",BUTTON_DIMMER,ICON_DIMMER,hall_light_state,PANEL_LOUNGE_LIGHT)
      json<<makeButton(7,"Dimmer",BUTTON_DIMMER_COLOR,ICON_DIMMER_COLOR,cabin_state,PANEL_CABIN_LIGHTS)
      json<<makeButton(8,"Status",BUTTON_PAGE,ICON_INFO,PAGE_STATUS,PANEL_STATUS)
      json<<"]}}"
    } else {
      json = makeSyncButtonStart(id,1,movie_state)
      json<<addSyncButton(2,lounge_state)
      json<<"]}}"
    }
    mqtt.publishMQTT(TOPIC, json.toString())
    break
  case PANEL_BEDROOM_1 :
    // set these from your own items
    fan_state = 1
    if (refresh) {
      json = makePage(id,'Bedroom 1')
      json<<format<<'buttons:['
      json<<makeButton(1,"A",BUTTON_PUSH,ICON_HOUSE)
      json<<makeButton(2,"Fan",BUTTON_DIMMER,ICON_FAN,fan_state,PANEL_LOUNGE_FAN)
      json<<makeButton(3,"C",BUTTON_PUSH,ICON_SOFA)
      json<<makeButton(4,"Music",BUTTON_PAGE,ICON_MUSIC,PAGE_MEDIA,PANEL_MUSIC)
      json<<makeButton(5,"D",BUTTON_PUSH,ICON_TALK)
      json<<makeButton(6,"Alarm",BUTTON_PAGE,ICON_BELL,PAGE_ALARM,NONE)
      json<<"]}}"
    } else {
      json = makeEmptySync(id)
    }
    mqtt.publishMQTT(TOPIC, json.toString())
    break
  case PANEL_BEDROOM_2 :
    json = makeEmptySync(id)
    mqtt.publishMQTT(TOPIC, json.toString())
    break
  case PANEL_LOUNGE :
    json = makeEmptySync(id)
    mqtt.publishMQTT(TOPIC, json.toString())
    break
  case PANEL_CABIN :
    json = makePage(id,'Cabin')
    json<<"}}"
    mqtt.publishMQTT(TOPIC, json.toString())
    break
  case PANEL_CABIN_THERMO :
    // set these from your own items
    var heater = 1
    var auto = 0
    var temp = 15
    var set = 21
    json = makePage(id,'Cabin')
    json<<format<<',"therm":{'
    json<<'"set":'<<set<<',"temp":'<<temp<<',"heat":'<<heater<<',"state":'<<auto<<'"'
    json<<"}}"
    mqtt.publishMQTT(TOPIC, json.toString())
    break
  case PANEL_CABIN_LIGHTS :
    json = makePage(id,'Cabin Lights')
    json<<'"power":'<<ON<<',"hsbcolor":'<<'"10,100,50"'
    json<<"}}"
    mqtt.publishMQTT(TOPIC, json.toString())
    break
  case PANEL_LOUNGE_FAN :
    // set these from your own items
    fan_state = ON
    fan_setting = 3
    json = makePage(id,'Lounge Fan')
    json<<'"power":'<<fan_state<<',"min":'<<1<<',"max":'<<4<<',"icon":'<<ICON_FAN<<',"dimmer":'<<fan_setting
    json<<"}}"
    mqtt.publishMQTT(TOPIC, json.toString())
    break
  case PANEL_LOUNGE_LIGHT :
    json = makePage(id,'Lounge Light')
    json<<'"power":'<<ON<<',"dimmer":'<<30
    json<<"}}"
    mqtt.publishMQTT(TOPIC, json.toString())
    break
  case PANEL_STATUS :
    json = makePage(id,'System Status')
    json<<'"status":['
    json<<'{"id":'<<1<<',"text":'<<'"Gate":'<<',"value":'<<'"Open"'<<',"color":'<<2<<'}'
    json<<','
    json<<'{"id":'<<2<<',"text":'<<'"Window":'<<',"value":'<<',"Shut"'<<',"color":'<<3<<'}'
    json<<','
    json<<'{"id":'<<5<<',"text":'<<'"Room Temp":'<<',"value":'<<',"20°C"'<<'}'
    json<<']}}'
    mqtt.publishMQTT(TOPIC, json.toString())
    break
  case PANEL_MUSIC :
    json = makePage(id,'Sonos Player')
    // set these from your own items
    json<<'"artist":'<<'"New Order"'<<',"album":'<<'"Movement"'<<',"track":'<<'"Power Play"'<<',"volume":'<<70
    json<<"}}"
    mqtt.publishMQTT(TOPIC, json.toString())
    break    
  default :
    logger.info("unknown page!")
    break

}

logger.info("rule done")


The Process

As NxPanel moves to a new screen, it will message over MQTT the page id to identify what room it’s looking for to render the page. If the screen was last used to show that room, it messages it only needs a sync of the room. the OH rule then only needs to send back a lightweight message with the states of buttons and so on. If the page was not showing this room last time, the message sent to OH will be a refresh one, and OH will send a heavyweight message back which will tell NxPanel how to render all the buttons and label and room titles.

Only the Home screen and Button pages will remember the last room, other screens will call refresh each time. This is purely down to memory of the Nextion. It only has 3K, which is not enough to remember that state of each screen page.

Useful Things

The buzzer on the NSPanel doesn’t seem to be set to work by default. I needed to do the following in Tasmota. The Alarm panel used this as do some other places.

buzzerpwm 1

UK Timezones

TimeZone 99
TimeDST 0,0,3,1,1,60
TimeSTD 0,0,10,1,2,0
2 Likes

Is there a way to go back to stock NSPanel?

Yes, you just download that a flash it the same way, I jumped back and forward a lot initially. Until the home screen was stable.

Hi Mike I wanted to try this out - I’ve downloaded the tft and be files. I have tasmota already, what is the process for flashing the tft file? On another one of your posts you mention a jar file but I don’t see a reference to that here.

Many thanks

@Julian_Divett I’ve added the flashing guide above.

Do you know where that stock .tft or hmi file is to download & flash? I cannot find it anywhere online. I did a backup before I flashed to Tasmota…I don’t need to go back to that, do I?

try here;

Thanks I’ve got it up and running.

When it is running I notice that I can’t access the panel via Tasmota? Is this normal.

I am not sure what you mean?

Sorry I didn’t explain that very well. I mean when I have it set up in Openhab I can’t access the tasmota console. I’ve checked the openhab logs and I am getting this error on the rule. I’ve always built rules using .rules files not through the interface and I’ve never done scripting. Do I need some sort of binding or plugin?

I copied your example and added a rule using the script editor.

2022-02-05 09:37:12.098 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'ca275a12c8' failed: <eval>:1:0 Expected an operand but found import
import org.slf4j.LoggerFactory
^ in <eval> at line number 1 at column number 0

Reading elsewhere on the forums it seems that imports don’t normally work in javascript?

I understand now.

Firstly, things you put in .rules files are rules written in a different format of script language, mine are written in groovy, not Javascript. They look kind of similar! :slight_smile: You can put them in text files, but they would need to go into a different folder. You’d also need to change them a little to add the part about what triggers it, as this is done in the GUI.

(I’ve changed the above to say use Groovy. It wasn’t obvious, my mistake!)

To be honest, easiest way for you to do it is through the the GUI. It’s easier as you don’t need to mess with any files.

  1. Make sure you have the Groovy script binding installed.
  2. Go into the GUI rules, create a new rule, call it what you like.
  3. Enter the “if channel changes do this part” from all the nice guided choices you get
  4. Pick “Groovy Script” for action. and past the code over in there.

That’s it. Dead easy and no messing with files.

Hope that helps.

NxPanel - v1.0.0-beta3

Note on the screens a new notification

image

Where are screen should be in sync as opened, this will activate. It turns yellow as the screen notifies the backend it’s been activated.

image

When the backend send’s the information to render the screen, it will turn green, then fade. It after 2 secs, the screen has not received this, it will turn red and request again from backend. If you have red your backend is not sending a sync or refresh back to the panel.

I think most of the parts are working now to get a system configured. A few tweeks around the light control and media screens. But it should be there to configure your pages and all your sub pages and switches. and all buttons should sent MQTT for you to process as you wish.

ok I’ve got groovy installed and I’ve copied in your sample code but I’m still getting an error (pasted at the bottom).

I just changed it to have a single page with 2 buttons. I am trying to connect to 2 lights as you can see.

Some questions:
what is the ir.getItem syntax specifically .state==ON?1:0
case 10 is fixed as the swipe right page - does this mean the page Id needs to be 10 as well?

  case "10" :
    walls = ir.getItem("Lt_Masterbed_Walls").state==ON?1:0
    bath = ir.getItem("Lt_Masterbed_Bath_Walls").state==ON?1:0
  
    if (refresh) {
      json = makePage(10,'Bedroom',2)
      json<<format<<',buttons:['
      json<<makeButton(1,"Walls",1,1,walls)
      json<<makeButton(2,"Bath",1,1,bath)
      json<<"]}}"
    } else {
      json = '{ "sync": {} }'
    }
    events.sendCommand("nxpanel_command",json.toString())
    break
 }

This is the error code:

2022-02-05 14:41:15.560 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'ca275a12c8' failed: javax.script.ScriptException: groovy.lang.MissingPropertyException: No such property: event for class: Script2

Can you paste the full script and a screen of the gui rule page.

As for the syntax. The state in the json needs to 0 or 1, so it’s an expression that evaluates to true or false, if its true, its the value after the ? If its false its the value after the colon

Also, you won’t be able to test the script by pressing run in the gui as event is the trigger event passed in from the item that triggered it. It going to the page on the panel and the panel sending the mqtt message via the channel. So you’ll née to right swipe home to make the panel send the message

Ok problem resolved. I was running the script as you guessed from the openhab UI.

You also need to create an item called nxpanel_command linked to the channel.

How do I get out of the dimming screen?

If you mean the temporary configuration screen, make sure your on in the latest version, I missed down swipe on first beta. Down swipe works on current

Glad you got it going.

It’s coming together nicely now, will be configuring my own setup on it now

You are not stuck to groovy scripts either, it’s all mqtt messages so you can use any scripting to handle it, I just prefer groovy

Yes great work. The down swipe works but i can’t get out of it. How are you coding the mqtt messages,. with the base tasmota I just created channels for each command. It woul be nice to see some examples of these and also updating the weather.

swipe on the setting page 100% works on latest version. Swipe worked on other screens, just not that one earlier. I’d re flash from the link in the first post, just to make sure you are on latest.

It you type…

Weblog 4

In tasmota, you can the see the messages in more detail. Weblog 2, back to normal.