Playing Sonos favorite from HomeyScript

Not sure if this question is for developers forum but it definitely something that you guys shoud be able to help me. I am looking in to using HomeyScript to do some more elaborate flows. In order make it work I am planning to use Homey.flow.runFlowCardAction. One of the action cards I want to run is “cloud_play_sonos_favorite”. I am trying to do it using following code:

await Homey.flow.runFlowCardAction({
  uri: 'homey:device:<speaker id>,
  id: 'cloud_play_sonos_favorite',
  args: [{ 
      name: '<favorite name>', 
      type: 'autocomplete', 
      title: 'Favorite' 
  }]
});

I am getting following error message:

:warning: Cannot read property ‘id’ of undefined: Cannot read property ‘id’ of undefined
at /node_modules/athom-api/dist/index.js:1:1186403
at processTicksAndRejections (internal/process/task_queues.js:97:5)

What am I missing here. I see this card has autocomplete argument so maybe I need to provide favorite value differently. Or this action card can not be called from script? Can we use all action card available in flows or there are limitations?

Theres a ‘ missing in the uri line. The string is not closed. I have no experience with Sonos, so I cannot be of much help with how to use the card. Values enclosed in <> should usually be replaced with actual values though.

// get the card for the device
// you need to find the device id first
const uri = 'homey:device:32d7a90e-abcb-41f1-ba09-04f833e96565';
const id = 'action_project_task';
const card = await Homey.flow.getFlowCardAction({ uri: uri, id: id });

// you dont really need this you can hardcode arg.name in getFlowCardAutocomplete
// this is just to show the relation between them
const arg = card.args.find((arg => arg.name === 'project'));

// the result of this is a list with all the possible options for this argument
// so select the one you want and pass it to runFlowCardAction
// you can also hardcode this in runFlowCardAction
const options = await Homey.flow.getFlowCardAutocomplete({
  uri: uri,
  id: id,
  name: arg.name,
  query: '',
  type: `flowcardaction`
})

Homey.flow.runFlowCardAction({
  uri: uri,
  id: id,
  // key value pairs
  // args[0] is an object in this case
  args: {
    [arg.name]: options[0],
    // hardcoded arg example
    content: 'Test'
  }
})

Example here for a different card but the principle is the same. You can find the device id in the developer tools or use HomeyScript to find it.

Hey thanks Edwin. Yes those values shod be replaced with real ones. And yes closing ’ is missing but that is just here not in my test code :).

Jero that is what I need, how to handle autocomplete parameters in general. Thanks :slight_smile: will try it.

Hmm. I don’t think it is funny that I spend time to find an error in your code that is not the code that gave the error. Sorry would fit better than smile.

Edwin I didn’t want to be disrespectful. I definitely apricate your help, and I also know that people don’t understand how important is to enclose your string values or what value between <> represent. So I know you were trying to help. I am developer my self so I sometimes forget that things like that are not obvious to everybody.

Sorry if offended you it was definitely not my intention.

Apology accepted and incident forgotten. From the initial question it is impossible to tell the experience level of the writer. So sorry I stated the obvious too.

1 Like

I only need name of the argument and id of the value (when argument is from the list of options). So If somebody else stumbles on this topic it looks something like this:

wait Homey.flow.runFlowCardAction({
  uri: 'homey:device:<device id>',
  id: 'cloud_play_sonos_favorite',
  args: { 
     favorite: { id: "<option id>"}  
  }
});

Where “favorite” is name of the argument in this case and id is id of the favorite in options.

Have you thougt of using the api from Sonos?
Sonos Api. Here a link with a how to Sonos api how to

You can install it on a server(with Docker) and or a Rpi. With get requests you are able to control Sonos. It works great. This is a flow i use to play a radiostation or a playlist from Sonos favorites

This are some possibilities with the Sonos api

Favorites

It now has support for starting favorites. Simply invoke:

http://localhost:5005/living room/favorite/[favorite name]

and it will replace the queue with that favorite. Bear in mind that favorites may share name, which might give unpredictable behavior at the moment.

Playlist

Playing a Sonos playlist is now supported. Invoke the following:

http://localhost:5005/living room/playlist/[playlist name]

and it will replace the queue with the playlist and starts playing.

With a script i shuffle my radiostations and playlists every 2 weeks

This way you also have the ability to easily raise or lower the volume

Thanks for letting me know. However I can do all of that in script as well without need for the server. So for now I am ok with using script that runs on homey.

Can you share your script?

Sure. This is the script I written today to control speakers using fibaro keyfob. In it you have how to group, ungroup speakers, play favorites, increase and decrease volume and also how to mute and unmute.
I still didn’t do full testing of it but it should give you idea how to use homey script to control speakers. It combines usage of runFlowCardAction and setCapabilityValue. to achieve what I need. It am not using playlists but you just need to use playlist action card same way I am using play favorite action card. (fibaro keyfob part is removed as it is not necessary here).

//Devices.
const bedroomSpeaker = await Homey.devices.getDevice({id:`<here you put speaker device id>`});
const livingRoomSpeakers = await Homey.devices.getDevice({id:`<here you put speaker device id>`});
const kitchenSpeaker = await Homey.devices.getDevice({id:`<here you put speaker device id>`,});
const romeSpeaker = await Homey.devices.getDevice({id:`<here you put speaker device id>`});

