Inter-device comms in Product using publish/subscribe


#1

Hi Folks,

I haven’t been able to find a recently active discussion on this topic, so I figured I’d spin up a new thread in the hopes of getting an answer that’s up to date.

My issue is fairly straightforward (as I see it):

  • I have a product.
  • Each of my customers will have a number of electrons (~= 10 devices), and this set of devices will all be owned by the customer’s particle account.
  • All of that customer’s devices are running the same firmware and are members of the same product.
  • I want to create web-hooks that will only be triggered by publishes from the devices owned by that customer.
  • I want each of the devices owned by that customer to be able to publish messages to only the other devices owned by that customer.
  • I want a web-app to do the same as above. (which it looks like is not possible directly, my thoughts on a workaround at the moment are to use one of the customer’s devices as a relay, and have a function call that will trigger that device to do the publish for me?)
  • It is critical to the proper operation of the system that events published by one customer are only received by that customer’s webhooks and devices.

I currently have a prototype system running for my first customer which uses the product-level webhooks and public events, but I will be needing to offer support for multiple customers (each with their own set of ~= 10 electrons) in the next few months.

I’ve had a look at some of the contributions by @Dave and @rickkas7. From my reading of the other topics around here, I get the general sense that what I want to do isn’t really possible under the current infrastructure. Does anyone have suggestions on the workaround that will get me closest to the behaviour I want?

Cheers in advance,
DKW


#2

Hi @DKW_Engineering,

Simply use your customer’s access token to create a webhook for them in their account. It’ll only see events from their owned product devices. :slight_smile:

If you want their devices to publish events that only the other customer’s devices can see, simply publish them as private.

Thanks,
David


#3

Oh wow, it’s actually that simple? Awesome!!! I was getting pretty worried after reading all the other product+publish related posts.

Will the same thing work for web-based event publishes? i.e. If my web-app is using the customer’s access token and it does a private publish, all the product devices will see that publish?


#4

Heya @DKW_Engineering,

Yup! If you use your customer’s account to publish privately, their owned devices should be able to subscribe privately to that event topic.

Thanks,
David


#5

Hey David,

I’ve just been doing a bit of testing, and have found some extra info. If you could just clarify that this is all ‘working as intended’ that would be great.

Question 1:

Particle.publish("EvtName", "EvtData", MY_DEVICES);

Will compile, and publishes a public event. A subscription to “EvtName” using MY_DEVICES will not receive those events. Is that meant to be the case?

Question 2:

Particle.subscribe("EvtName", &EvtHandler, PRIVATE);

throws a compile error. Why?

Question 3:

When I use the following in combination:

Particle.subscribe("EvtName", &EvtHandler, MY_DEVICES);
Particle.publish("EvtName", "EvtData", PRIVATE);

The device receives it’s own event publish. Is there a simple way to block this to prevent double-counting?

Question 4:

When I’m subscribing to the event using the MY_DEVICES flag, e.g.

Particle.subscribe("EvtName", &EvtHandler, MY_DEVICES);

I can send a ‘private’ event from the Events pane of the web console when logged in as the device owner and it works. If I send a ‘private’ event from any of the web console event panes logged in as the product owner, it doesn’t work. Is this as intended? I haven’t tested using the web API yet.


#6

Hi @DKW_Engineering,

“MY_DEVICES” is a keyword for the subscribe command, and “PRIVATE” is a keyword for the publish command, they’re not interchangeable as far as I know. So for question 1, it is working as intended, but you’re using the wrong keywords. Same is true for Question 2.

Question 3 you’re using the keywords correctly. If you don’t want to receive your own events, then publish before you subscribe, or use different keywords.

Question 4 – yes, as a user / customer you are scoped to devices that you own, as a product creator you are scoped to all devices in that product.

Thanks,
David


#7

Thanks so much for the responses.

Makes sense, but that Question 3 answer is really really going to hurt me.

I’m using that publish to maintain a common variable pool across all 10 devices a customer owns. An example would be:

Device A: I have received a button press, increment NUM_COFFEE by 1.
Device A: Publish a message telling all the other devices to increment their own copy of NUM_COFFEE.
Device A: Oooh, a message telling me to increment NUM_COFFEE, I'd better do that.

I need the initial increment to be done internally, so that even if the cloud’s not up, device A will increment it’s coffee counter and at-least will behave locally consistently. For system reasons, I also need to send increment/decrement messages, not absolute value ones.

I was looking into it, and the only fix I can think of is to append the core ID to the event name (since the Particle.subscribe() callback only takes the event name and event data as arguments). That would mean each device would receive knowledge about the core that’s published the event and could filter out their own publishes. The question is then how do I make that play nice with and cloud API published events I want to add into the mix? It just feels like a hack on top of a hack which makes me uncomfortable. Am I missing something, or is that all I can do?


#8

Hi @DKW_Engineering,

The pub / sub system lets you publish data on topics scoped to user accounts and products, so it really depends on how you want to use it and what you want to accomplish.

For example, I could see you avoiding that issue with the following change:

  • each device publishes its own “coffee number”

Particle.publish("coffees/MY_DEVICE_ID", String(num_coffees));

  • when you want a total, you ask for a coffee count:

Particle.publish("count_coffees");

  • and then all devices can publish their counts, and all devices can count all the coffee reports that were published back from all the devices, etc, etc.

That way you’re adding up individual states to get the total value, instead of trying to make sure your action is atomic and fires exactly once, which is a little easier to do. :slight_smile:

Thanks,
David


#9

Awesome, thanks @Dave.

I was looking into it, but having to do that operation across all 10 electrons each time any of them needed to know one of my variables was going to kill me on data. Just so anyone else who stumbles across this thread knows, this is how I ended up solving it.

In the end, this is what I settled on, which leverages the fact that subscriptions are only head-matched:

The basic behaviour is that now any device that publishes a message uses a defined SETVAR_HEADER string, but appends to that a “:” character followed by the device’s coreID.

Any time a published event is received, the device checks for the colon. If there’s a colon and the end of the event name matches our own ID, then just ignore the event. If the end of the event name doesn’t match ourselves, trim off anything after the colon and then pass the string through to the event parser. If there’s no colon, then just call the event parser straight up.

That way, I can use just the SETVAR_HEADER string from my external events coming in through the particle cloud API so things are a easier to read, but no device will be responding to it’s own event publications.

At setup time I cache my ID into a global string variable:

String MyID = "";

void setup() {
  /* Other setup code ...*/
  MyID = Particle.deviceID();
}

Each device subscribes using:

Particle.subscribe(SETVAR_HEADER , SetVariableEventHandler, MY_DEVICES);

When any of the devices publishes it does it like so, the only requirement is that there are no “:” characters in the defined header string:

String evtNameString = SETVAR_HEADER;
evtNameString += ":";
evtNameString += MyID;
Particle.publish(evtNameString, data, PRIVATE);

The subscription call back does a pretty simple check:

void SetVariableEventHandler(const char* name, const char* data){
  String nameString = name;
  int colonLoc = nameString.indexOf(':');
  // Is there a colon with something after it?
  if(colonLoc != -1 && colonLoc != (nameString.length() - 1))
  {
    if(nameString.endsWith(MyID)) return;
    nameString = nameString.remove(colonLoc);
  }

  if(nameString == SETVAR_HEADER) SetVarParser(data);
}

It’s easy to read with external subscribers which makes debugging a lot easier, it keeps my data down, and it works a treat!