NSPanel Lovelace UI Helpers (part 1/5, v0.9)

ok, another question and i’m really sorry, maybe i miss it reading.
When openhab restarts for any reason, nspanels pages needs to be reloaded, and first time you ask for a page takes little time. i have seen the helper “Reload page” , how can i use it to reload all pages ?
I mean, i need to make a new script, or in the rule that we have nspanel callback have to add this. can you give an example with a picture, updating 2 pages, if you have time?

thank you in advanced.

Hej,

is there any example for the Entity Light. I do not understand how to use it.

thanks for help

Hitting a bit of a snag. I get a repeated error
2024-09-09 14:42:00.939 [WARN ] [e.automation.internal.RuleEngineImpl] - Failed to execute rule 'CBNSPanelUI': Invalid Rule UID
I don’t have a rule of that name nor do any of my rules call it. I presume it’s a Callback rule.
Any ideas?

sorry, I was offline soooo long…

Hi teo406,

Reload page is probably not what you want, because this will show the current page on you display over and over again. To re-compile all the scripts just let them run with the function found in Blockly → OpenHAB → Run&Process

grafik
With the missing context information about the NSPanel all the cards won’t work, therefore you might not have any display issues with them. But before figuring this out they need to be compiled. Do this for all your scripts and you should be fine.

btw, I recently changed my installation from some rapsberry-like board to some ‘real’ intel NUC computer, now openHAB is compiling on the fly without issues. Power usage is the same, but the machine has multiple dockers which in the end save multiple raspberry boards. Maybe some idea…

Best regards, Rene

Hi Gerd,

the only documentation available is at github. But if you add something like the following in a script (and configure the NXPanel_1_Lovelace_UI pointing to your Lovelace-UI Item) you should see some light configuration panel.

Does this work for you? Do you have any problems with this?

Best regards, Rene

Hi pmknwoles,

… but something is looking for this rule/script without finding it.

I don’t know any frontend-based approach to figure out which script is doing that. But if you can login into your openHAB server, go to your userdata/jsondb folder and search for you script:

root@homebase:~# docker exec -it openhab bash
root@2fc51413eec3:/openhab# cd userdata/jsondb/
root@2fc51413eec3:/openhab/userdata/jsondb# grep -rl nspanel_cardGrid *
automation_rules.json
...
root@2fc51413eec3:/openhab/userdata/jsondb#

