[APP][Pro] HomeyScript

Thank you very much, that put me on the right track!

I understand how to set variables with Logic, but can I also set/get them in code? Or is that not recommended, it is better to use flow building blocks for that?

Also, when I call console.log() in a flow - where does that log end up? I’ve seen that I can test-run a single Hscript block and get the output, but not possible for a whole flow?

YW!

You can write & read logics variables like this

.

Yeah you can see the log when test run a Hscript block.
I don’t know exactly how, but you can log errors, which should be returned by the ‘red’ wire of flowcards / hscript blocks.
You should catch errors like this

‘Normal’ console.log output you can redirect to a Homeyscript tag, or a logics var.
Or, use the ‘return’, which writes to the ‘result’ tag of Hscript blocks

.
You can use (almost?) any ‘And’ or ‘Then’ flowcard as well. Take a look at the example scrips in Hscript
Write log to timeline:

Homey.flow.runFlowCardAction({
        uri: 'homey:manager:notifications',
        id: 'create_notification',
        args: {
          text: 'This is a test Timeline notification sent by Homeyscript'
        }
});


  • Write log to push message:
    First run this little script
// To send a push message for example, you'll need the id and athomId of the user
resId = await Homey.users.getUsers().then(f => Object.values(f).reduce((r,b,c)=>Object.assign(r, {[b.name]:b.id}), {}));
log("The \'id\' of the users:\n", resId);

resAthomid = await Homey.users.getUsers().then(f => Object.values(f).reduce((r,b,c)=>Object.assign(r, {[b.name]:b.athomId}), {}));
log("\n\nThe \'athomId\' of the users:\n", resAthomid);


  • Send the actual pushmessage using the id and athomId:
Homey.flow.runFlowCardAction({
        uri: 'homey:manager:mobile',
        id: 'push_text',
        args: {
			    user: {
			    id: 'xxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx',
			     athomId: 'xxxxxxxxxxxxxxxxxxxxxx',
		    	},
		    text: 'This is a test push notification from HomeyScript'
        },
      });
  return(true);

Thanks for all the help Peter, very informative !

1 Like

You’re very welcome, Christer

You can throw an error. That triggers the error output and the error message is returned and can be used to write to timeline (or anythin else).

throw new Error("Message");

@clindh fyi

1 Like

I’m trying to start with HomeyScript. As one of my first projects I’m trying to get a list of all plants that need water in just one notification, instead of a notification per plant. I created the following script, but there is one problem. In the app that contains my plants, the ID of the value is written like this: measure_numeric.sensor.beaucarnea_recurvata_moisture. Since all the plants have different names, their ID changes as well. Is there a way to check for all the variables that contain “_moisture” so that I can have all plants without adding them on a namely basis.

Thanks in advance.

// Common variables
let devices = await Homey.devices.getDevices();
let firstLineMoisture = "Plants that need water:\n";

// Low Moisture variables
const percentage = (10); 
let devicesLowMoisture = [];

for (const device of Object.values(devices)) {
  if (!device.capabilitiesObj["_moisture"]) continue;
  // Only check devices in Home Assistant
  if (!device.driverUri.match("io.home-assistant.community")) continue; 

  // Check moisture percentage of devices
  let lowMoistureCount = 0;
  for (const capabilityObj of Object.values(device.capabilitiesObj)) {
    if (device.capabilitiesObj["_moisture"].value <= 100) {
      lowMoistureCount++;
    }
  }

  // Add to list if moisture < 10%
  if (lowMoistureCount && lowMoistureCount === Object.keys(device.capabilitiesObj).length) {
    devicesLowMoisture.push("- " + device.name + " (" + device.capabilitiesObj["_moisture"].value + "%)");
  }
}

// When there are matching sensors, create / update HomeyScript variable
if (devicesLowMoisture.length != 0) {
  await tag("devicesLowMoisture", devicesLowMoisture.join("\n"));
} 

// Output
if (devicesLowMoisture.length != 0) {
  console.log(firstLineMoisture + devicesLowMoisture.join("\n"));
  return(firstLineMoisture + devicesLowMoisture.join("\n"));
}
if (devicesLowMoisture.length == 0) {
  console.log("none");
  return("none");
}

Solved this with a little help of ChatGPT. The only thing ChatGPT wasn’t able to tell me is how to also have the zone visible within the list of results. I can show the ID of the zone without any problems. But as soon as I want to show the name, it says undefined. Is there a way to show the name of the zone the device is in?

I think it’s more fun for other readers when you share what the solution was :wink:

PeterGPT says:

let devices = await Homey.devices.getDevices();

