Getting Started with Spark Core and Photon (2015)
Chapter 8. Machine to Machine Communication
Most of the projects in this book rely on the Photon or Core either being completely independent (once programmed) or being controlled through a web browser or other user interface over the Internet.
In this chapter, you will learn how to make one Photon/Core speak to another, or even many other Photons or Cores.
Publish and Subscribe
In the previous chapter, when you used IFTTT to monitor the temperature on a Photon/Core, IFTTT only checked the temperature Variable on the Photon every 15 minutes. The reason they limit it in this way is that otherwise there could be potentially huge volumes of web and email traffic if everyone’s triggers were being monitored continuously. This type of checking on values is called “polling” and is not very efficient.
The alternative to polling in this situation would be to move the temperature testing to the Photon/Core, so that only when the level is exceeded would the device initiate communication by “publishing” a temperature-exceeded event to any services such as IFTTT or other Photons that are “subscribing” to that event.
In other words, a device or web service can subscribe to an event. That is, they can register an interest in that event, so that when that event is published, they are notified. This is a lot more efficient than continually polling and means that you don’t have to (in the case of IFTTT) wait for 15 minutes before you receive your notification.
Temperature Monitor Example
If you have made projects 10 and 12, and have two Photons, then you can try out this project using the exact same hardware as those two projects. You will just need to flash the new software onto the Photons.
In this case, there is one Photon acting as a thermometer (A) and two Photons with alarm bells attached (B and C). The intention is that these alarm bells will ring whenever Photon A publishes a “toohot” event. Previously (in their setup functions) Photons B and C will have registered an interest in the “toohot” notification by subscribing to “toohot." Once they do this, then the Spark cloud service knows that every time Photon A says its “toohot” Photons B and C need to be informed.
The code on Photon A will look something like the code below. This code is a little flawed. You will fix this later, so for now, don’t flash this onto your project 10 hardware, wait until it has been improved to solve excessive publishing.
#include "spark-dallas-temperature/spark-dallas-temperature.h"
#include "OneWire/OneWire.h"
int tempSensorPin = D2;
OneWire oneWire(tempSensorPin);
DallasTemperature sensors(&oneWire);
void setup() {
sensors.begin();
}
void loop() {
sensors.requestTemperatures();
float tempC = sensors.getTempCByIndex(0);
float tempF = tempC * 9.0 / 5.0 + 32.0;
if (tempF > 80.0) {
Spark.publish("toohot");
}
}
Most of this code is concerned with measuring the temperature, so refer back to project 10 for more information on that. The key line is highlighted and shows just how easy it is to publish an event.
The code for Photon B (the relay and bell hardware) is shown below.
int relayPin = D0;
void setup() {
pinMode(relayPin, OUTPUT);
Spark.subscribe("toohot", soundAlarm);
}
void loop() {
}
void soundAlarm(const char *event, const char *data) {
digitalWrite(relayPin, HIGH);
delay(200);
digitalWrite(relayPin, LOW);
}
The setup function contains the code to subscribe to the “toohot” event and specifies that the function soundAlarm should be called if such an event happens. You will notice that soundAlarm has some strange parameters, you will learn how these work later.
All though this pair of programs will work, there is (as I mentioned earlier) a slight flaw in the first program, and that is that the temperature is checked in the loop function and just so long as the temperature is over 80, the event will be published every time around the loop. So Photon A will be publishing the “toohot” event over and over again, as fast as it can while the temperature is over 80. This is not very socially responsible programming. It would be better, if we used two events. One event that would be published when the temperature exceeded 80 and a second event (“tempnormal”) that was published when the temperature had fallen back below say 78. The reason for a second threshold temperature of 78 rather than 80 is that if both threshold temperatures were 80, then at the point where the actual temperature was just around 80, the readings could alternate between 79 and 80 causing a flurry of both events.
The modified code for the thermometer end (Photon A) is listed below:
#include "spark-dallas-temperature/spark-dallas-temperature.h"
#include "OneWire/OneWire.h"
int tempSensorPin = D2;
OneWire oneWire(tempSensorPin);
DallasTemperature sensors(&oneWire);
boolean toohot = false;
void setup() {
sensors.begin();
}
void loop() {
sensors.requestTemperatures();
float tempC = sensors.getTempCByIndex(0);
float tempF = tempC * 9.0 / 5.0 + 32.0;
if (tempF > 80.0 && toohot == false) {
Spark.publish("toohot");
toohot = true;
}
if (tempF < 78.0 && toohot == true) {
Spark.publish("tempnormal");
toohot = false;
}
}
A new boolean variable toohot has been added to the program, so now, after reading the temperature, the “toohot” event is only published if the variable toohot is false, but the measured temperature has risen above 80. The variable toohot is then set to true, to prevent any further publishing of “toohot” until the temperature has fallen back below 78.
The second if statement takes care of this. If the measured temperature is below 78 and the toohot variable is currently true, then the “tempnormal” event is published and toohot set to false.
The code for the relay end of the project (Proton B) also needs to be modified to handle the new event (“tempnormal”).
int relayPin = D0;
void setup() {
pinMode(relayPin, OUTPUT);
Spark.subscribe("toohot", soundAlarm);
Spark.subscribe("tempnormal", cancelAlarm);
}
void loop() {
}
void soundAlarm(const char *event, const char *data) {
digitalWrite(relayPin, HIGH);
}
void cancelAlarm(const char *event, const char *data) {
digitalWrite(relayPin, LOW);
}
Now, setup subscribes to both “toohot” and “tempnormal” events. The “toohot” event will turn the alarm bell on and “tempnormal” will turn it off again -- just what we want.
If you want to try out these programs, the thermometer program is in the file ch_08_Temp_monitor_Pub and the relay program in ch_08_Temp_monitor_Sub. These will run on the hardware for Projects 10 and 12 respectively.
IFTTT and Publish/Subscribe
IFTTT is able to use the Spark Publish and Subscribe system. For example, you could arrange for an email to be sent when the temperature is exceeded not by monitoring a Variable as we did in Project 10, but by having an IFTTT recipe that subscribes to the “toohot” event would be much more efficient.
Create a new Recipe in IFTTT and then select Spark as the Trigger Source and instead of selecting “Monitor a Variable," select “New event published” as the trigger. Figure 8-2 shows the “Complete Trigger Fields” page.
Figure 8-2. Figure 8-2. Setting and Event Trigger
Specify “toohot” as the event name and leave the Event Contents field blank. Finally, select the Photon/Core that is going to be sending the events.
Select an Action channel of Email and then the Action of “Send me an Email," as you did in Project 11.
When the recipe is finished and active, you will receive just one email each time the event is triggered. Pinch your temperature sensor between your fingers to warm it up and test out this example.
Advanced Publish and Subscribe
The example above uses publish and subscribe at its most simple and for many applications that will be all that you need. However the Spark framework for publish and subscribe is actually capable of more advanced features.
Spark.publish
You can read the full documentation for the publish command here: ch_08_Temp_monitor_Pub.
In the example above, the only parameter that we supplied to publish was the name of the event. In fact, you can supply the following additional parameters:
§ data - This string could contain a a value to accompany the event. For example, the actual temperature could be sent, but this would need to be converted to a string first.
§ time to live - This value specifies the number of seconds that the event should be allowed to live before it is automatically removed. This prevents too many events accumulating in the system. At the time of writing, this parameter is ignored by the Spark Cloud, which discards events after the default of one minute.
§ public/private - The default is public, meaning that anyone can subscribe to these events. This allows for the possibility of interesting collaborative projects.
Spark.subscribe
The subscribe command also has an extra parameter, that follows the name of the handler function. This specifies the scope of the events that are to be subscribed to. You can use this third parameter to limit subscriptions to events coming from a particular Photon or Core by supplying the device’s ID like this:
Spark.subscribe("toohot", soundAlarm,
"55ff700649555344339432587");
You can also limit the subscription to just your own devices using the command below:
Spark.subscribe("toohot", soundAlarm, MY_DEVICES);
Subscription name matching is actually even more subtle than this. It will match against however much of the name you supply, so the line below will still receive the “toohot” events.
Spark.subscribe("too", soundAlarm);
This opens up the possibility of organizing events into related families based on the event name. Note that the maximum size of an event name is 64 characters.
Project 15. Magic Rope
This project was inspired by a video made by Leena Ventä-Olkkonen, Tobi Stockinger, Claudia Zuniga and Graham Dean that showed how a public installation could be made that would allow large maps of the world to be positioned in various public spaces in a city. These maps would have short lengths of rope sticking out of holes on the map corresponding to other cities around the world. The idea is then that the public at any one of these cities (let’s say London) could walk up to a rope at their map, pull on it and the paired rope in the other city (say New York) would be pulled into the map, attracting the attention of people near the installation. A gentle exchange of rope pulling could then occur across the world. You can view the original video here: https://www.youtube.com/watch?v=RzW9PJEpIhw
The original project was only ever developed as a concept and not actually implemented as a real installation. In this project, you will make a pair of “entangled” ropes that could be positioned in different cities. This could be used as a nice way of staying in touch with distant relatives.
Parts
To build this project, you are going to need two sets of each of the following parts in addition to two Photons/Cores.
Part |
Description |
Appendix A code |
R1 |
Slide Pot - Motorized |
M4 |
R2 |
220Ω resistor |
C1 |
Q1 |
2N3904 transistor |
C10 |
D1 |
1N4001 diode |
C11 |
Male-to-male jumper wire |
H4 |
|
Half-sized breadboard |
H5 |
|
Assorted hookup wire |
H3 |
|
Table 8-1. Parts Bin |
These components are all included in the Maker’s Kit apart from the motorized slide pots.
The sliding pots (potentiometers) used in this project are variable resistors intended for use in automated music mixing desks. You can adjust the resistance by sliding the “slider” up and down the length of the pot, but there is also a little motor that will move the slider using a toothed belt drive.
These motorized pots do not have leads attached to the pins, so this is one project where you will need to use a soldering iron and attach some leads to the pins.
Software
Both ends of this project have exactly the same software running on them and you can find it in the files P_15_Magic_Rope in the PHOTON_BOOK library.
int motorPin = D4;
int potPin = A0;
String thisID = Spark.deviceID();
boolean myTurn = true;
int maxPosn = 4000;
int minPosn = 3000;
void setup() {
Spark.subscribe("pulled", remoteRopePulled);
pinMode(motorPin, OUTPUT);
moveSliderTo(maxPosn);
}
void loop() {
int newLocalPosition = analogRead(potPin);
if (newLocalPosition < minPosn && myTurn) {
Spark.publish("pulled", thisID);
myTurn = false;
}
}
void remoteRopePulled(const char *event, const char *data)
{
String dataS = String(data);
// ignore messages from yourself
if (dataS.indexOf(thisID) == -1)
{
moveSliderTo(maxPosn);
myTurn = true;
}
}
void moveSliderTo(int newPosition) {
while (analogRead(potPin) < newPosition) {
digitalWrite(motorPin, HIGH);
};
digitalWrite(motorPin, LOW);
}
The original of this file has some extra commands commented out, that can be used to debug the project if the events don’t seem to be getting through. See the comments in the original program if you need to use them.
The program starts by defining the two pins to be used. A0 for the voltage output of the potentiometer, which will be 0V if the rope is fully pulled out and 3.3V if the rope is fully pulled in.
The boolean variable myTurn is used to keep track of whose turn it is to pull on the rope. If myTurn is set to true then it is this device’s turn to have its rope pulled.
Both ends of this project both publish and subscribe to the same event, so the variable thisID is needed so that the device knows its own ID and can disregard its own publish events, only reacting to events coming from the other Photon/Core.
The constant maxPosn is the analog input reading at which the sliding pot is at the position where the rope is fully pulled in. This is set slightly lower than the theoretical maximum analog input value of 4095 to allow for any inaccuracy in the analog readings.
The second constant minPosn is equivalent to about three quarters of the way pulled in and this is the threshold at which a “pulled” event will be published.
The setup function makes the necessary subscription to “pulled” associating it with the function remoteRopePulled. It also calls the function moveSliderTo to position the slider at its fully pulled in position, ready to be pulled out.
The loop function reads the analog input to find the newLocalPosition. If this is less than the minPosn constant and its this device’s turn to move, then the “pulled” event is published with this device’s ID as its parameter.
In the situation where the rope has been pulled on the other Photon/Core the function remoteRobePulled will be called. This function will be supplied with the ID of the device where the rope was pulled, so that it can be compared with thisID, the ID of the receiving Photon by searching for the string of characters in thisID within the ID passed in data.
If the event has come from a remote Photon/Core, then the slider is pulled fully in and myTurn flipped over to true.
The function moveSliderTo handles all automated movement of the slider. In fact it can only pull the slider in, but then you can’t push rope, so that’s fine. The function takes the new position as a parameter and keeps power supplied to the motor until such time as the measured position is no longer less than the desired position.
Hardware
The breadboard layout for this project is shown in Figure 8-5.
Figure 8-5. Figure 8-5. The Magic Rope Breadboard Layout
The motorized pots are actually stereo devices but we only need one channel for this project. This means that there are some pins that you do not need to connect leads to.
Before assembling the breadboard, you will need to solder some wires to the motorized pot legs. The motor leads are easily identified, and if you are using the same motorized pot as me, attach a red lead to the bottom-most motor lead (as shown in Figure 8-6) and a black lead to the other motor lead. All leads need to be about 6 inches long to comfortably reach the breadboard.
At the far end of the motor, attach a red lead to the right-most lead. This is the lead that will go to 3.3V on the breadboard. Attach a yellow or orange lead to the right-most pin at the motor end of the pot. This lead is the slider of the pot that will connect to A0 on the Photon/Core. Finally, connect a brown or blue lead next to this yellow lead. This will connect to GND on the breadboard.
TRANSISTORS
The motors of the motorized pots use upwards of 100mA which is far too much for a Photon/Core digital output which has an upper limit of about 20mA. To allow the digital output to turn the motor on and off a transistor is used.
Figure 8-7 shows the schematic diagram for the project.
Figure 8-7. Figure 8-7. Using a Transistor to Control a Motor
You can think of the transistor (Q1) as a kind of digital switch. That can use a small current to control a much bigger current.
When D4 is HIGH, a small current flows through R2 and the transistor to GND which stimulates a much higher current to flow from Vin (5V) through the motor, through the transistor and to GND.
Finally connect everything up as shown in Figure 8-5 paying special attention to the transistor and diode, to make sure they are the right way around. The diode has a stripe at one end that should be towards the top of the breadboard and the transistor has one curved side.
Driving motors can result in voltage spikes and the diode protects the Photon/Core from accidental damage resulting from these spikes.
Using the Project
To use the project, power up both ends of the magic rope. After the Photon/Core has finished starting up (flashing green light) the motor should activate on both ends of the project, pulling the slide up to one end of the track.
Pull on one of the strings and then the other. When you pull on the second string the first string should be pulled back automatically.
You could find a nice wooden box for this project, drilling a hole for the string to emerge at one end and a hole for the USB lead at the other.
Summary
This is the final chapter containing projects. In the next chapter various more advanced topics will be covered.