Caution
All content in this repository is provided "as is" and may render your device unusable. Always exercise caution when working with your device. No warranty or guarantee is provided.
PanelElevate turns Android wall panels into first-class Home Assistant panels: the Shelly Wall Displays (XL, 2, X2, …) and the Sonoff NSPanel Pro, without root. One app, two parts:
- a native dashboard that renders your Lovelace dashboard live from Home Assistant without a WebView (Kotlin + Jetpack Compose, HA look and feel, instant popups even on the slow panel SoCs), including browser_mod emulation, WebRTC camera live view and a guest lock;
- a background service that exposes the panel's hardware to Home Assistant via MQTT auto-discovery: light/proximity/presence sensors, hardware buttons, relays, screen brightness and sleep, plus an ESPHome-compatible Bluetooth proxy and an acoustic doorbell detector.
The dashboard part is optional: the service also works next to the Home Assistant Companion app (or Fully Kiosk, WallPanel, …) and keeps it in front with its watchdog.
PanelElevate started as ShellyElevate, a service that kept the Shelly Wall Display's hardware usable next to the HA Companion app, whose WebView was the only fast option at the time.
The native dashboard renders the Home Assistant dashboards you already have (no extra configuration), here on a Shelly Wall Display XL (1280×800) and a Sonoff NSPanel Pro (480×480).
![]() Home with badges and auto-entities |
![]() Covers and media player |
![]() Mushroom climate cards |
![]() Weather forecast |
![]() Energy per device |
![]() Vacuum map with rooms |
![]() Doorbell popup (browser_mod) |
![]() Guest lock |
![]() Accept/decline popup |
Window view and floor plan are blurred for privacy.
| Feature | Description |
|---|---|
| Native dashboard | Lovelace renderer without WebView: sections/masonry views, badges, core and common HACS cards, swipe navigation, see Native dashboard and dashboard/README.md |
| browser_mod emulation | Existing popup, notification, navigate and TTS automations keep working (the panel registers as a browser_mod browser) |
| Camera live view | WebRTC playback of HA cameras (e.g. a doorbell), with a still until the first frame |
| Guest lock | While an entity is on, touches show a lock screen instead of operating the dashboard |
| MQTT integration | Home Assistant auto-discovery: sensors, buttons, relays, switches, events |
| Hardware buttons | Single/double/long press events (Shelly XL), instant on-screen feedback |
| Screen control | Idle dimming or real display off, wake on touch/proximity/button, auto brightness |
| Presence | Wide-range presence sensor (XL) or proximity (NSPanel Pro, adjustable threshold) |
| Relays | Built-in relays via MQTT or HTTP (Shelly: 1, NSPanel Pro: 2) |
| Bluetooth proxy | ESPHome-compatible proxy (passive scanning and active GATT connections), see ESPHome Bluetooth proxy |
| Doorbell detection | Recognises a doorbell chime on the microphone and reports it as an event, see Acoustic doorbell detection |
| App watchdog | Keeps the dashboard (native or another app) in front, restart via MQTT |
| HTTP API | Configuration and control, bearer token |
One APK runs on all of them; the device is detected from Build.MODEL and only the hardware it
really has is exposed (HTTP API, MQTT discovery).
| Device | Build.MODEL |
Relays | Temp / Hum | Light | Proximity | Presence | Buttons | Backlight |
|---|---|---|---|---|---|---|---|---|
Shelly Wall Display (STARGATE) |
Stargate |
1 | yes | yes | – | – | – | sysfs / Settings |
Shelly Wall Display 2 / X2 (ATLANTIS, PEGASUS) |
Atlantis, Pegasus |
1 | yes | yes | yes | – | – | sysfs / Settings |
Shelly Wall Display XL (BLAKE) |
Blake |
1 | yes | yes | yes | wide-range sensor | 4 | sysfs / Settings |
Sonoff NSPanel Pro 86 (NSPANEL_PRO) |
px30_evb |
2 (GPIO 114/115) | – | yes | yes (reflectance) | from proximity | – | Settings API (min = sysfs 10), sleep = display off (screen-off timeout) |
Unknown models are treated as the original Shelly Wall Display. If detection picks the wrong
profile, set deviceModel to one of the names in brackets (""/auto = detect; needs an API
token, takes effect after a service restart).
Enable adb on the panel (Shelly: tap F, H, F, F, H, F, H, H on Settings → General → About Device; NSPanel Pro: adb over network is on by default on port 5555), download the APK from the releases and run:
scripts/setup.sh -s <panel-ip>:5555 --apk PanelElevate-v1.0.0.apk \
--mqtt-broker tcp://<ha-ip> --mqtt-user <user> --mqtt-pass <password> --device-id kitchen \
--ha-url http://<ha-ip>:8123 --ha-token <long-lived token of the panel user> \
--dashboard <dashboard url_path> --browser-id "walldisplay Kitchen" \
--ble-proxy # optional: ESPHome Bluetooth proxyThe script detects the device (Shelly or NSPanel Pro), installs the APK, grants all permissions
(settings, overlay, battery optimisation, location for Bluetooth, microphone with --doorbell),
starts the service, sets a generated HTTP API token (printed at the end), applies the MQTT and
watchdog settings and configures the native dashboard. On the NSPanel Pro it also disables
NSPanel Pro Tools and stops Android from dimming on its own. Re-running it is safe;
scripts/setup.sh --help lists all options.
Use a long-lived token of a non-admin user per panel (the MQTT user can be the same user).
PanelElevate has a new Android package (io.github.simllll.panelelevate), so it installs next to
ShellyElevate (com.stretter.shellyelevateservice). Run the setup script with --migrate and the
same --device-id as before (the old mqttDeviceId): Home Assistant keeps all entities, because
MQTT topics, setting keys and discovery ids are unchanged.
- Where
adb rootworks (NSPanel Pro, userdebug firmware) the old preference files are copied as they are, including the dashboard token and API token. - Elsewhere the non-secret settings are read from the old app's HTTP API; pass the MQTT password and a dashboard token again.
- The old app is disabled, not uninstalled (
adb shell pm enable com.stretter.shellyelevateservicebrings it back); remove it withadb uninstall com.stretter.shellyelevateserviceonce you are happy. - The ESPHome Bluetooth proxy is now called
panelelevate-<device id>; Home Assistant picks up the new name of an existing entry by itself.
-
Enable adb (see above) and install the APK:
adb install PanelElevate-v1.0.0.apk -
Grant the permissions:
adb shell appops set io.github.simllll.panelelevate WRITE_SETTINGS allow adb shell appops set io.github.simllll.panelelevate SYSTEM_ALERT_WINDOW allow adb shell dumpsys deviceidle whitelist +io.github.simllll.panelelevate # Bluetooth proxy (Android 10+ also ACCESS_BACKGROUND_LOCATION), doorbell detector adb shell pm grant io.github.simllll.panelelevate android.permission.ACCESS_FINE_LOCATION adb shell pm grant io.github.simllll.panelelevate android.permission.RECORD_AUDIO
-
Start the service. The service is not exported, so
am start-foreground-servicefrom the adb shell is refused; instead launch the dashboard once (a freshly installed app gets no broadcasts until it was launched) and reinstall, which starts the service viaMY_PACKAGE_REPLACED(it also starts on every boot):adb shell am start -n io.github.simllll.panelelevate/.dashboard.DashboardActivity adb install -r PanelElevate-v1.0.0.apk adb shell dumpsys activity services io.github.simllll.panelelevate # verify -
Set an HTTP API token. Settings that redirect the device (
mqttBroker,mqttPort,mqttUsername,mqttDeviceId,watchdogPackage,deviceModel) can only be changed over HTTP with a token, and the token can only be set via adb (the receiver is protected byandroid.permission.DUMP):adb shell am broadcast -n io.github.simllll.panelelevate/.ApiTokenReceiver \ -a io.github.simllll.panelelevate.SET_API_TOKEN --es token 'choose-a-long-random-token' curl -H "Authorization: Bearer choose-a-long-random-token" http://<panel-ip>:8080/settings
-
Configure MQTT and the watchdog over HTTP (see Configuration) and the native dashboard via adb (see
dashboard/README.md).
PanelElevate replaces the closed-source NSPanel Pro Tools app (com.seaky.nspanelpro.tools):
one app provides MQTT sensors, relays, sleep/wake, brightness, reboot, watchdog and (optionally) the
native dashboard.
The NSPanel Pro runs an Android 8.1 userdebug build (480×480 screen). Its backlight node
(/sys/class/backlight/backlight/brightness) is root-only and the shipped /system/xbin/su is
not setuid, so PanelElevate works without root, with the same permissions NSPanel Pro Tools
had (WRITE_SETTINGS, WAKE_LOCK):
- Brightness / dimming go through the Android Settings API (Settings 0 = backlight 10).
- Sleep with
screenSaverMinBrightness0(the NSPanel default) switches the display really off: the screen-off timeout is lowered to 1 s, Android turns the display off, and the original timeout is restored right after (also after a crash, on the next start). While the display is off a partial wake lock keeps MQTT and the sensors running, and the watchdog pauses.screenSaverMinBrightness> 0 dims instead. - Wake (touch, proximity with
wakeOnProximity, MQTT/HTTP) uses a short screen wake lock. Touches are read from the touchscreen node (/dev/input/event1is world-readable); a tap on the off display is dropped by Android, so it never reaches the dashboard. - Light and proximity come from the Android sensors (their raw input nodes are not readable).
The proximity sensor reports raw infrared reflectance, not a distance (higher = closer:
~20 at rest, 55–80 with a person standing in front of the panel, 100–6000 with a hand close to
it; Android's
maxRangeof 9 is meaningless). The resting level drifts with ambient light (~20 at night, 30–40 in daylight), so the panel learns it continuously (slowly upwards, quickly downwards, frozen while something is near). Near is a rise of at leastproximityThresholdabove that level (default 20, 5–500; also the Proximity Threshold number entity in HA) and far a drop back below half of it (hysteresis); this drivespresenceandwakeOnProximity. It only reacts to someone directly in front of the panel, not across a room. Its value is published at most every 5 s plus on every near/far change (the sensor itself reports several values per second), without a unit. - Relays: relay 1 and 2 are GPIO 114 and 115 (
/sys/class/gpio/gpio11x/value, writable by apps; the same nodes NSPanel Pro Tools drives). GPIO 113 is never touched. - Reboot sets the
sys.powerctlproperty, which init accepts from the app on this permissive-SELinux userdebug build; the panel is back after about 70 s. - If the touchscreen node is not readable, a 1 px overlay detects touches instead (needs
SYSTEM_ALERT_WINDOW). A setuid-rootsu(e.g. Magisk), if present, is used automatically for the backlight,rebootandrestart_app. Without it,restart_apponly works with the native dashboard as watchdog target (it restarts the:dashboardprocess); other apps cannot be force-stopped.
Warning
The stock firmware ships with ro.adb.secure=0: adb on port 5555 accepts any connection
without an authorization prompt, so everyone on your network can control the panel. Keep it in
a trusted network/VLAN.
scripts/setup.sh handles the NSPanel Pro as well (see Installation): it disables
NSPanel Pro Tools (it also drives the backlight and would fight over it), grants the
permissions, sets screen_off_timeout/screen_brightness_mode so that Android does not dim or
switch off on its own (PanelElevate's idle timer decides; the sleep trick restores the timeout
every time), and starts the service. Manually:
adb connect <nspanel-ip>:5555
adb shell pm disable-user --user 0 com.seaky.nspanelpro.tools
adb shell settings put system screen_off_timeout 2147483647
adb shell settings put system screen_brightness_mode 0
# then the manual installation steps aboveadb logcat -s PanelElevateService ScreenOffTimeout InputEventReader DeviceHelper shows sleep/wake
(Switching screen off via screen-off timeout, Screen-off timeout restored to …) and which input
devices are read.
NSPanel Pro Tools announced its own MQTT device. After switching, delete that device in Home
Assistant (Settings → Devices → MQTT) and update automations to the new entities
(<id> = mqttDeviceId, the entity ids follow the device name Sonoff NSPanel Pro):
| NSPanel Pro Tools | PanelElevate |
|---|---|
| light / lux sensor | sensor.…_light (topic shellyelevateservice/<id>/lux) |
| proximity sensor | sensor.…_proximity (reflectance) + binary_sensor.…_presence (near = ON) |
switch_1 / switch_2 (relays) |
switch.…_relay / switch.…_relay_1 (topics relay_command, relay_command_1) |
wake_up command |
button.…_wake (or POST /device/wake) |
sleep command |
button.…_sleep (or POST /device/sleep) |
| screen on/off state | binary_sensor.…_sleeping |
| brightness | number.…_screen_brightness + switch.…_auto_brightness |
reboot command |
button.…_reboot |
default_app / launcher |
watchdog: watchdogPackage (dashboard = native dashboard) + watchdogEnabled; button.…_restart_app |
adb command |
not offered: enable ADB over network once via adb tcpip 5555 |
There are no temperature, humidity or button entities on the NSPanel. The adc-keys input
device (ESC/HOME/VOLUME) is not wired on the 86 model; its key codes are only published with
debugKeys (topic unknown_key).
Instead of the HA Companion app, PanelElevate can show your dashboard itself: a native
(Kotlin + Jetpack Compose) Lovelace renderer in its own process (:dashboard). It loads the
dashboard config live from Home Assistant and draws the cards natively in the HA default (dark)
theme, so popups and views open instantly even on the slow SoCs of these panels, and a dashboard
change in HA shows up without touching the device.
- Layouts: sections and masonry views, view badges; swipe navigation between views (live pager), back key, automatic return to the first view after inactivity.
- Core cards plus common HACS cards (bubble-card, button-card, auto-entities, Mushroom, template-entity-row, timer-bar, xiaomi-vacuum-map-card, fab-card, …); unknown card types show a placeholder.
- browser_mod emulation: registers as a browser_mod browser, so popups, notifications, navigate and the browser's media player (TTS) from existing automations keep working.
- Instant on-screen feedback for the hardware buttons.
- Guest lock: while an HA entity (default
binary_sensor.lock_dashboards) is on, touches only show a lock screen and the display goes back to sleep after 10 s; automation popups (alarm keypad) stay usable. The dashboard asks the service to sleep through a not-exported broadcast receiver (GuestLockContract.ACTION_SLEEP, same path as MQTTsleep).
Setup, the full list of supported cards and the limitations are in
dashboard/README.md. Use watchdogPackage dashboard to keep it in
front (Native dashboard as watchdog target).
Both panels have Bluetooth LE. With bleProxyEnabled (default false) PanelElevate speaks
the ESPHome native API on TCP port 6053 and Home Assistant's ESPHome integration adds the
panel as a Bluetooth proxy, like an ESP32 running bluetooth_proxy:. It forwards
advertisements (plant sensors, Oral-B, iBeacons, …) and lets integrations open active GATT
connections through it (e.g. La Marzocco, Xiaomi plant sensor battery/history).
- Plaintext API without password (no API encryption key). Device name
panelelevate-<mqttDeviceId>, announced via mDNS (_esphomelib._tcp), so HA offers it under Discovered; adding it manually by IP and port 6053 works too. - Feature flags: passive scan, active connections (3 slots), remote caching, pairing, cache
clearing, raw advertisements, scanner state. Android always scans actively (scan responses
are requested);
BluetoothScannerSetModeis answered with the current (active) mode. - GATT handles are the real ATT handles, so HA's service cache stays valid across connections.
- No entities: the proxy only shows up as a Bluetooth scanner/proxy in HA.
- Wi-Fi impact measured with continuous low-latency scanning: ping to the HA host rose from 2.6 to 2.9 ms on average on the Wall Display XL (9.2 → 10.3 ms on the NSPanel Pro), no extra packet loss.
Setup (no root needed):
# Location permission (BLE scans return nothing without it) and location services on
adb shell pm grant io.github.simllll.panelelevate android.permission.ACCESS_FINE_LOCATION
adb shell pm grant io.github.simllll.panelelevate android.permission.ACCESS_COARSE_LOCATION
adb shell settings put secure location_mode 3
# Bluetooth must be on
adb shell settings get global bluetooth_on # 1
# Enable the proxy (or the "Bluetooth Proxy" switch in HA via MQTT, or bleProxyEnabled via the HTTP API)
adb shell am broadcast -n io.github.simllll.panelelevate/.AdbSettingReceiver \
-a io.github.simllll.panelelevate.SET_SETTING --es key bleProxyEnabled --es value trueThen add the ESPHome device in HA (Settings → Devices & services, discovered
panelelevate-…, or Add integration → ESPHome with the panel's IP, port 6053, no
encryption key). Notes:
- NSPanel Pro (Android 8.1): Android 8.1 pauses unfiltered BLE scans while the display
is off; the proxy passes an empty scan filter and keeps scanning with the display off
(verified: ~400 results in 30 s while asleep). The location permission must be granted as a
runtime permission (
adb shell pm grant io.github.simllll.panelelevate android.permission.ACCESS_FINE_LOCATION), otherwise Android rejects the scan with a SecurityException in logcat. The Bluetooth MAC is not readable for apps there, so the proxy reports only the Wi-Fi MAC (HA then uses it as the scanner's source address). - Wall Display XL (Android 11): scans keep running with the display dimmed.
For door bells that ring an indoor unit only locally (no event in HA), the panel can listen
for the ringtone with its microphone (doorbellDetectEnabled, default false, also the
"Doorbell Detection" switch in HA). On a match it fires the MQTT event entity Doorbell sound
(event_types: [ring], device class doorbell); combine it in HA with your intercom's own
ring events (e.g. "tone heard and no intercom call within ±10 s = flat door").
- Continuous AudioRecord (source MIC, 16 kHz mono) in the service, also with the display off; the microphone is released when the setting is off.
- Goertzel filters on the three notes of the BTicino chime (1760 Hz → 2090 Hz → 1395 Hz, ±3 %)
and reference frequencies between them in 64 ms blocks. A note counts when it is above
doorbellMinLevel(default 100, ≈ a sine of amplitude 200) anddoorbellMinRatio(default 8) times the strongest reference bin; the notes must follow in order, each ≥ 2 blocks, ≤ 0.6 s apart. Cooldown 5 s. Steps, doors and voices (below 1 kHz, not tonal) do not match. - Log tag
DoorbellDetector(levels and ratios of each hit). - Needs
adb shell pm grant io.github.simllll.panelelevate android.permission.RECORD_AUDIO.
Recording samples for tuning (adb only; the detector pauses while a sample is recorded):
adb shell am broadcast -n io.github.simllll.panelelevate/.audio.AudioSampleReceiver \
-a io.github.simllll.panelelevate.RECORD_AUDIO_SAMPLE --ei seconds 60 [--es source mic]
adb pull /sdcard/Android/data/io.github.simllll.panelelevate/files/audio/- PanelElevate with the native dashboard (
watchdogPackagedashboard): fastest on these panels, popups and TTS from browser_mod automations keep working. - On the Shelly, an alternative launcher so that Shelly's own app does not start, e.g.
Ultra Small Launcher
(
adb install ultra-small-launcher.apk, set as default;adb shell input keyevent 3opens it). - If you prefer the web dashboard: the
Home Assistant Companion app (minimal)
with
watchdogPackageio.homeassistant.companion.android.minimal.
All configuration is done via the HTTP API. The service runs on port 8080.
The examples assume an API token is set (see Installation step 5); without a token, drop the
Authorization header (the MQTT broker/user/device id and the watchdog package can then not be changed).
curl -X POST http://<device-ip>:8080/settings \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"mqttEnabled": true,
"mqttBroker": "tcp://your-mqtt-broker",
"mqttPort": 1883,
"mqttUsername": "your-username",
"mqttPassword": "your-password"
}'Keep the Home Assistant app running in the foreground:
curl -X POST http://<device-ip>:8080/settings \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"watchdogEnabled": true,
"watchdogPackage": "io.homeassistant.companion.android.minimal",
"watchdogInterval": 10
}'Set watchdogPackage to dashboard (or the own package name io.github.simllll.panelelevate)
to keep the built-in native dashboard (see dashboard/README.md) in front
instead of the HA app. The watchdog then checks the :dashboard process and starts
DashboardActivity directly; restart_app kills only the dashboard process (never the service)
and relaunches it after 500 ms. Switching between HA app and dashboard works at runtime; the next
check runs immediately after the change.
Without an HTTP API token, the watchdog settings can be changed via adb (the receiver is protected
by android.permission.DUMP like the token receiver; only watchdogPackage, watchdogEnabled
and watchdogInterval are accepted):
# switch to the native dashboard
adb shell am broadcast -n io.github.simllll.panelelevate/.AdbSettingReceiver \
-a io.github.simllll.panelelevate.SET_SETTING --es key watchdogPackage --es value dashboard
# back to the HA Companion app
adb shell am broadcast -n io.github.simllll.panelelevate/.AdbSettingReceiver \
-a io.github.simllll.panelelevate.SET_SETTING --es key watchdogPackage \
--es value io.homeassistant.companion.android.minimal
# enable/disable, interval in seconds
adb shell am broadcast -n io.github.simllll.panelelevate/.AdbSettingReceiver \
-a io.github.simllll.panelelevate.SET_SETTING --es key watchdogEnabled --es value true
adb shell am broadcast -n io.github.simllll.panelelevate/.AdbSettingReceiver \
-a io.github.simllll.panelelevate.SET_SETTING --es key watchdogInterval --es value 10The broadcast result (data="Set watchdogPackage = dashboard" or data="Rejected …") shows
whether the value was accepted.
curl -X POST http://<device-ip>:8080/settings \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"screenSaver": true,
"screenSaverDelay": 45,
"screenSaverMinBrightness": 10,
"wakeOnProximity": true
}'If httpApiToken is set, every endpoint requires Authorization: Bearer <token> (otherwise 401).
POST /settings returns 403 for protected keys when no token is set, and 400 for invalid JSON
or values that do not match the setting's type (e.g. "mqttPort": "abc"; "1883" is accepted and
stored as a number). Nothing is stored if any key is rejected.
| Method | Endpoint | Description |
|---|---|---|
| GET | / |
Device info (name, version, model) |
| GET | /settings |
Get all settings |
| POST | /settings |
Update settings |
| Method | Endpoint | Description |
|---|---|---|
| GET | /device/relay?num=0 |
Get relay state (num 0 = relay 1, 1 = relay 2) |
| POST | /device/relay |
Set relay state {"num": 0, "state": true} |
| GET | /device/getTemperature |
Get temperature |
| GET | /device/getHumidity |
Get humidity |
| GET | /device/getLux |
Get light level |
| GET | /device/getProximity |
Get proximity value (distance in cm; reflectance on the NSPanel Pro) |
| GET/POST | /device/wake |
Wake screen |
| GET/POST | /device/sleep |
Dim screen |
| POST | /device/reboot |
Reboot device |
| POST | /device/launchApp |
Launch app {"package": "..."} |
| Method | Endpoint | Description |
|---|---|---|
| POST | /media/play |
Play audio {"url": "...", "music": true, "volume": 0.5} |
| POST | /media/pause |
Pause music |
| POST | /media/resume |
Resume music |
| POST | /media/stop |
Stop all audio |
| GET/POST | /media/volume |
Get/set volume |
All topics are prefixed with shellyelevateservice/<client-id>/ (kept from ShellyElevate so existing
Home Assistant entities and automations keep working after the rename to PanelElevate).
| Topic | Description |
|---|---|
status |
online (retained) while connected, offline as MQTT will (LWT) and on service stop. Used as availability topic for all entities. |
| Topic | Description |
|---|---|
temp |
Temperature in °C (every 30 s) |
hum |
Humidity in % (every 30 s) |
lux |
Light level in lux |
proximity |
Proximity distance in cm (NSPanel Pro: reflectance, at most every 5 s) |
presence |
Presence sensor (ON = near / OFF = far): XL wide-range sensor, NSPanel Pro proximity |
bri |
Screen brightness (0-255) |
auto_bri |
Automatic brightness enabled (ON/OFF) |
sleeping |
Screen dimmed state (ON/OFF) |
relay_state, relay_state_1 |
Relay 1 / relay 2 state (ON/OFF) |
| Topic | Payload | Description |
|---|---|---|
button_event/1 |
{"event_type": "single"} |
Button 1 single press |
button_event/1 |
{"event_type": "double"} |
Button 1 double press |
button_event/1 |
{"event_type": "long"} |
Button 1 long press (held 500ms+) |
button_event/2 |
{"event_type": "..."} |
Button 2 events |
button_event/3 |
{"event_type": "..."} |
Button 3 events |
button_event/4 |
{"event_type": "..."} |
Button 4 events |
Event types:
single- Quick press and releasedouble- Two presses within 300mslong- Button held for 500ms or more
| Topic | Payload | Description |
|---|---|---|
relay_command, relay_command_1 |
ON / OFF |
Control relay 1 / relay 2 |
bri_set |
0-255 |
Set screen brightness (turns automatic brightness off) |
auto_bri_set |
ON / OFF |
Turn automatic brightness on/off |
proximity_threshold_set |
25-500 |
Near threshold of the NSPanel Pro proximity sensor (state topic proximity_threshold) |
sleep |
any | Dim screen |
wake |
any | Wake screen |
reboot |
any | Reboot device |
restart_app |
any | Force kill and restart the watchdog app (native dashboard: only its process) |
update |
any | Re-publish discovery and all states |
Retained messages on command topics are ignored (a retained reboot can never cause a reboot loop).
The service also listens to homeassistant/status and re-publishes discovery and all states when
Home Assistant comes online.
PanelElevate publishes MQTT discovery config to homeassistant/device/<client-id>/config. After connecting to MQTT, the device will automatically appear in Home Assistant with:
- Temperature sensor (if available)
- Humidity sensor (if available)
- Light sensor
- Proximity sensor (if available)
- Presence binary sensor (occupancy, if available)
- Screen Brightness number (slider 0-255)
- Auto Brightness switch
- Proximity Threshold number (NSPanel Pro)
- Sleeping binary sensor
- Relay switch(es) (if available)
- Button events (for automations)
- Sleep/Wake/Reboot/Restart App buttons
All entities use shellyelevateservice/<client-id>/status as availability topic, so they become
unavailable when the display is offline. Stopping the service only marks the device offline; it is
never removed from Home Assistant automatically (delete it in HA if you no longer need it).
Setting the brightness from Home Assistant switches automatic brightness off (so the value sticks); turn the Auto Brightness switch back on to return to light-sensor control. With automatic brightness on, the brightness is only changed when the computed value moves by at least 3 steps.
automation:
- alias: "Button 1 Single Press - Toggle Kitchen Light"
trigger:
- platform: state
entity_id: event.shelly_wall_display_button_1
attribute: event_type
to: "single"
action:
- service: light.toggle
target:
entity_id: light.kitchenautomation:
- alias: "Button 1 Double Press - Movie Mode"
trigger:
- platform: state
entity_id: event.shelly_wall_display_button_1
attribute: event_type
to: "double"
action:
- service: scene.turn_on
target:
entity_id: scene.movie_modeautomation:
- alias: "Button 1 Long Press - All Lights Off"
trigger:
- platform: state
entity_id: event.shelly_wall_display_button_1
attribute: event_type
to: "long"
action:
- service: light.turn_off
target:
entity_id: allautomation:
- alias: "Wall Display Button 1 - Multi-Action"
trigger:
- platform: state
entity_id: event.shelly_wall_display_button_1
action:
- choose:
- conditions:
- condition: template
value_template: "{{ trigger.to_state.attributes.event_type == 'single' }}"
sequence:
- service: light.toggle
target:
entity_id: light.kitchen
- conditions:
- condition: template
value_template: "{{ trigger.to_state.attributes.event_type == 'double' }}"
sequence:
- service: light.turn_on
target:
entity_id: light.kitchen
data:
brightness_pct: 100
- conditions:
- condition: template
value_template: "{{ trigger.to_state.attributes.event_type == 'long' }}"
sequence:
- service: light.turn_off
target:
entity_id: light.kitchenAll settings can be configured via POST /settings with a JSON body.
| Key | Type | Default | Description |
|---|---|---|---|
mqttEnabled |
boolean | false |
Enable MQTT connection |
mqttBroker |
string | "" |
MQTT broker URL (e.g., tcp://192.168.1.100) |
mqttPort |
int | 1883 |
MQTT broker port |
mqttUsername |
string | "" |
MQTT username |
mqttPassword |
string | "" |
MQTT password |
mqttDeviceId |
string | auto-generated | MQTT client ID (used in topics) |
| Key | Type | Default | Description |
|---|---|---|---|
automaticBrightness |
boolean | true |
Auto-adjust brightness based on ambient light |
brightness |
int | 255 |
Manual brightness level (0-255, used when auto is off) |
minBrightness |
int | 48 |
Minimum brightness for auto-brightness |
| Key | Type | Default | Description |
|---|---|---|---|
screenSaver |
boolean | true |
Enable screen dimming on idle |
screenSaverDelay |
int | 45 |
Seconds of inactivity before dimming |
screenSaverMinBrightness |
int | 10 (NSPanel Pro: 0) |
Brightness level when dimmed (0-255; on the NSPanel Pro 0 switches the display off) |
wakeOnProximity |
boolean | false |
Wake screen when the proximity/presence sensor reports "near" |
| Key | Type | Default | Description |
|---|---|---|---|
watchdogEnabled |
boolean | false |
Enable app watchdog |
watchdogPackage |
string | io.homeassistant.companion.android.minimal |
Package name of app to keep running (dashboard = native dashboard) |
watchdogInterval |
int | 10 |
Check interval in seconds (changes apply from the next check) |
| Key | Type | Default | Description |
|---|---|---|---|
httpServer |
boolean | true |
Enable HTTP API server on port 8080 |
httpApiToken |
string | "" |
Bearer token for the HTTP API (set via adb, see Installation step 5; masked in GET /settings) |
mediaEnabled |
boolean | true |
Enable media/audio playback |
debugKeys |
boolean | false |
Publish unknown key codes to MQTT for debugging |
bleProxyEnabled |
boolean | false |
ESPHome Bluetooth proxy on port 6053 (see ESPHome Bluetooth proxy; also settable via adb and the MQTT switch) |
doorbellDetectEnabled |
boolean | false |
Acoustic doorbell detection via the microphone (see Acoustic doorbell detection; also settable via adb and the MQTT switch) |
doorbellMinRatio |
int | 8 |
Doorbell detector: minimum ratio of a note to the strongest reference frequency |
doorbellMinLevel |
int | 100 |
Doorbell detector: minimum note level (Goertzel magnitude) |
deviceModel |
string | not set (detect) | Force a device profile, e.g. NSPANEL_PRO or BLAKE (see Supported devices; protected, applies after a service restart) |
# Debug build
./gradlew assembleDebug
# Release build
./gradlew assembleRelease
# Install on connected device
./gradlew installDebugIf you'd like to contribute or have a feature request, please create a pull request or open an issue.
Originally based on ShellyElevate by RapierXbox.