Have a look into automation_rules.json and you will find the script/rule which is calling “CBNSPanelUI” in your case (search for the string in this file and scroll upwards, you might understand the schema and find some name or id of the script calling this (here its rule with id ‘17b0230a2d’).

  "17b0230a2d": {
    "class": "org.openhab.core.automation.dto.RuleDTO",

But be aware that this json is the base of your configuration, best don’t change it, only have a look and get some idea in which script your problem occurs (Maybe copy it to another machine first). Finally change this script via your common OpenHAB user interface.

Hope this was helpful, best regards,
Rene

Hi

I have run into an issue with one of my panels

Background
I had one page where the panel landed after touching the screen saver and two other pages one before (page 2) and one after the landing page Page 3.
I then created an additional page (page 4) to control a thermostat and set it in between the landing page and page 3. Page 3 can go back to page 4 but not forward.

Problem
The Issue is that the panel does not go back to the screen saver. I see this in the logs

17:34:38.861 [INFO ] [ab.automation.script.ui.kitchen_blind] - starting script 'absorb_it_nspanel_showCardEntities' {timerName=absorb_it_nspanel_refreshTimer_nspanel_k_callback, ruleUID=kitchen_blind, previousPage=kitchen_blind, target=NSPanel_K_Lovelace_UI}
17:34:38.861 [DEBUG] [ab.automation.script.ui.kitchen_blind] - remove existing refresh timer absorb_it_nspanel_refreshTimer_nspanel_k_callback
17:34:38.863 [DEBUG] [ab.automation.script.ui.kitchen_blind] - create refresh timer absorb_it_nspanel_refreshTimer_nspanel_k_callback
17:34:38.863 [INFO ] [openhab.event.ItemCommandEvent       ] - Item 'NSPanel_K_Lovelace_UI' received command entityUpd~Blinds~X~kitchen_entities~~65504~~~~~~~~~shutter~kitchen_blind?0~~29~Kitchen~~shutter~kitchen_blind?1~~29~FR Center~~shutter~kitchen_blind?2~~29~FR Side~~~~~~~

It keeps doing that about every 30 seconds which is the refresh time for my pages (entities)

Any ideas where to start looking?

Thank you

Hi Carlos,

you are looking only at the refresh of that page, that is right. The screensaver is triggered by the panel itself, only handled by openHAB when it receives the information, that the screensaver is now shown.

The problem lies most likely in your configuration of the callback for that panel. Reconfigure and restart that callback, maybe restart your panel and this should working.

Does this solved your problems? Best regards,
Rene

Hi:

I can return to the screen saver by running the callback rule from inside Openhab.

But the problem is the Card Thermo, if I remove it everything works fine again.

Maybe I just made it too complex? I don’t know how to copy and paste the blockly graphics in one step.

Carlos




This is the code

var returnValue;

function absorb_it_nspanel_cards_startupCheck(functionName) {
  console.log("starting script '" + functionName + "' " + ctx);
  if (!ctx.target) {
    if (!ctx.triggerLoadPage) {
      console.error("missing context for loading Card '" + functionName + "' in '" + ctx.ruleUID + "', try 'NSPanel Item' or 'NSPanel Item via CallBack' from Helpers library!");
    }
    else if (!ctx.triggerLoadPageDone) {
      ctx.triggerLoadPageDone = 1;
      items.getItem(ctx.triggerLoadPage).postUpdate('loadPage?' + ctx.ruleUID);
    }
    return 0;
  }
  return 1;
}

function absorb_it_nspanel_cards_killRefreshTimer() {
  if (ctx.timerName && cache.shared.exists(ctx.timerName)) {
    console.debug("remove existing refresh timer " + ctx.timerName);
    cache.shared.remove(ctx.timerName).cancel();
  }
}

function absorb_it_nspanel_cards_checkResult(result) {
    if (result && result.sendNow && result.resultString) {
      items.getItem(ctx.target).sendCommand(result.resultString);
    }
    if (result && result.refreshPage) {
      items.getItem(ctx.target).postUpdate('refresh');
    }
    return (result && (result.sendNow || result.refreshPage));
}

function absorb_it_nspanel_cards_startRefreshTimer(refresh) {
  if (ctx.timerName) {
    if (cache.shared.exists(ctx.timerName)) {
      absorb_it_nspanel_cards_killRefreshTimer(); // just to be 100% shure
    }
    refresh = parseInt(refresh);
    if (refresh != 0) {
      console.debug("create refresh timer " + ctx.timerName);
      cache.shared.put(ctx.timerName,
        actions.ScriptExecution.createTimer(
          ctx.timerName,
          time.ZonedDateTime.now().plusSeconds(refresh),
          function () {
            items.getItem(ctx.target).postUpdate('refreshTimer');
          }
        )
      );
    }
  }
}

function absorb_it_nspanel_cards_safeEval(command, returnValue = '', returnMode = '') {
  // evaluate command, use returnValue/returnMode as context
  let result = "";
  // convert to returnValue to Number if possible
  if (parseFloat(returnValue) == returnValue)
    returnValue = parseFloat(returnValue);
  if (command) {
      try { result = eval(command); }
      catch (e) {
        console.error("Error", e.stack);
        console.log("Error", e.toString());
        console.log("eval failed: '" + command + "'");
      }
  }
  return result;
}

function absorb_it_nspanel_cards_checkParam(param, noOfItems) {
  let result = String(param);
  if (result.charAt(0) != '~')
      result = '~' + result;
  while ((result.match(/~/g) || []).length < noOfItems)
      result += '~'
  while ((result.match(/~/g) || []).length > noOfItems)
      result = result.replace(/~/, '');
  return result;
}

function absorb_it_nspanel_cards_getPreamble(title, navigation) {
  return 'entityUpd' + absorb_it_nspanel_cards_checkParam(title, 1) +
      absorb_it_nspanel_cards_checkParam((navigation[0])?navigation[0].resultString:'', 6) +
      absorb_it_nspanel_cards_checkParam((navigation[1])?navigation[1].resultString:'', 6);
}

function absorb_it_nspanel_showCardThermo(title, navigation, target_temp, show_second_temp, second_temp, min_temp, max_temp, temp_steps, temp_unit, tCurTempLbl, currentTemp, tStateLbl, status, actions, tempChangeAction, pagePopupAction, refresh) {
  if (!absorb_it_nspanel_cards_startupCheck(arguments.callee.name)) return;
  absorb_it_nspanel_cards_killRefreshTimer();
  if (ctx.request == "pageOpenDetail") {
      let result = pagePopupAction[0];
      if (absorb_it_nspanel_cards_checkResult(result)) {
        absorb_it_nspanel_cards_startRefreshTimer(refresh);
        return;
      }
  }
  if (ctx.request == "pageUpdate") {
      let result = {};
      if (ctx.trigger == "tempUpdHighLow" || ctx.trigger == "tempUpd") {
          let newState = ctx.newState.split('|');
          let newValue = String(parseInt(newState[0])/10);
          if (newState[1] !== undefined)
              newValue += '|' + String(parseInt(newState[1])/10);
          absorb_it_nspanel_cards_safeEval(tempChangeAction, newValue);
          result = { "refreshPage": "1" }
      }
      if (absorb_it_nspanel_cards_checkResult(result)) {
        absorb_it_nspanel_cards_startRefreshTimer(refresh);
        return;
      }
  }
  let mqtt = absorb_it_nspanel_cards_getPreamble(title, navigation);
  mqtt += absorb_it_nspanel_cards_checkParam(ctx.ruleUID, 1) + absorb_it_nspanel_cards_checkParam(currentTemp, 1) + absorb_it_nspanel_cards_checkParam(target_temp * 10, 1) + absorb_it_nspanel_cards_checkParam(status, 1) + absorb_it_nspanel_cards_checkParam(min_temp * 10, 1) + absorb_it_nspanel_cards_checkParam(max_temp * 10, 1) +
  absorb_it_nspanel_cards_checkParam(temp_steps * 10, 1);
  for (let i = 0; i < 8; i++) {
    let result = actions[i];
    if (result === undefined) {
        mqtt += absorb_it_nspanel_cards_checkParam("", 4);
        continue;
    }
    if (absorb_it_nspanel_cards_checkResult(result)) {
      absorb_it_nspanel_cards_startRefreshTimer(refresh);
      return;
    }
    mqtt += absorb_it_nspanel_cards_checkParam((result.resultString)?result.resultString:'', 4);
  }
  mqtt += absorb_it_nspanel_cards_checkParam(tCurTempLbl, 1) + absorb_it_nspanel_cards_checkParam(tStateLbl, 1) + '~' + absorb_it_nspanel_cards_checkParam(temp_unit, 1);
  mqtt += (show_second_temp == "TRUE")?(absorb_it_nspanel_cards_checkParam(second_temp*10, 1)):'~';
  mqtt += (pagePopupAction[0])?'~0':'~1';
  if (ctx.previousPage !== ctx.ruleUID)
    items.getItem(ctx.target).sendCommand("pageType~cardThermo");
  items.getItem(ctx.target).sendCommand(mqtt);
  absorb_it_nspanel_cards_startRefreshTimer(refresh);
}

function absorb_it_nspanel_helpers_checkParam(param, noOfItems) {
  let result = String(param);
  if (result.charAt(0) != '~')
      result = '~' + result;
  while ((result.match(/~/g) || []).length < noOfItems)
      result += '~'
  while ((result.match(/~/g) || []).length > noOfItems)
      result = result.replace(/~/, '');
  return result;
}

function absorb_it_nspanel_helpers_getRGB565(color) {
  let result = (parseInt("0x"+color.substr(1, 2)) << 8) & 0xF800;
  result |= (parseInt("0x"+color.substr(3, 2)) << 3) & 0x07E0;
  result |= (parseInt("0x"+color.substr(5, 2)) >> 3) & 0x001F;
  return result;
}

function absorb_it_nspanel_getNavString(intNameEntity, icon, iconColor) {
  return {
    "sendNow": "1",
    "resultString": absorb_it_nspanel_helpers_checkParam('X', 1) + absorb_it_nspanel_helpers_checkParam(intNameEntity, 1) + absorb_it_nspanel_helpers_checkParam(icon, 1) + absorb_it_nspanel_helpers_checkParam(absorb_it_nspanel_helpers_getRGB565(iconColor), 1) + '~~'
  }
}

function absorb_it_nspanel_helpers_getEntityPos() {
  let ownEntityPos = cache.private.get('entity_pos');
  // prepare Position for next entity
  cache.private.put('entity_pos', parseInt(ownEntityPos) + 1)
  return ownEntityPos;
}

function absorb_it_nspanel_helpers_safeEval(command, returnValue = '') {
  // evaluate command, use returnValue as context
  let result = "";
  // convert to returnValue to Number if possible
  if (parseFloat(returnValue) == returnValue)
    returnValue = parseFloat(returnValue);
  if (command) {
      try { result = eval(command); }
      catch (e) {
        console.error("Error", e.stack);
        console.log("Error", e.toString());
        console.log("eval failed: '" + command + "'");
      }
  }
  return result;
}

function absorb_it_nspanel_getHVACButtonString(icon, iconColor, getState, setState) {
  let ownEntityPos = absorb_it_nspanel_helpers_getEntityPos();
  if (ctx.item == ownEntityPos) {
    if (ctx.request == 'pageUpdate') {
      absorb_it_nspanel_helpers_safeEval(setState);
      return { "refreshPage": "1" };
    }
  }
  return {
    "resultString": absorb_it_nspanel_helpers_checkParam(icon, 1) + absorb_it_nspanel_helpers_checkParam(absorb_it_nspanel_helpers_getRGB565(iconColor), 1) + absorb_it_nspanel_helpers_checkParam(getState, 1) + absorb_it_nspanel_helpers_checkParam('item?' + ownEntityPos, 1)
  }
}

function absorb_it_nspanel_cards_getRGB565(color) {
  let result = (parseInt("0x"+color.substr(1, 2)) << 8) & 0xF800;
  result |= (parseInt("0x"+color.substr(3, 2)) << 3) & 0x07E0;
  result |= (parseInt("0x"+color.substr(5, 2)) >> 3) & 0x001F;
  return result;
}

function absorb_it_nspanel_setupPopupThermo(icon, iconColor, topHeading, topOptions, topSelected, topAction, midHeading, midOptions, midSelected, midAction, lowHeading, lowOptions, lowSelected, lowAction) {
  if (ctx.newState) {
    switch (ctx.trigger) {
      case "mode-0":
        absorb_it_nspanel_cards_safeEval(topAction, parseInt(ctx.newState) + 1, topOptions.split('?')[ctx.newState]);
        break;
      case "mode-1":
        absorb_it_nspanel_cards_safeEval(midAction, parseInt(ctx.newState) + 1, midOptions.split('?')[ctx.newState]);
        break;
      case "mode-2":
        absorb_it_nspanel_cards_safeEval(lowAction, parseInt(ctx.newState) + 1, lowOptions.split('?')[ctx.newState]);
        break;
    }
    return { "refreshPage": "1" };
  }
  return {
      "sendNow": "1",
      "resultString": 'entityUpdateDetail' + absorb_it_nspanel_cards_checkParam(ctx.ruleUID, 1) + absorb_it_nspanel_cards_checkParam(icon, 1) + absorb_it_nspanel_cards_checkParam(absorb_it_nspanel_cards_getRGB565(iconColor), 1) + absorb_it_nspanel_cards_checkParam(topHeading, 1) + absorb_it_nspanel_cards_checkParam("0", 1) + absorb_it_nspanel_cards_checkParam(topSelected, 1) + absorb_it_nspanel_cards_checkParam(topOptions, 1) + absorb_it_nspanel_cards_checkParam(midHeading, 1) + absorb_it_nspanel_cards_checkParam("1", 1) + absorb_it_nspanel_cards_checkParam(midSelected, 1) + absorb_it_nspanel_cards_checkParam(midOptions, 1) + absorb_it_nspanel_cards_checkParam(lowHeading, 1) + absorb_it_nspanel_cards_checkParam("2", 1) + absorb_it_nspanel_cards_checkParam(lowSelected, 1) + absorb_it_nspanel_cards_checkParam(lowOptions, 1)
  }
}


cache.private.put('entity_pos', '0'); absorb_it_nspanel_showCardThermo(
    'Thermostat',
    [
        absorb_it_nspanel_getNavString('kitchen_entities', '', '#ffff00'),
        absorb_it_nspanel_getNavString('kitchen_blind', '', '#999999')
    ],
    items.getItem('Thermostat_Mode').state == '1' ? parseFloat(items.getItem('Thermostat_Set_Point_Heating').state) : (items.getItem('Thermostat_Mode').state == '2' ? parseFloat(items.getItem('Thermostat_Set_Point_Cooling').state) : null),
    'FALSE',
    21,
    60,
    85,
    1,
    '°F',
    ['Temp: ',parseFloat(items.getItem('Thermostat_Current_Temprature').state),'°F'].join(''),
    ['Hum: ',parseFloat(items.getItem('Thermostat_Humidity').state),'%'].join(''),
    'Mode: ' + String(items.getItem('Thermostat_Mode').state == '0' ? 'OFF' : (items.getItem('Thermostat_Mode').state == '1' ? 'HEAT' : (items.getItem('Thermostat_Mode').state == '2' ? 'COOL' : (items.getItem('Thermostat_Mode').state == '11' ? 'HEAT-AWAY' : 'COOL-AWAY')))),
    'Status: ' + String(items.getItem('Thermostat_Status').state == '0' ? 'IDLE' : (items.getItem('Thermostat_Status').state == '1' ? 'HEATING' : (items.getItem('Thermostat_Status').state == '2' ? 'COOLING' : null))),
    [
        absorb_it_nspanel_getHVACButtonString(items.getItem('Thermostat_state').state == 'ON' ? '' : '海', '#990000', items.getItem('Thermostat_state').state == 'ON' ? '1' : '0', `items.getItem('Thermostat_state').sendCommand((items.getItem('Thermostat_state').state == 'OFF' ? 'ON' : 'OFF'));`), absorb_it_nspanel_getHVACButtonString(items.getItem('Thermostat_state').state == 'ON' ? 'HO' : 'AW', '#ffffff', items.getItem('Thermostat_state').state == 'ON' ? '1' : '0', ``), absorb_it_nspanel_getHVACButtonString(items.getItem('Thermostat_state').state == 'ON' ? 'ME' : 'AY', '#ffffff', items.getItem('Thermostat_state').state == 'ON' ? '1' : '0', ``),
        , , ,
        ,
    ],
    `items.getItem('ThermostatSetPoint').sendCommand(returnValue);`,
    [
        absorb_it_nspanel_setupPopupThermo('*', '#ee0000', 'Mode', 'OFF?HEAT?COOL?AWAY_H?AWAY_C', 'opt1', `items.getItem('Thermostat_Mode').sendCommand((returnValue == '1' ? '0' : (returnValue == '2' ? '1' : (returnValue == '3' ? '2' : (returnValue == '4' ? '11' : (returnValue == '5' ? '12' : null))))));
  items.getItem('Thermostat_state').sendCommand((returnValue == '4' || returnValue == '5' ? 'OFF' : null));`, 'Fan Mode', 'AUTO?ON?CIRC', 'cold', `items.getItem('Thermostat_Fan').sendCommand((returnValue == '1' ? '0' : (returnValue == '2' ? '1' : (returnValue == '3' ? '6' : null))));`, '', '', '', ``)
    ],
    30
  );

Hi, there is no easy way to check this on my side, I don’t have the same Items and things. But because the screensaver activation is triggered by “sleepReached” context provided by the panel code. The callback code I wrote only reacts on this.

Best will be to debug the Card you created by adding every feature step by step. You can copy/paste things from blockly to blockly, this should make this attempt easier.

I assume there is some issue with the core nspanel code you flashed on the panel. But you can probably narrow it down removing some parts of your code step by step.

Give it a try, best regards, Rene

Hi:
After a whole lot of trials with inconsistent results. I created new scripts with exactly the same functionality just different files.
Now everything has worked fine for a couple of days.
I have no idea what was causing the problems.

Thank you

Hi @rene_rostock ,
first of all thanks for this great library and all the effort you put into it.
I have some questions and/or ideas for improvements.
As I just started my journey I may have missed something though.

Light Popup Style
In the screenshots on the Lovelace UI (https://github.com/joBr99/nspanel-lovelace-ui/raw/main/docs/img/screens.png) you can see the light entity popup with a styled slider for brightness and color temp. It looks different (way better imho) to what I get at the moment.
Seems that this newer style was introduced in v4.2.1 (Release v4.2.1 · joBr99/nspanel-lovelace-ui · GitHub). Is there any way to change the popup style to the newer one?

Light Entity in Grid Card
Is it possible to use the light entity from the grid card? According to lovelace ui docs this should be possible but in the blockly lib it seems to be disabled. Somewhere I read that in Grid Card the entity is rendered as button which can be pushed (toggle) or long pressed to open the popup ([BUG] Unable to change icon when using `service.light.toggle` · Issue #710 · joBr99/nspanel-lovelace-ui · GitHub).

Entity Card
I use the US version (portrait) and have space for more than 4 entities. According to the screenshots it should be possible to have 6 entities. Is it possible to change that in the blockly lib?

Hi @fog , lets go step by step trough your questions…

you can flash another release from github joBr99s nspanel firmwares. The blockly released for OpenHAB has nothing to do with the design used on the panel, this is all done by nspanel firmware. The OpenHAB addon only monitors and triggers changes on the nspanel, all drawing is done by the firmware. Just try to flash the new release onto your nspanel and you will get the new design.

I did not knew that this long-press was possible at all. You can try to edit the blocklibrary via the OpenHAB frontend:


Just remove lines 154 to 159 from absorb-it:blockly:nspanel_cards to enable any Entity at the first position (remove the check). If it works, just add " - nspanel_entityLightString" to every check and you can use this. Please report back, if this works I might change the source to enable this by default.

Didn’t thought of the US version and don’t have any to test things. But just try the updated version from github, which will support 6 entities. Not tested, but might work.

Best regards, Rene

Hi Rene,

thanks for your quick response. I’ll give it a try as soon as I can and report the results. May take some time because I’m not home for several days.

Thanks for clarifying this, but what do you mean by flash another release? Since I flashed the firmware just some days ago I assume that it is the most recent firmware already. I can double check which version was linked in the lovelace ui docs. However there is this hint in the release notes:

In the (HA) backend it looks like this:

 # background color
        dbc = 0
        defaultBackgroundColor = self._config.get("defaultBackgroundColor")
        if type(defaultBackgroundColor) is str:
            if defaultBackgroundColor == "ha-dark":
                dbc = 6371
            elif defaultBackgroundColor == "black":
                dbc = 0
        elif type(defaultBackgroundColor) is list:
            dbc = rgb_dec565(defaultBackgroundColor)

        featureExperimentalSliders=0
        if self._config.get("featureExperimentalSliders"):
            featureExperimentalSliders=1

        self._send_mqtt_msg(f"dimmode~{sleepBrightness}~{brightness}~{dbc}~~{featureExperimentalSliders}")

So I guess I would have to edit the nspanel_callback.yaml line 374?

Again thank so much for this great library.

br, Marc

Hi Marc,

Good point. :slight_smile: I won’t be near any suitable hardware for a good while, but changed the callback according to the nspanel file you referenced. Give it a try, hope it will work… (and pls. report back)

All the best, Rene

Hi Rene,

thanks for the update.
I had some time to test and it seems that there is a syntax error in the new code resulting in a nonfunctional callback. Accrording to the logs this statement is the issue, probably the semicolon. I removed it for testing but this did not work either. Don’t know why but the dimmode message is not send if the semicolon is removed - but no message in the logs.

(experimentalSliders == "TRUE")?'~~1':'~~0';

So to test further I simply send a static string in the sendCommand action for now. This works and the new style will be rendered. For some reason the brightness slider does not render correctly - the color temp works fine. May be an issue with the US Version. I’ll ask on github why this happens.

EDIT:
Okay, the glitches seem to be a known issue.

Don’t know if its worth to proceed on this topic…

That’s all for now.

br, Marc

Hi @rene_rostock
thanks to your suggestion to temporarily remove the check I can confirm that long press works with the light entity. Added the check for all entities and it works like a charm :slight_smile:
Also the 6 entities for the entities card work.

Will check the experimentalSliders later.

br, Marc and wish you a Merry Christmas

Hello @rene_rostock,

I am in the process of switching from NxPanel to the Lovelace design.

Thanks for the great blockly library and the super detailed description.

Hi @rene_rostock just discovered this great piece of coding and documentation! I have been running a version of Home Assistant which I can now switch off after finding this and the esphome binding. Thanks for your hard work. Do you have a buy me a beer page?

I am an old school user of Openhab and have always worked with text files so blockly is new to me. Has anyone just done a simple weather forecast page which I can cut and paste. I’ve got the panel working fine following the documentation but can’t get my head around getting a nice looking home screen. Thanks!

Ok after much frustration - i’ve figured out blockly! For anyone who want just a simple screensaver with todays weather and forecast using openweather - this is the code.

Can someone explain the Volume preset on the Media card - I am connecting a Sonos system and when I move the slider it keeps putting it back to the preset. The Sonos volume control works but it does not reflect in the slider.