_.some(devices, device => 
  {
    console.log(`\n[Name: ${device.name} \nZoneName: ${device.zoneName} \ndevice.class: ${device.class} \ndevice.capabilities: ${device.capabilities}]`); 
  }
)
1 Like

Below is the new code that I use. Unfortunately when I add the code you mention, I still get undefined as zone name.

// Common variables
let devices = await Homey.devices.getDevices();
let firstLineMoisture = "Plants that need water:\n";
let firstLineMoistureOne = "A plant needs water:\n";

// Plant names and their respective moisture thresholds
const plants = {
  "Plant 1": 100,
  "Plant 2": 100,
  "Plant 3": 100
};

// List to store plants that need water
let plantsNeedWater = [];

for (const device of Object.values(devices)) {
  if (!device.capabilitiesObj) continue; // Ensure capabilitiesObj exists for the device
  if (!device.driverUri.match("io.home-assistant.community")) continue; // Only check devices in Home Assistant
  
  const deviceName = device.name;
  const capabilityKeys = Object.keys(device.capabilitiesObj);
  
  for (const key of capabilityKeys) {
    if (key.endsWith('_moisture')) {
      const moistureCapability = device.capabilitiesObj[key];
    
      
      // Check if the device belongs to a plant and if its moisture is below the threshold
      for (const plantName in plants) {
        if (deviceName.includes(plantName) && moistureCapability.value <= plants[plantName]) {
          plantsNeedWater.push("- " + deviceName + " (" + moistureCapability.value + "%)");
        }
      }
    }
  }
}

// When there are matching sensors, create / update HomeyScript variable
if (plantsNeedWater.length === 1) {
  await tag("Low moisture", firstLineMoistureOne + plantsNeedWater.join("\n"));
}
else if (plantsNeedWater.length !== 0) {
  await tag("Low moisture", firstLineMoisture + plantsNeedWater.join("\n"));
} 


// Output
if (plantsNeedWater.length === 1) {
  console.log(firstLineMoistureOne + plantsNeedWater.join("\n"));
}
else if (plantsNeedWater.length !== 0) {
  console.log(firstLineMoisture + plantsNeedWater.join("\n"));
} 
else {
  console.log("none");
}

I don’t believe the zone name is in the details of the device itself, just the ID (as you already noticed).

To get the name you’ll need to get the zone information with
const zone = await Homey.zones.getZone({ id: 'ID-OF-ZONE' });
function, and then get the name from that result.

That seems to work :D. Thanks! I ended up with this code:

// Common variables
let devices = await Homey.devices.getDevices();
let firstLineMoisture = "Plants that need water:\n";
let firstLineMoistureOne = "A plant needs water:\n";

// Plant names and their respective moisture thresholds
const plants = {
  "Plant 1": 100,
  "Plant 2": 100,
  "Plant 3": 100
};

// List to store plants that need water
let plantsNeedWater = [];

for (const device of Object.values(devices)) {
  if (!device.capabilitiesObj) continue; // Ensure capabilitiesObj exists for the device
  if (!device.driverUri.match("io.home-assistant.community")) continue; // Only check devices in Home Assistant
  
  const deviceName = device.name;
  const zone = await Homey.zones.getZone({ id: device.zone });
  const capabilityKeys = Object.keys(device.capabilitiesObj);
  
  for (const key of capabilityKeys) {
    if (key.endsWith('_moisture')) {
      const moistureCapability = device.capabilitiesObj[key];
    
      
      // Check if the device belongs to a plant and if its moisture is below the threshold
      for (const plantName in plants) {
        if (deviceName.includes(plantName) && moistureCapability.value <= plants[plantName]) {
          plantsNeedWater.push("- " + deviceName + " in " + zone.name + " (" + moistureCapability.value + "%)");
        }
      }
    }
  }
}

// When there are matching sensors, create / update HomeyScript variable
if (plantsNeedWater.length === 1) {
  await tag("Low moisture", firstLineMoistureOne + plantsNeedWater.join("\n"));
}
else if (plantsNeedWater.length !== 0) {
  await tag("Low moisture", firstLineMoisture + plantsNeedWater.join("\n"));
} 


// Output
if (plantsNeedWater.length === 1) {
  console.log(firstLineMoistureOne + plantsNeedWater.join("\n"));
}
else if (plantsNeedWater.length !== 0) {
  console.log(firstLineMoisture + plantsNeedWater.join("\n"));
} 
else {
  console.log("none");
}

I created a new topic as per below, then I found this topic which does seem to have some recent activity. I don’t know how or if I can move it.

Could someone have a look at this for me. I have done a lot of reading but can’t find an answer.

How to run Homeyscript from an Advanced Flow with Args[]