[APP][Pro] NEW: Dashboard Studio - A completely free-form dashboard designer

I have tried the new image refresh interval a couple of times. But each time it made my Synology nas unresponsive for about 5-10 minutes. Will try to do some further testing this evening.
My setup is a camera in the Synology Surveillance Station added as device in Homey.
As soon as I copy the url from Homey Developer Tools as an image to Dashboard Studio and enable the refresh my Synology nas is unreachable within 10 seconds. (so not the Homey app or device, but the nas itself)

Execute the following Homey script after change of artist or track and save the song text in a Homey flow variable:

// 1. Configuratie & Hardware
const SONOS_ID = 'ce48fa04-xxxx-xxxx-xxxxxxxxxxxxxxxxx';
const LRCLIB_ENDPOINT = 'https://lrclib.net/api/get';
const DEFAULT_TEXT = "Geen songtext gevonden"; // Centraal ingesteld

// 2. Data ophalen
const sonos = await Homey.devices.getDevice({ id: SONOS_ID });
const { 
    speaker_artist, 
    speaker_track, 
    speaker_playing, 
    speaker_duration 
} = sonos.capabilitiesObj;

const artist = speaker_artist?.value;
const track = speaker_track?.value;
const isPlaying = speaker_playing?.value;
const duration = speaker_duration?.value;

// 3. Validatie
if (!artist || !track || !isPlaying) return false;

// 4. Voorbereiding resultaat
let resultaat = DEFAULT_TEXT;

// 5. API Request
const query = new URLSearchParams({
    artist_name: artist,
    track_name: track,
    duration: duration
});

try {
    const response = await fetch(LRCLIB_ENDPOINT + '?' + query.toString());

    if (response.ok) {
        const data = await response.json();
        const syncedLyrics = data.syncedLyrics;

        // 6. Verwerking van Lyrics (indien aanwezig)
        if (syncedLyrics && syncedLyrics.trim() !== "") {
            const verwerkteTekst = syncedLyrics
                .split('\n')
                .filter(line => line.trim() !== "")
                .map(line => {
                    const timestamp = line.substring(1, 6); // mm:ss
                    let text = line.substring(10).trim(); 
                    
                    if (!text) {
                        text = "[instrumentaal]";
                    }
                    
                    return timestamp + " - " + text;
                })
                .join('\n');
            
            if (verwerkteTekst.length > 0) {
                resultaat = verwerkteTekst;
            }
        }
    }
} catch (error) {
    console.log("Fout bij ophalen lyrics: " + error.message);
}

// 7. Opslaan in Homey Logic variabelen
const allVars = await Homey.logic.getVariables();
const myVar = Object.values(allVars).find(v => v.name === 'DSSongtext');
const myVarNL = Object.values(allVars).find(v => v.name === 'DSSongtextNL');

// Altijd DSSongtextNL leegmaken bij een nieuwe actie
if (myVarNL) {
    await Homey.logic.updateVariable({ id: myVarNL.id, variable: { value: "" } });
}

if (myVar) {
    await Homey.logic.updateVariable({ id: myVar.id, variable: { value: resultaat } });
    console.log("Status opgeslagen in DSSongtext. DSSongtextNL is leeggemaakt.");
    return true;
} else {
    console.log("Variabele 'DSSongtext' niet gevonden in Homey Logic.");
    return false;
}

Match the song text with the actual play time of the current song. There is even a song text translation option in the code, which needs a separate script (not included here).

// 1. Configuratie & Performance Caching
const SONOS_ID = 'ce48fa04-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
const CORRECTIE_ID = '88ba3497-xxxx-xxxx-xxxx-xxxxxxxxxxxx'';
const OFFSET_MS = 1000;
const MELDING = "Geen songtext gevonden";

global.myDevices = global.myDevices || {};
if (!global.myDevices.sonos) {
    global.myDevices.sonos = await Homey.devices.getDevice({ id: SONOS_ID });
}
if (!global.myDevices.correction) {
    global.myDevices.correction = await Homey.devices.getDevice({ id: CORRECTIE_ID });
}