//Volume.
const normalVolume = await Homey.logic.getVariable({id:`<varibale id>`});
const roamNormalVolume = await Homey.logic.getVariable({id:`<varibale id>`});
const volumeIncrement = await Homey.logic.getVariable({id:`<varibale id>`});

//Actions.
const actionPlayFavorit = `cloud_play_sonos_favorite`;
const actionJoinSpeaker = `cloud_join_player`;
const actionLeaveCurrentGroup = `cloud_leave_current_group`;

//Capabilities
const capabilitySetVolume = `volume_set`;
const capabilityMute = `volume_mute`;

//Sonos Favorites.
const favorites = await Homey.flow.getFlowCardAutocomplete({
  uri: `homey:device:${livingRoomSpeakers.id}`,
  id: actionPlayFavorit,
  name: `favorite`,
  query: ``,
  type: `flowcardaction`
});

const dasko_i_mladja = favorites[0];
const flow = favorites[1];
const indie_gold = favorites[2];
const jazz_london_radio = favorites[3];
const kexp_fm = favorites[4]; 
const punk_fm = favorites[5]; 


//Helper functions.

async function joinAllSpeakers()
{
  await runFlowCard(bedroomSpeaker, actionJoinSpeaker, livingRoomSpeakers);
  await runFlowCard(kitchenSpeaker, actionJoinSpeaker, livingRoomSpeakers);
  await runFlowCard(romeSpeaker, actionJoinSpeaker, livingRoomSpeakers);
}

async function ungroupAllSpeakers()
{
  await runFlowCard(bedroomSpeaker, actionLeaveCurrentGroup);
  await runFlowCard(kitchenSpeaker, actionLeaveCurrentGroup);
  await runFlowCard(romeSpeaker, actionLeaveCurrentGroup);
}

async function setNormalVolumeToAllSpeakers()
{
  await setSpeakerVolume(bedroomSpeaker, normalVolume.value);
  await setSpeakerVolume(kitchenSpeaker, normalVolume.value);
  await setSpeakerVolume(romeSpeaker, roamNormalVolume.value);
}

async function playFavoriteOnAllSpekaers(favorite)
{
  await setNormalVolumeToAllSpeakers();
  await runFlowCard(livingRoomSpeakers, actionPlayFavorit, favorite)
  await joinAllSpeakers();
}

async function joinSpeaker(device)
{
  await runFlowCard(device, actionJoinSpeaker, livingRoomSpeakers);
}

async function ungroupSpeaker(device)
{
  await runFlowCard(device, actionLeaveCurrentGroup);
}

async function decreaseSpeakerVolume(device)
{
  const volume = getSpeakerVolume(device) - volumeIncrement.value; 
  await setSpeakerVolume(device, volume);
}

async function decreaseAllSpeakersVolume()
{
  await decreaseSpeakerVolume(livingRoomSpeakers);
  await decreaseSpeakerVolume(bedroomSpeaker);
  await decreaseSpeakerVolume(romeSpeaker);
  await decreaseSpeakerVolume(kitchenSpeaker);
}

async function pauseAllSpeakers()
{
  await muteSpeakerVolume(livingRoomSpeakers, true)
  await muteSpeakerVolume(kitchenSpeaker, true)
  await muteSpeakerVolume(bedroomSpeaker, true)
  await muteSpeakerVolume(romeSpeaker, true)
}

async function increaseSpeakerVolume(device)
{
  const volume = getSpeakerVolume(device) + volumeIncrement.value; 
  await setSpeakerVolume(device, volume);
}

async function increaseAllSpeakersVolume()
{
  await increaseSpeakerVolume(livingRoomSpeakers);
  await increaseSpeakerVolume(bedroomSpeaker);
  await increaseSpeakerVolume(romeSpeaker);
  await increaseSpeakerVolume(kitchenSpeaker);
}

async function playAllSpeakers()
{
  await muteSpeakerVolume(livingRoomSpeakers, false)
  await muteSpeakerVolume(kitchenSpeaker, false)
  await muteSpeakerVolume(bedroomSpeaker, false)
  await muteSpeakerVolume(romeSpeaker, false)
}

function getSpeakerVolume(device)
{
  return device.capabilitiesObj.volume_set.value;
}

async function muteSpeakerVolume(device, mute)
{
  await Homey.devices.setCapabilityValue({ deviceId:device.id, capabilityId: capabilityMute, value: mute} )
}

async function setSpeakerVolume(device, value)
{
  await Homey.devices.setCapabilityValue({ deviceId:device.id, capabilityId: capabilitySetVolume, value: value} )
}

async function runFlowCard(device, actionId, argument = null)
{
  var argName = argument? await getFlowCardArgName(device, actionId) : null;

  return await Homey.flow.runFlowCardAction({
    uri: `homey:device:${device.id}`,
    id: actionId,
    args: argument? { [argName]: argument } : []
  });
}

async function getFlowCardArgName(device, actionId)
{
  const card =await Homey.flow.getFlowCardAction({
    uri: `homey:device:${device.id}`,
    id: actionId
  });

  return card.args[0].name;
}
1 Like