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

Hi again:

I have run into an interesting problem

I have 3 panels, 2 use the “standard” screen saver and 1 uses the complex screen saver.
For all of them I created a rule using the helper to change the page brightness. The panels with the standard screen saver work without a problem

Here is a very interesting bug
The panel with the complex screen saver runs into an issue after changing the brightness, if I touch the screen to go to the main page it does not go back to the screen saver. I have to run the callback to fix the issue and then it works fine until I change the brightness again.

It seems to happen only when the andscreensaverbrightness part of the helper is set to 50, I have not played with the SetPageBrightness to see if the bug is also dependent on that value. I currently have it setup to 50.

Carlos

Hi,

Interesting, really. Are you sure all your libraries on the same version? Especially the callback might be older to trigger some crazy things, including this… Check if your Callback has INITIAL SCREENSAVER SETTINGS written as caption, as below

Else I can’t really help, you might sniff the MQTT traffic to see whats really going on. See some example here:

 ~ $ mosquitto_sub -v -h squid -t "+/nspanel_1/#"
...
cmnd/nspanel_1/CustomSend dimmode~50~100
stat/nspanel_1/RESULT {"CustomSend":"Done"}
...
tele/nspanel_1/RESULT {"CustomRecv":"event,buttonPress2,screensaver,bExit,1"}
cmnd/nspanel_1/CustomSend pageType~cardGrid2
...
tele/nspanel_1/RESULT {"CustomRecv":"event,sleepReached,cardGrid"}
cmnd/nspanel_1/CustomSend pageType~screensaver2

First I’m sending the default brightness values with the helper, than I press a button and later the panel triggers some sleepReached, which tells the callback to display the screensaver again. (in between there are a lot of lines skipped here)

Best regards, Rene

this helped a lot, thank you!

but before i go “live” with this solution, i have another problem to solve:
i use two entity buttons to change to specific favorites.
when i change the favorite my play/pause item changes to PAUSE and some ms later to PLAY again.
a possible solution would be to wait for some time and then send “refresh” to NSPanel_Item (for every ENTITY BUTTON):

but i have a feeling this could be solved in a smoother way…
it would also be nice if song title and author would be refreshed as soon one of these changes.

i have tried a rule that sends “refresh” to NSPanel_Item when one the item changes but that also interrupts the screensaver.

Hi Peter,

That’s how I implemented this as well, but I’m using the helper to reload:


(old-style vs. new-style)

You can also modify the Refresh Time of the Card to some low value. Shouldn’t be too short, 5sec. might be suitable.

You can use some other refresh option I used to prevent refresh timers to disable the screensaver (that’s why the name):


Maybe I should create some helper (with a better name) for that as well.

Btw, I’m just using “post update” in the examples above, it does not really make any difference, you can also “send command” to achive the same result.

Best regards, Rene

:+1:

it this a rule or is this part of a card?

You can just create this on your own.(?) The item is my own NSPanel Item, just send the “refreshTimer” string to the callback via the Item.

But I probably got the question wrong… Best regards, Rene

yes, i can do it :slight_smile:
i wasn’t sure where this block should be placed (or if it should be a RULE outside of any cards).

i have tried it with a rule:

configuration: {}
triggers:
  - id: "1"
    configuration:
      itemName: piCoreK_title
    type: core.ItemStateUpdateTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      itemName: NSPanel_Item
      state: refreshTimer
    type: core.ItemStateUpdateAction

seems to work!

1 Like

Hi,
i have a problem with the script.
The callback script produces this error message in openhab.log:

Script execution of rule with UID ‘ff5ee7ceb7’ failed: org.graalvm.polyglot.PolyglotException: ReferenceError: “contextValues” is not defined

Can anyone tell me what could create this issue ?

Hi,

Which version of openHAB you are using? The add-on only works with openHAB 4. If you are using version 4, the only way to help you is when you are posting some screenshot of the callback script (best the source code, but also blockly is ok) and the line number of the error and I will try to reproduce the issue here.

Best regards, Rene

Hi Rene,

i use Openhab 4.1.0 Release Build

This is the error log:

