[BETA] HD+ Hubitat Dashboard for iOS, Mac, Windows & Linux

1.0.760 MacOS

The new version seems to have introduced a small issue for me. My copies of Hub Info Driver device tile that show Sunrise and Sunset attributes via 'manage display items' now have % after the time:

Screenshot 2026-05-06 at 08.15.01

I'm on 1.0.760 Android . Can't make grid size higher than 7. The + button is grayed out

Fixed in the next version. I also did a little more work to folders / multi-sensor device tiles to make them work closer to the original Android HD+.. things like stacking 2 items vertically if they're both text (otherwise they'd go side-by-side)
image image

I’ve noticed that with a couple of my drivers - I get ‘nothing to show’ until I set the tile to custom and select the ‘html’ attribute- then the text is displayed normally.

This should also be fixed in the next version. I haven't done much testing with HTML tiles so if you have some other examples I can test with let me know. Also, 1 thing to keep in mind is HTML support on the desktop isn't as good as the mobile (iOS/Android) versions.. they both have nice WebView widgets but the desktop version doesn't

2 Likes

I also tried to fix this in the next version - I believe I handle it like the original Android HD+ which shows the first custom attribute (It'd probably be too small to try and show more than 1)

I think with the drivers I'm referring to (the HTML Clock) as an example, are just outputting a plain text string as a html attribute. There's no formatting, it gets sent as plain text so that it will match the dashboard settings. So it's likely my misunderstanding of how it works rather than an anything in your app. It's just a couple of clicks to set type to custom and select the http attribute to display. This driver I did with ChatGPT is an example of the same. I use this in a wide tile and send/clear messages to its queue with Rule Machine or text from an iOS shortcut on my phone.

Notification Tile Sequencer
/*
 * Notification Tile Sequencer - v1.11
 * Split from 'Scrolling Event Notifier' for sequencing events only
 */

static String version() { return '1.11' }

metadata {
    definition (
        name: "Notification Tile Sequencer",
        namespace: "John Williamson",
        author: "John Williamson with ChatGPT",
        singleThreaded: true
    ) {
        capability "Actuator"
        capability "Notification"
        attribute "html", "string"
        attribute "version", "string"

        command "deviceNotification", [[
            name:"msg",
            type:"STRING",
            description:"'MsgID, Message' or 'p, MsgID, Message' (for priority)"
        ]]
        command "addMessage", [
            [name:"msgID*", type:"STRING"],
            [name:"msgContent*", type:"STRING"]
        ]
        command "remMessage", [
            [name:"msgID*", type:"STRING"]
        ]
        command "clear", [[
            name:"info",
            type:"STRING",
            description:"No input needed, click 'Run' to clear all messages"
        ]]
    }
}

preferences {
    input("debugEnabled", "bool", title: "Enable debug logging?")
    input("displayTime", "number", title: "Display Time (seconds)", defaultValue:5)
    input("blankTime", "enum", title: "Blank Time (seconds)",
          options:["none","1","2","3"],
          defaultValue:"none",
          description: "Tile will be empty for this many seconds between messages; 'none' disables")
    input("noMessageText", "text", title: "No Message Display",
          description: "Text to show when no message present, leave blank for empty tile")
    input("priorityIndicator", "enum", title: "Priority Message Indicator",
          options:["⚠️","🔔","✋","‼️","⛔️","🚨","💩"],
          defaultValue:"⚠️")
}

/* ---------------- Core ---------------- */

def installed() {
    state.messages = [:]
    state.priority = [:]
    state.standardIndex = 0
    showEmpty()
    sendEvent(name:"version", value:version())
}

def updated() {
    unschedule()
    sendEvent(name:"version", value:version())
}

/* ---------------- Notification Parser ---------------- */

void deviceNotification(String msg) {
    if(!msg) return
    msg = msg.trim()

    if(msg.equalsIgnoreCase("clear")) {
        clear()
        return
    }

    if(!msg.contains(",")) {
        def id = "auto_${now()}"
        state.messages[id] = msg
        state.priority[id] = false
        displayTicker()
        return
    }

    if(msg.contains(",")) {
        def parts = msg.split(",",3)
        def first = parts[0]?.trim()

        if(first.equalsIgnoreCase("p") && parts.size()==3) {
            def id = parts[1]?.trim()
            def content = parts[2]?.trim()
            def indicator = priorityIndicator ?: "⚠️"
            state.messages[id] = "${indicator}${content}${indicator}"
            state.priority[id]=true
            displayTicker()
            return
        }

        if(first.equalsIgnoreCase("rem") && parts.size()>=2) {
            remMessage(parts[1]?.trim())
            return
        }

        if(parts.size()>=2) {
            def id = parts[0]?.trim()
            def content = parts[1]?.trim()
            state.messages[id]=content
            state.priority[id]=false
            displayTicker()
            return
        }
    }
}

/* ---------------- Manual Commands ---------------- */

void addMessage(id, content){
    state.messages[id]=content
    state.priority[id]=false
    displayTicker()
}

void remMessage(id){
    state.messages.remove(id)
    state.priority.remove(id)
    if(state.messages.size()==0) showEmpty()
    else displayTicker()
}

void clear(){
    state.messages=[:]
    state.priority=[:]
    showEmpty()
}

/* ---------------- Display Logic ---------------- */

private void showEmpty(){
    def html = noMessageText ? noMessageText : " "
    sendEvent(name:"html", value:html)
}

void displayTicker(){
    if(state.messages.size()==0){
        showEmpty()
        return
    }

    if(state.messages.size()==1){
        def key = state.messages.keySet().first()
        def msg = state.messages[key]
        sendEvent(name:"html", value:msg)
        return
    }

    state.standardIndex = state.standardIndex ?: 0
    cycleStandardMessage()
}

/* ---------------- Standard Cycling ---------------- */

void cycleStandardMessage(){
    if(state.messages.size()<2){
        displayTicker()
        return
    }

    if(state.showingBlank == null) state.showingBlank = false

    def keys = state.messages.keySet().toList()
    if(state.standardIndex==null || state.standardIndex >= keys.size()) state.standardIndex = 0

    if(state.showingBlank && blankTime != "none"){
        sendEvent(name:"html", value:" ")
        state.showingBlank = false
        runIn(blankTime.toInteger(), "cycleStandardMessage")
    } else {
        String key = keys[state.standardIndex]
        String msg = state.messages[key]
        sendEvent(name:"html", value:msg)
        state.showingBlank = (blankTime != "none")

        state.standardIndex++
        if(state.standardIndex >= keys.size()) state.standardIndex = 0

        runIn(displayTime ?: 3, "cycleStandardMessage")
    }
}

Just bumping this. When rotating the screen in iOS the grid size resets to default so needs to be altered back:
screenrotationresetsgrid-ezgif.com-optimize

That's probably a better way to display plain text. Maybe that's something I can just automatically handle if I see it (plain text in an 'html' field) by showing it in the custom device type

FWIW - html tiles are normally fairly heavy-weight components as they have to handle all kinds of various HTML.. basically mini web browsers. Any other device type like Custom/Text/etc are all native components -- so much faster.

HTML tiles on the desktop are particularly painful -- it's much better on mobile devices. But, still generally speaking I try to avoid any HTML tiles if at all possible.. that's why I've tried to create some custom device types like that pollen counter.. stuff that could be done in HTML but I like to have a native option when possible

1 Like

I’m used to displaying that attribute like that, so not much point in complicating things for what’s effectively an edge case :+1:t2:

The android version with the updated video drivers has fixed my video issue. Thank you

Looking forward to you bringing the remaining functionality to this.

I found a workaround for this to get me by. In Apple shortcuts I added two automations:

  • When HD+ is opened orientation lock on
  • When HD+ is closed orientation lock off

As soon as the app is swiped off screen (even if its running in the background) rotation works again.

iOS 1.0.759

@jpage4500 - Hi Joe, a couple of things I've noticed in the latest iOS version.

Hide Tile bug. In full edit mode, 'Hide Tile' works but the tile doesn't 'vanish' until you click 'Done' so it appears not to have worked even though it has (may be expected behaviour). In tile edit mode (long press on tile > edit) 'Hide Tile' doesn't seem to work at all.
Edit: The tile seems to hide after a delay or screen drag refresh

Tile Actions. On a shade tile I can set the tap action to 'do nothing' and it works. I have some generic sensor tiles and when I set those to 'do nothing', tapping the tile still brings up a Details, Activity, Edit dialogue box.

Request. Any chance of allowing tile width to be increased? I've set my display to 4 tiles wide (which is a good size on a Pro Max) but the tile width tops out at 3. I've 1 or two tiles that I'd like to go the full width of my portrait screen.

I have this on my list to look at.. it could just be something simple but more likely it's because I have logic that prevents tiles from getting too small.

1 Like

version 1.0.800 (desktop / android)

  • android widget support (device and image/map/calendar widgets)
  • handle plain HTML text
  • add change device type location
  • change folder layout
  • fix folder text wrapping
  • fix sunrise/sunset display
  • fix showing custom tile on folder
  • fix network activity while app isn't in foreground
  • android: back key clears search/filter
  • WIP on geofence for android
  • fix hide tile not hiding right away

Most of the changes are Android only - both still a work in progress but good enough at least to consider beta.

Widget support:

  • 1x1 device widget
    • shows an icon and optional label
    • device state tracks Hubitat device (on/off)
    • clicking device can toggle device state, prompt (on/off) or view device in a small popup window
    • calendar device shows
  • image / location widget
    • auto-updating image
    • shows a map for location devices (ie: Life360)



Geofence support:
Still very much a work in progress.. I'll be testing in the next next few weeks so it'll improve over time.

Thanks for the update. Grid sizing still broken

Looking at one this now -- also making some changes to simplify the grid size as well.. should have something to try shortly

1 Like

version 1.0.812 (desktop / android)

  • android: support HTML widget
  • show some widgets fullscreen
  • refresh device state when updating widget
  • fix click action dialog options (read only, PIN)
  • desktop: use grid size for all platforms
  • always use grid size regardless of screen size
  • add option to auto size grid

I started working on allowing HTML tiles to be widgets.. I'll document it more once I've had time to really test it out.

The changes that'll affect all platforms are around click action and grid size.

Click action should offer more appropriate choices and behave more as expected. For example, you can set a PIN on a folder. Clicking the folder prompts for the PIN and shows it when entered. PIN also works now for HSM and Mode tiles. When you set a PIN on a device that can be toggled on/off (ie: light, lock, etc) - after entering the PIN it'll send the command.

There's still some edge cases.. PIN support doesn't work on buttons or any tile that has an actionable control on the main dashboard.

Grid Size has 2 options now - an 'auto size' mode that will try to default to a decent tile size depending on screen size. If you want to go between both portrait and landscape modes on your phone - this setting (which is enabled by default) is what you want.

If you disable this, you can set a fixed number of tiles and it'll use this setting regardless of screen size.

You can also set each tile size to much larger values now as well and they should work as expected.

2 Likes

Is "Move selected into folder" working yet?

fixed in next version

1 Like

Great update.

HSM tile not working correctly. It won't activate "Armed Home". If i use one of my other android tablets running an older version of HD+, when I activate HSM to Armed Home, this latest version shows Armed Away.

I'm confused. Does the Windows HD+ version have a Maker API on my Hubitat hub? How do I select the devices to use on the Windows version?