How to call a Homey script from an App

Is it possible to call a Homey script (bij it’s name) with a parameter from a .js file in an App. I tried:

result = window[ScriptName](ScriptPara);
result = this[ScriptName](ScriptPara);
result = self[ScriptName](ScriptPara);
result = top[ScriptName](ScriptPara);
result = globalThis[ScriptName](ScriptPara);
result = global[ScriptName](ScriptPara);

(ScriptName contains the name of the script and ScriptPara it’s parameter as a string object.)

the above all crash.

window, top, etc won’t work because Homey apps have no DOM because they’re not running in a browser (nor do they run in some sort of global context, apps are self-contained).

It’s probably possible by executing a flow card through the Web API, but from what I can see in the HomeyScript app source, it doesn’t return the result of the script in any useful way.

Besides that: why…?

I’m busy changing the MQTT Hub App, fixing some bugs and add functionality.
There is an item that’s called outputTemplate and I changed the code so that it will accept a string like {“RoomTemp” : “#value#”} which I need for my ECODAN Heat Pump.

And than is there a bug if you change the Topics in settings the displayName will revert back to it’s defaults. A quick and dirty trick is to change de item “displayName” by “title” and that will fix it, but I’m busy to fix it in code.

{
  "target_temperature": {
    "capability": "target_temperature",
    "stateTopic": "ECODAN/tele/0x09",
    "commandTopic": "ECODAN/cmnd",
    "valueTemplate": "Zone1_Room_Temp_Setpoint",
    "outputTemplate": "<<{\"SetRoomTemperature\" : \"#value#\"}",
    "title": "Room Temperature"
  }
}

note the “valueTemplate” (<< is a trigger) and the “title” which previously was “displayName”.

The new code will take the string in outputTemplate and swap the #value# with the real value received earlier from MQTT and send it out.

This is working fine for now but sometimes you need to do more complex operations before sending it to MQTT and that’s why I got the idea of calling a user defined Homey script.

But if that’s not possible I can always fill the string with a javascript and execute that script with eval or with:

var Func=new Function(outputTemplate);

and call it to get the result.

This is not easy for me because I just started using Homey and altough I’m a Assembler/C++ programmer for 40 years I’m strugling with Java :slight_smile:

Webhook is also possible

HomeyScript really isn’t meant for that, it’s main purpose is to allow for more complex flows than what’s possible with existing cards. So another solution would probably be better.

However, are you sure that it’s not already possible to do what you want using the existing app? The documentation says here that " If JSON output is required, JSON-T style formatting is implemented".

Homey is JavaScript, not Java. Very different :wink:

I tried but I have no idea how to create a value with JSON-T that looks like:
{“SetRoomTemperature” : “#value#”} where #value# is the value received by valueTemplate. The original code piece is:

    if(config.outputTemplate && config.outputTemplate.replace("{{", "").replace("}}", "").trim() !== 'value') {
        const template = config.outputTemplate;
        try {
            let state = this.getState() || {};
            state.value = payload;
            const mathjs = this.compiled.get(template);
            if(mathjs) { // math?
                payload = mathjs.evaluate(state);
            } else { // json-t
                if(!template.startsWith('{{') && (template.startsWith('{') || template.startsWith('['))) {
                    payload = jsontObject(template, state);
                } else {
                    payload = jsontString(template, state);
                }
            }
            
        } catch(e) {
            this.log("failed to format output message", e);
        }
    }

My guess would be:

{ "SetRoomTemperature" : "$[value]" }

Never mind, wrong JSON-T.

It looks like the app uses this package, so:

{ "SetRoomTemperature" : "{{value}}" }

But it depends on what state contains.

The idea was a simple way for the normal user by creating a script for that purpose. I don’t think there is a way to trigger a webhook which will call a script and send the result of that script back to the original caller.

I tried that one and it didn’t work?

What are the contents of state?

I don’t use this app so can’t test myself.

Result in MQTT " { “SetRoomTemperature” : “{{value}}” }" so value is taken literal. I tried so many different ways and nothing worked so I decided to change the code :slight_smile:

What does the original developer (@HarriedeGroot) have to say about it not working?

Until now no response…

Did you try this example:

So:
{ “SetRoomTemperature” : {{value}} }

HUB Version 3.1.0+

1 Like

And also make sure everything is properly quoted.

Here’s an example I’m testing with, which is working fine:

{
  "measure_battery": {
    "capability": "measure_battery",
    "stateTopic": "test/state",
    "setTopic": "test/set",
    "valueTemplate": "$.value",
    "outputTemplate": "{\"xxx\":{{value}}}",
    "displayName": "Battery"
  }
}

Pushing a value:

$ mqtt_pub -vh 192.168.23.2 'test/state' '{"value":80}'

Output (on the test/set topic):

{"xxx":80}

Yes it works, “{"SetRoomTemperature":"{{value}}"}” gives the output “cmnd = {“SetRoomTemperature”:“20”}” cmd is the topic.

Thanks for helping that’s one problem solved, is it also possible to do some calculations in JSON-T like {{value*100}}?

It doesn’t look like that’s supported, no, not with the JSON templater lib nor with the app (which looks like it supports either MathJS or a JSON template, but not both at the same time).

Yes it works, thanks.
There are also a few bugs (3) I put below, maybe you have some spare time left and will be so kind to fix them?. May thanks in advance and I love your App.

  1. If you change a Topics in settings the displayName is reset to default. You can fix this with replacing “displayName” with “title” in Topics or to fix the code so that it calls setCapabilityOptions (when the displayName changes) i’m trying to find a good spot to call it, I tried to use it in onSettings but that’s not working ok.

  2. Your GitHub code can’t be run in debug mode (homey app run) it gives the error:

× App Crashed. Stack Trace:
× Inspector is already activated. Close it with inspector.close() before activating it again.
───────────────────────────────────────────────────────
✓ Uninstalling nl.hdg.mqtt
✓ Removing container…
node:internal/process/promises:288
triggerUncaughtException(err, true /* fromPromise */);
^

[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason “Inspector is already activated. Close it with inspector.close() before activating it again.”.] {
code: ‘ERR_UNHANDLED_REJECTION’
}

  1. Some warnings:

Warning: flow.triggers[‘device_changed’].tokens[‘value’].type is missing, it defaults to “string”. Specifying a token type will be required in the future.
Warning: flow.triggers[‘device_changed’].tokens[‘Capability’].type is missing, it defaults to “string”. Specifying a token type will be required in the future.
Warning: flow.triggers[‘device_changed’].tokens[‘device’].type is missing, it defaults to “string”. Specifying a token type will be required in the future.
Warning: flow.actions[‘set_value’].titleFormatted is missing. Specifying a Flow card’s formatted title will be required in the future.

That’s exactly how I run it (well, using homey app run --remote) so that’s something local to you. Did you run npm install before homey app run?

About de displayName problem, I have changed the code with the part below and it seems to work fine. Maybe you could implent it because there are more people with this problem.

And I also have a suggestion to enhance the "_publishMessage"routine with a function mode to be called from a string. This way you can do more complex things, combine JSON-T and Mathjs to make a layout like {“Name”, “value”} and also do complex calculations on value. I put the code also below it’s tested and seems to work ok.

    if (changedKeys && changedKeys.includes('topics') && settings.topics) {
        try {
            await this.unsubscribeFromTopics();
            await this._updateCapabilities(capabilities);
            this.initTopics();
            await this.subscribeToTopics();
            await this.broadcast();

            // TSP Start
            for (let capabilityId in capabilities) { 
              var DPN = capabilities[capabilityId].displayName;
              if (DPN){
                await this.setCapabilityOptions( capabilityId, {title: DPN} ).catch(this.error); 
              };
            }
            // TSP - End 

        } catch(e) {
            // probably invalid JSON
            this.log("failed to update MQTT Device topics", e);
            this.restoreSettingsTopics(settings);
            this.initTopics();
        }
    } else {
        this.initTopics();
    }

    // output template?
    if(config.outputTemplate && config.outputTemplate.replace("{{", "").replace("}}", "").trim() !== 'value') {
        const template = config.outputTemplate;
        try {
            let state = this.getState() || {};
            state.value = payload;
            const mathjs = this.compiled.get(template);
            //
            // TSP - Start
            //
            // outputTemplate example
            //
            // "<<SetRoomTemperature@@if (Para != \"\" && Val){\n  RV = '{\"' + Para + '\" : \"' + Math.round(State.value) + '\"}'\n  return( RV );\n}"
            //
            // The format of the outputTemplate is:
            // "<<"   - The Trigger to let this routine know that is has an embedded function
            // Para   - Your passed value for the subroutine as string (can be multiple with a delimiter)
            // @@     - Delimiter for the Para and the Function code as String after @@ is your function code in a string
            //
            // The Subroutine has 2 passed variables:
            // Para   - Your string para (e.g SetRoomTemperature)
            // State  - the state variable which contains all current values of the device
            //          e.g. {"target_temperature":null,"measure_temperature":22,"measure_temperature.1":24.5,"target_temperature.1":20,"value":20}
            //               value is the value of the current item that's being processed
            //
            // Good web tool to convert your funtion to a string: https://tomeko.net/online_tools/cpp_text_escape.php?lang=en
            //
            if (template.startsWith("<<")){                                       // Embedded Function Trigger
              const Dum = template.replace("<<", "").split("@@");                 // Remove Trigger and split rest of outputTemplate
              var Para  = Dum[0];                                                 // Get user parameter
              var Func  = Dum[1];                                                 // Get embedded function
              try {
                var FNC = new Function("Para", "State", Func);
                payload = FNC( Para, state  );
              } catch(e) {
                this.log("TSP - Error calling Function.");
								} // TSP - End (Including the if then else loop)
            } else {
                if(mathjs) { // math?
                    payload = mathjs.evaluate(state);
                } else {    // json-t
                    //
                    // JASON-T in the format {"SetRoomTemperature":"{{value}}"} or {"SetRoomTemperature":"{{measure_temperature.1}}"} etc...
                    //
                    if(!template.startsWith('{{') && (template.startsWith('{') || template.startsWith('['))) {
                        payload = jsontObject(template, state);
                    } else {
                        payload = jsontString(template, state);
                    }
                }
            }                
        } catch(e) {
            this.log("failed to format output message", e);
        }
    }