2024-03-07 20:27:34.723 [ERROR] [internal.handler.ScriptActionHandler] - Script execution of rule with UID 'ff5ee7ceb7' failed: org.graalvm.polyglot.PolyglotException: ReferenceError: "contextValues" is not defined
2024-03-07 20:27:40.571 [ERROR] [b.automation.script.javascript.stack] - Failed to execute script:
org.graalvm.polyglot.PolyglotException: ReferenceError: "contextValues" is not defined
        at <js>.absorb_it_nspanel_callback(<eval>:174) ~[?:?]
        at <js>.:program(<eval>:276) ~[?:?]
        at org.graalvm.polyglot.Context.eval(Context.java:399) ~[?:?]
        at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:458) ~[?:?]
        at com.oracle.truffle.js.scriptengine.GraalJSScriptEngine.eval(GraalJSScriptEngine.java:426) ~[?:?]
        at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:262) ~[java.scripting:?]
        at org.openhab.automation.jsscripting.internal.scriptengine.DelegatingScriptEngineWithInvocableAndAutocloseable.eval(DelegatingScriptEngineWithInvocableAndAutocloseable.java:53) ~[?:?]
        at org.openhab.automation.jsscripting.internal.scriptengine.InvocationInterceptingScriptEngineWithInvocableAndAutoCloseable.eval(InvocationInterceptingScriptEngineWithInvocableAndAutoCloseable.java:78) ~[?:?]
        at org.openhab.automation.jsscripting.internal.scriptengine.DelegatingScriptEngineWithInvocableAndAutocloseable.eval(DelegatingScriptEngineWithInvocableAndAutocloseable.java:53) ~[?:?]
        at org.openhab.automation.jsscripting.internal.scriptengine.InvocationInterceptingScriptEngineWithInvocableAndAutoCloseable.eval(InvocationInterceptingScriptEngineWithInvocableAndAutoCloseable.java:78) ~[?:?]
        at org.openhab.core.automation.module.script.internal.handler.ScriptActionHandler.lambda$0(ScriptActionHandler.java:71) ~[?:?]
        at java.util.Optional.ifPresent(Optional.java:178) [?:?]
        at org.openhab.core.automation.module.script.internal.handler.ScriptActionHandler.execute(ScriptActionHandler.java:68) [bundleFile:?]
        at org.openhab.core.automation.internal.RuleEngineImpl.executeActions(RuleEngineImpl.java:1188) [bundleFile:?]
        at org.openhab.core.automation.internal.RuleEngineImpl.runRule(RuleEngineImpl.java:997) [bundleFile:?]
        at org.openhab.core.automation.internal.TriggerHandlerCallbackImpl$TriggerData.run(TriggerHandlerCallbackImpl.java:87) [bundleFile:?]
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) [?:?]
        at java.util.concurrent.FutureTask.run(FutureTask.java:264) [?:?]
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) [?:?]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) [?:?]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) [?:?]
        at java.lang.Thread.run(Thread.java:840) [?:?]

This is the code from the blocky callback script:

function absorb_it_nspanel_callback_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 getTimerName() {
  return 'absorb_it_nspanel_refreshTimer_' + ctx.ruleUID;
}

function absorb_it_nspanel_callback_startScript(scriptName, context, updateCacheLink) {
  const RuleManager = osgi.getService('org.openhab.core.automation.RuleManager');
  var thread = Java.type('java.lang.Thread');

  if (!scriptName || scriptName === "Script Id")
    return 0;
  context["timerName"] = getTimerName();
  try {
      let timer = 10;
      while (RuleManager.getStatus(scriptName) && RuleManager.getStatus(scriptName).toString() === "RUNNING") {
        if (!timer--) {
          console.log("finished waiting for rule " + scriptName);
          break;
        }
        console.log("waiting 1 sec. for running rule " + scriptName);
        thread.sleep(1000);
      }
      rules.runRule(scriptName, context);
      cache.private.put('lastPage', scriptName); // any last page
      if (updateCacheLink)
        cache.private.put('currentCard', scriptName); // only some last pages (excluding popupNotify, Screensaver)
      return 1;
  }
  catch (e) {
      console.error("Error", e.stack);
      console.log("Error", e.toString());
      console.log("can't start/find rule with id '" + scriptName + "'");
  }
  return 0;
}