const sonos = global.myDevices.sonos;
const correctionDevice = global.myDevices.correction;

// 2. Haal de Sonos data op
let posObj = sonos.capabilitiesObj.speaker_position || { value: 0, lastUpdated: new Date().toISOString() };
const durObj = sonos.capabilitiesObj.speaker_duration;

let manualCorrectionSeconds = 0;
if (correctionDevice?.capabilitiesObj.target_temperature) {
    manualCorrectionSeconds = correctionDevice.capabilitiesObj.target_temperature.value || 0;
}

// 3. De EXACTE berekening
const lastUpdatedUnix = new Date(posObj.lastUpdated).getTime(); 
const driftSeconds = (Date.now() - lastUpdatedUnix) / 1000;
const exactTimeSeconds = posObj.value + driftSeconds + (OFFSET_MS / 1000) + manualCorrectionSeconds;

// 4. Helper functie
const formatTime = (totalSeconds) => {
    if (isNaN(totalSeconds) || totalSeconds < 0) return "0:00";
    const minutes = Math.floor(totalSeconds / 60);
    const seconds = Math.floor(totalSeconds % 60);
    return minutes + ":" + seconds.toString().padStart(2, '0');
};

// 5. Haal songtekst variabelen op
const allVars = await Homey.logic.getVariables();
const vertaalNaarNL = Object.values(allVars).find(v => v.name === "DSVertaalNaarNL")?.value || false;

const targetVarName = vertaalNaarNL ? "DSSongtextNL" : "DSSongtext";
const songtextVar = Object.values(allVars).find(v => v.name === targetVarName);

if (!songtextVar || !songtextVar.value) {
    return JSON.stringify({ error: "Variabele '" + targetVarName + "' is leeg" });
}

// 6. Bepaal de tekstinhoud (Efficiƫnte toewijzing)
let lyrics = { vorige_regel: "", huidige_regel: "[instrumentaal]", volgende_regel: "" };

if (songtextVar.value.trim() === MELDING) {
    // Specifieke afhandeling voor "Geen songtext gevonden"
    lyrics = { vorige_regel: "", huidige_regel: MELDING, volgende_regel: "" };
} else {
    // Verwerk tekst naar array en zoek huidige index
    const lines = songtextVar.value.split('\n')
        .filter(l => l.includes(' - '))
        .map(line => {
            const [timeStr, ...textParts] = line.split(' - ');
            const [min, sec] = timeStr.split(':').map(Number);
            return { totalSeconds: (min * 60) + sec, text: textParts.join(' - ') };
        });

    let currentIndex = -1;
    for (let i = 0; i < lines.length; i++) {
        if (exactTimeSeconds >= lines[i].totalSeconds) currentIndex = i;
        else break;
    }

    if (currentIndex !== -1) {
        lyrics = {
            vorige_regel: lines[currentIndex - 1]?.text || "",
            huidige_regel: lines[currentIndex].text,
            volgende_regel: lines[currentIndex + 1]?.text || ""
        };
    } else if (lines.length > 0) {
        lyrics.volgende_regel = lines[0].text;
    }
}

// 7. EƩn centrale return voor alle data
return JSON.stringify({
    ...lyrics,
    actuele_speeltijd: formatTime(exactTimeSeconds),
    actuele_speeltijd_sec: Math.round(exactTimeSeconds),
    totale_duur: formatTime(durObj?.value || 0)
});

The Homey script ā€œcardā€ is the script above.

1 Like

Menu Icons are fixed in test-version 1.5.3 :+1: (already pushed in the appstore)

Thank you for the quick response! The icons are visible now in 1.5.3.
Some time needed to understand and evaluate all new menu options and combinations.

Yes… lots of possibilities…. But I’m sure you can come up with even more feature request for it :squinting_face_with_tongue:

I don’t think this is a Dashboard Studio bug. It seems like the Synology might not be able to handle the data stream. I’ve limited the image polling rate to 5 images per second (0.2), but that might be too high. I personally have mine set to 2 times a second (0.5), though I’m still using the Homey method for sending images. I also pull the images directly from the IP camera rather than routing them through a NAS.

What refresh rate are you using, Robin? Have you tried lowering it to 1 image (or lower) per second to see if that helps?

tried it with 10 seconds. But that was already too much. While with my previous advanced flow it was working fine. But I will first do some further investigation on my side.

Hi @Satoer Thanks for fixing the grouped stuff while migrating. That stuff seems to migrate OK now.
There seems to be another small glitch though: In one of the (grouped) switch widgets, I use a custom image. After the migration to 1.5.2 the (custom) icon is much smaller than before, and I can’t seem to resize it anymore. It does no longer respond to the icon size setting.
The below indicated icons used to be twice as high.

Could it be that there is something wrong with the Menu widget? I can add one, but when I try to set menu icons, they don’t show up. Next, I copy/pasted the menu widget from your example (snark menu). This one does have visible menu option icons. However, when I try to edit them in my own design to another icon, the icon doesn’t change?

Another thing with the button widget now is that if I use the topic for the custom image url it is not working anymore. If I use the same topic with the image widget it is working fine

Please use version 1.5.3 in which the icon issues with the new Menu options have been solved.

I reproduced the issue.

Create an image widget. Add the url. Enable refresh (tried 1 up to 10 sec).
This works fine.
Now resize the image widget. This immediately makes my synology unresponsive.
The nas is not rebooting, but is just not reachable for about 10 minutes.

Thanks man!
I will give it a try

I really don’t think this is a migration issue, it’s more about how the new feature is behaving. :sweat_smile: I added some internal padding for the images so you can now align the text and icons to the left or right inside the button, rather than just keeping everything centered. The problem was that the text and icons were touching the button borders, which looked pretty terrible. To fix that, I added an internal spacing setting called ā€œContent inset,ā€ though I might need to rename it to ā€œinside paddingā€ or something similar. It defaults to 10px, but if you decrease that value, the icon has room to grow larger again.

Yes, there was a problem with the menu icons. Already fixed in 1.5.3 (I think you are on 1.5.2).

1 Like

Oh snap, nice debugging, you are right. I can recreate the problem. It indeed is hammering the request when resizing. I shall fix this :+1:

1 Like

I shall look into this :+1:

1 Like

Thanks! Works like a charm.
In case other people are also interested. I had to make a couple of small changes:
I removed speaker_playing from the first script and the CORRECTIE_ID and DSSongtextNL stuff.
In the screenshot the jsonhandler had all values in [Eigenschap Pad Nummer 1]. I have split them over Nummer 1 to 6.

But with these minor changes it really works great!

2 Likes

New TEST release V1.6.0

Updates:
New Image Gallery browser:

Note: This is not a Widget. This is an improved image browser to easier image uploading / deleting / browsing inside the editor.

I had already for a while in the making, but needed to fix a couple of quirks first before I could release it :grinning_face:

Fixed! Resizing the image widget was constantly resetting the refresh timer, which caused it to keep requesting new images over and over. :sweat_smile:

Fixed :+1:

@Paul_van_der_Heijden Thanks! :clinking_beer_mugs:

2 Likes

Thanks!
Can confirm that the button widget topic fix and resize of the image widget are working now.

Yes. The fall back (old) dashboards show that message. They don’t exist as far as I know but for some reason an old version is shown. I can’t even find that old version when searching within the editor.

the things that broke were several, like no vertical center text in buttons anymore (still can’t fix that), broken images and suddenly a black background for containers. Don’t ask me why. :sweat_smile:

The url in capitals still won’t work Chrome accepts it but immediately makes it without capitals and loads an old dashboard.

I am using a Homey Pro 23, most recent firmware, newest Chrome browser and a MacBook.

I have no idea… Could it be the browser was trying to load a cached old version if you use the url method? Can you press CTRL+SHIFT+DEL in the browser, and then delete cashed images and files?
Might fix the problem