function absorb_it_nspanel_callback_safeEval(command, returnValue = '') {
  // evaluate command, use returnValue as context
  let result = "";
  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_callback_killRefreshTimer() {
  if (cache.shared.exists(getTimerName())) {
    console.debug("remove existing refresh timer " + getTimerName());
    cache.shared.remove(getTimerName()).cancel();
  }
}

function absorb_it_nspanel_callback(target, timeout, screenSaverBrightness, activeBrightness, screensaver2, screensaverStartupScript, startupScript, startupAction, startupRedo) {
  // on first run only...
  if (!cache.private.exists('initialized')) {
    // allow refreshing of dimming and timeout when saving configured callback
    items.getItem(target).sendCommand(
      "timeout" +
      absorb_it_nspanel_callback_checkParam(timeout, 1)
    );
    items.getItem(target).sendCommand(
      "dimmode" +
      absorb_it_nspanel_callback_checkParam(screenSaverBrightness, 1) +
      absorb_it_nspanel_callback_checkParam(activeBrightness, 1)
    );
    // store settings from blockly interface to be able to change them on-the-fly
    cache.private.put('complexScreenSaver', screensaver2);
    cache.private.put('enterScriptName', screensaverStartupScript);
    cache.private.put('leaveScriptName', startupScript);
    cache.private.put('startupRedo', startupRedo);
    // done
    cache.private.put('initialized', 1);
  };

  try {
    contextValues = (JSON.parse(event.itemState))["CustomRecv"].split(',');
  } catch {
    if (event.itemState) {
      // if callback received non-JSON string check for local requests
      let callbackParam = event.itemState.toString().split('?');
      switch (callbackParam[0]) {
        case "refresh":
        case "ON":    // triggered by Hardware Button press (if rule configured to fire)
        case "OFF":   // triggered by Hardware Button press (if rule configured to fire)
          if (cache.private.get('currentCard')) {
            let context = {
              "previousPage": cache.private.get('lastPage'),
              "target": target
            };
            if (cache.private.exists('DetailPage')) {
              context["request"] = "pageOpenDetail";
              context["item"] = cache.private.get('DetailPage');
            };
            absorb_it_nspanel_callback_startScript(
              cache.private.get('currentCard'),
              context,
              0
            );
          }
          return;
        case "refreshTimer":
          if (cache.private.get('currentCard') && (cache.private.get('lastPage') !== "screensaver"))
            absorb_it_nspanel_callback_startScript(
              cache.private.get('currentCard'),
              {
                "previousPage": cache.private.get('lastPage'),
                "target": target
              },
              0
            );
          return;
        case "loadPage":
          if (callbackParam[1])
            contextValues = [ , "buttonPress2", callbackParam[1] ];
          break;
        case "loadScreensaver":
          contextValues = [ , "sleepReached" ];
          break;
        case "newTimeout":
          if (callbackParam[1])
            items.getItem(target).sendCommand(
              "timeout" +
              absorb_it_nspanel_callback_checkParam(callbackParam[1], 1)
            );
          return;
        case "newBrightness":
        case "newBrigthness":   // keep old value, type of parameter was fixed in 0.6
          if (callbackParam[1] && callbackParam[2])
            items.getItem(target).sendCommand(
              "dimmode" +
              absorb_it_nspanel_callback_checkParam(callbackParam[1], 1) +
              absorb_it_nspanel_callback_checkParam(callbackParam[2], 1)
            );
          return;
        case "complexScreenSaver":
        {
          cache.private.put('complexScreenSaver', callbackParam[1]);
          return;
          }
        case "enterScriptName":
        {
          cache.private.put('enterScriptName', callbackParam[1]);
          return;
        }
        case "leaveScriptName":
        {
          cache.private.put('leaveScriptName', callbackParam[1]);
          if (callbackParam[1])
            cache.private.put('startupRedo', "TRUE");
          else
            cache.private.put('startupRedo', "FALSE");
          return;
        }
        default:
          return;
      }
    }
  }
  switch(contextValues[1]) {
    case "startup":
      cache.private.put('screensaverActive', "FALSE");
      items.getItem(target).sendCommand(
        "timeout" +
        absorb_it_nspanel_callback_checkParam(timeout, 1)
      );
      items.getItem(target).sendCommand(
        "dimmode" +
        absorb_it_nspanel_callback_checkParam(screenSaverBrightness, 1) +
        absorb_it_nspanel_callback_checkParam(activeBrightness, 1)
      );
      let success = absorb_it_nspanel_callback_startScript(
        cache.private.get('leaveScriptName'), { "target": target }, 1
      );
      absorb_it_nspanel_callback_safeEval(startupAction);
      if (success || startupAction != '')
        break;
    case "sleepReached":
      absorb_it_nspanel_callback_killRefreshTimer;
      cache.private.put('screensaverActive', "TRUE");
      if (cache.private.get('complexScreenSaver') == "TRUE")
        items.getItem(target).sendCommand("pageType~screensaver2");
      else
        items.getItem(target).sendCommand("pageType~screensaver");
      absorb_it_nspanel_callback_startScript(
        cache.private.get('enterScriptName'), { "target": target }, 0
      );
      cache.private.put('lastPage', 'screensaver'); // therefore ignore timer updates during screensaver
      break;
    case "buttonPress2":
      if (cache.private.get('screensaverActive') == "TRUE") {
        cache.private.put('screensaverActive', "FALSE");
        if (cache.private.get('startupRedo') == "TRUE")
          cache.private.remove('currentCard');
      }
      switch(contextValues[3]) {
        case "bExit":
          cache.private.remove('DetailPage');
          if (cache.private.get('currentCard'))
            absorb_it_nspanel_callback_startScript(
              cache.private.get('currentCard'), { "target": target }, 0
            );
          else
            absorb_it_nspanel_callback_startScript(
              cache.private.get('leaveScriptName'), { "target": target }, 1
            );
        break;
        default:
          if (contextValues[2].split('?')[0] !== cache.private.get('lastPage')) {
            // new page called, probably clicked on navigation buttons
            absorb_it_nspanel_callback_startScript(
              contextValues[2].split('?')[0], { "target": target },
              (contextValues[2] !== "popupNotify" && contextValues[3] !== "notifyAction")
            );
          }
          // find the clicked item, if there hade been multiple on the page
          let item = "";
          // alarmButtons: third context field contains 'cb' and item id, reparated by '?'
          // {"CustomRecv":"event,buttonPress2,*cardAlarmScriptName*,item?1,1"}
          if (contextValues[3] && contextValues[3].split('?')[0] === "item")
            item = contextValues[3].split('?')[1];
          // HVACButtons: third context field contains script name and item id, reparated by '?'
          // {"CustomRecv":"event,buttonPress2,*cardThermoScriptName*,hvac_action,item?1"}
          if (contextValues[4] && contextValues[4].split('?')[0] === "item")
            item = contextValues[4].split('?')[1];
          // entities: third context field contains both script name and item id, reparated by '?'
          // {"CustomRecv":"event,buttonPress2,*cardEntitiesScriptName*?2,OnOff,0"}
          else item = contextValues[2].split('?')[1];
          absorb_it_nspanel_callback_startScript(
            contextValues[2].split('?')[0],
            {
              "request": cache.private.exists('DetailPage')?"pageOpenDetail":"pageUpdate",
              "item": item,
              "trigger": contextValues[3],
              "newState": contextValues[4],
              "previousPage": cache.private.get('lastPage'),
              "target": target
            },
            (contextValues[2] !== "popupNotify" && contextValues[3] !== "notifyAction")
          );
        }
        break;
    case "pageOpenDetail":
      let item = contextValues[3].split('?')[1];
      cache.private.put('DetailPage', item?item:0);
      absorb_it_nspanel_callback_startScript(
        contextValues[3].split('?')[0],
        {
          "request": "pageOpenDetail",
          "item": item,
          "trigger": contextValues[2],
          "target": target
        },
        1
      );
      break;
    default:
  }
}
absorb_it_nspanel_callback('nxpanel_command', 20, 50, 100, 'FALSE', '', 'nxpanel_card_grid', `items.getItem('nxpanel_command').sendCommand('loadScreensaver');`, 'TRUE');

The mqtt items work, because i tried nxpanel before.

Regards B-Tronic

Hi B-Tronic,

thanks for posting this. It seems like you are calling the callback directly, this error also comes up if you press run in the blockly editor. The context information is missing, mainly event.itemState. Do you have the callback configured in a rule, as mentioned in the documentation? … I surely have to add some more meaningful error message in this case.

Hope this helps, best regards, Rene

Hi Rene,

thank you for the hint, but i didn´t call the callback directly.
The problem was the item. I use openhab till version 2.x and my configuration is done with text files.

Now i have setup the trigger/command item with the ui and everything works.

Thankyou for your help, also the great work !

Regards B-Tronic

Hi B-Trionic,

ahh, just because of you I figured out now that there is a difference in the interface of UI created rules and file based rules. I will check this during the next days, maybe I can get the add-on working in both modes by verifying if receivedState or itemState exist and use the right one for processing.

Good that it works now, best regards, Rene

Did anyone test the color wheel from the entity light ?
The RGB values are strange.

Hi,

same here in the received mqtt (always having 160 as blue value, that’s definitely not right):

tele/nspanel_1/RESULT {"CustomRecv":"event,buttonPress2,nspanel_cardEntities?0,colorWheel,115|83|160"}
tele/nspanel_1/RESULT {"CustomRecv":"event,buttonPress2,nspanel_cardEntities?0,colorWheel,30|94|160"}
tele/nspanel_1/RESULT {"CustomRecv":"event,buttonPress2,nspanel_cardEntities?0,colorWheel,82|87|160"}
tele/nspanel_1/RESULT {"CustomRecv":"event,buttonPress2,nspanel_cardEntities?0,colorWheel,72|27|160"}
tele/nspanel_1/RESULT {"CustomRecv":"event,buttonPress2,nspanel_cardEntities?0,colorWheel,90|37|160"}
tele/nspanel_1/RESULT {"CustomRecv":"event,buttonPress2,nspanel_cardEntities?0,colorWheel,130|47|160"}

That’s probably some issue with the NSpanel Lovelace UI implementation, or maybe I’m missing some undocumented but required initialization? No idea how to fix that, sorry. Maybe you open some bug report for NSPanel Lovelace UI?

Best regards, Rene

Hi,

just released version 0.9 which fixes the issue with the color-wheel. It now returns a color instead of a touch position. The interface of the Light Entity has changed, therefore you need to re-insert the new Light Entity block to get it running with the new version.

Enjoy and thanks to @b-tronic for helping to solve this. Best regards,
Rene

1 Like

Hi.
Its really amazing what you made Rene.
I just want to ask some things.
First in card grid why i cant use entity switch. i know i can use entity button, just asking.
and second, if i wanted to use in card grid the pop up of entity light, how can i achieve that?

thanks in advanced, anyone who can answer those questions.

one last thing, nspanel-tasmota doesn’t support long press on touchscreen, right?

Hi thkouk, sorry for the late reply, currently on some festival I’m not daily connected to internet…

Currently this is not possible, without a one-line change in the blockly code. If you are familiar with this, you can modify the block library on your own by opening the nspanel_cards library and removing/modifying the “check” from lines 154 to 159 . This way you can add any entity to the CardGrid and see if it works. I can’t remember why I did not enabled the entityLight, maybe I misjudged this and it will work.

II’m not at home now, therefore I can’t test this on my own installation and tell you more.
thanks in advanced, anyone who can answer those questions.

Not that I know it, but might be wrong. Sorry if this wasn’t helpful, just come back on this topic and I will check again in about one week.

Best regards, Rene

Thank you for your answer.
i dont think its going to work your suggestion. the reason is the light is a switch, so even if i add it in card grid, it will turn on and off the light. what i want, is the pop up with the color wheel. how can i card grid add a button that will open the color wheel selection of the light entity that you made.
Its not something important, but i believe it will be nice to exist.

Hi, back at home and thought about this again…

That’s not the problem, you can decide with the configuration what will happen if you trigger the switch. Therefore also nothing can happen…

But, the change from the normal entity into the popup is entirely done on the panels software. And this popup opening is not triggered on the panel itself when the entity is used in CardGrid. Therefore you can’t successfully use it without any changes to the firmware.

You can use the CardGrid to navigate to some CardEntity, which can open the LightPopup, but this is probably not what you like to have.

Sorry, don’t see any option to change this on my side - maybe you can open an issue and ask at the issue tracker for the firmware. Best regards,
Rene