In this article by Gastón C. Hillar , author of book Internet of Things with Python, we will take advantage of many cloud services to publish and visualize data collected for sensors and to establish bi-directional communications between Internet-connected things.
(For more resources related to this topic, see here.)
Sending and receiving data in real-time through Internet with PubNub
We want to send and receive data in real-time through the Internet and a RESTful API is not the most appropriate option to do this. Instead, we will work with a publish/subscribe model-based on a protocol that is lighter than the HTTP protocol. Specifically, we will use a service-based on the MQ Telemetry Transport (MQTT) protocol.
The MQTT protocol is a machine-to-machine (M2M) connectivity protocol. MQTT is a lightweight messaging protocol that runs on top of the TCP/IP protocol and works with a publish-subscribe mechanism. It is possible for any device to subscribe to a specific channel (also known as topic) and receive all the messages published to this channel. In addition, the device can publish message to this or other channel. The protocol is becoming very popular in IoT and M2M projects. You can read more about the MQTT protocol in the following Webpage: http://mqtt.org.
PubNub provides many cloud-based services and one of them allows us to easily stream data and signal any device in real-time, working with the MQTT protocol under the hoods. We will take advantage of this PubNub service to send and receive data in real-time through Internet and make it easy to control our Intel Galileo Gen 2 board through the Internet. As PubNub provides a Python API with high quality documentation and examples, it is extremely easy to use the service in Python. PubNub defines itself as the global data stream network for IoT, Mobile and Web applications. You can read more about PubNub in its Webpage: http://www.pubnub.com.
In our example, we will take advantage of the free services offered by PubNub and we won't use some advanced features and additional services that might empower our IoT project connectivity requirements but also require a paid subscription.
PubNub requires us to sign up and create an account with a valid e-mail and a password before we can create an application within PubNub that allows us to start using their free services. We aren't required to enter any credit card or payment information. If you already have an account at PubNub, you can skip the next step.
Once you created your account PubNub will redirect you to the admin portal that lists your PubNub applications. It is necessary to generate your PubNub publish and subscribe keys in order to send and receive messages in the network. A new pane will represent the application in the admin portal. The following screenshot shows the Temperature Control application pane in the PubNub admin portal.
Click on the Temperature Control pane and PubNub will display the Demo Keyset pane that has been automatically generated for the application. Click on this pane and PubNub will display the publish, subscribe and secret keys. We must copy and paste each of the keys to use them in our code that will publish messages and subscribe to them. The following screenshot shows the prefixes for the keys and the remaining characters have been erased in the image.
In order to copy the secret key, you must click on the eye icon at the right-hand side of the key and PubNub will make all the characters visible.
Now, we will use pip installer to install PubNub Python SDK 3.7.6. We just need to run the following command in the SSH terminal to install the package. Notice that it can take a few minutes to complete the installation.
pip install pubnub
The last lines for the output will indicate that the pubnub package has been successfully installed. Don't worry about the error messages related to building wheel and the insecure platform warning.
Downloading pubnub-3.7.6.tar.gz
Collecting pycrypto>=2.6.1 (from pubnub)
Downloading pycrypto-2.6.1.tar.gz (446kB)
100% |################################| 446kB 25kB/s
Requirement already satisfied (use --upgrade to upgrade): requests>=2.4.0 in /usr/lib/python2.7/site-packages (from pubnub)
Installing collected packages: pycrypto, pubnub
Running setup.py install for pycrypto
Installing collected packages: pycrypto, pubnub
Running setup.py install for pycrypto
Running setup.py install for pubnub
Successfully installed pubnub-3.7.6 pycrypto-2.6.1
We will use iot_python_chapter_08_03.py (you will find this in the code bundle) code as a baseline to add new features that will allow us to perform the following actions with PubNub messages sent to a specific channel from any device that has a Web browser:
Rotate the servo's shaft to display a temperature value in degrees Fahrenheit received as part of the message
Display a line of text received as part of the message at the bottom of the OLED matrix
We will use the recently installed pubnub module to subscribe to a specific channel and run code when we receive messages in the channel. We will create a MessageChannel class to represent the communications channel, configure the PubNub subscription and declare the code for the callbacks that are going to be executed when certain events are fired. The code file for the sample is iot_python_chapter_09_02.py (you will find this in the code bundle). Remember that we use the code file iot_python_chapter_08_03.py (you will find this in the code bundle)as a baseline, and therefore, we will add the class to the existing code in this file and we will create a new Python file. Don't forget to replace the strings assigned to the publish_key and subscribe_key local variables in the __init__ method with the values you have retrieved from the previously explained PubNub key generation process.
import time
from pubnub import Pubnub
class MessageChannel:
command_key = "command"
def __init__(self, channel, temperature_servo, oled):
self.temperature_servo = temperature_servo
self.oled = oled
self.channel = channel
# Publish key is the one that usually starts with the
"pub-c-" prefix
# Do not forget to replace the string with your publish
key
publish_key = "pub-c-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# Subscribe key is the one that usually starts with the
"sub-c" prefix
# Do not forget to replace the string with your subscribe key
subscribe_key = "sub-c-xxxxxxxx-xxxx-xxxx-xxxx-
xxxxxxxxxxxx"
self.pubnub = Pubnub(publish_key=publish_key,
subscribe_key=subscribe_key)
self.pubnub.subscribe(channels=self.channel,
callback=self.callback,
error=self.callback,
connect=self.connect,
reconnect=self.reconnect,
disconnect=self.disconnect)
def callback(self, message, channel):
if channel == self.channel:
if self.__class__.command_key in message:
if message[self.__class__.command_key] ==
"print_temperature_fahrenheit":
self.temperature_servo.print_temperature
(message["temperature_fahrenheit"])
elif message[self.__class__.command_key] ==
"print_information_message":
self.oled.print_line(11, message["text"])
print("I've received the following message:
{0}".format(message))
def error(self, message):
print("Error: " + str(message))
def connect(self, message):
print("Connected to the {0} channel".
format(self.channel))
print(self.pubnub.publish(
channel=self.channel,
message="Listening to messages in the Intel Galileo
Gen 2 board"))
def reconnect(self, message):
print("Reconnected to the {0} channel".
format(self.channel))
def disconnect(self, message):
print("Disconnected from the {0} channel".
Format(self.channel))
The MessageChannel class declares the command_key class attribute that defines the key string that defines what the code will understand as the command. Whenever we receive a message that includes the specified key string, we know that the value associated to this key in the dictionary will indicate the command that the message wants the code running in the board to be processed. Each command requires additional key-value pairs that provide the necessary information to execute the command.
We have to specify the PubNub channel name, the TemperatureServo instance and the Oled instance in the channel, temperature_servo and oled required arguments. The constructor, that is, the __init__ method, saves the received arguments in three attributes with the same names. The channel argument specifies the PubNub channel to which we are going to subscribe to listen to the messages that other devices send to this channel. We will also publish messages to this channel, and therefore, we will be both a subscriber and a publisher for this channel.
In this case, we will only subscribe to one channel. However, it is very important to know that we are not limited to subscribe to a single channel, we might subscribe to many channels.
Then, the constructor declares two local variables: publish_key and subscribe_key. These local variables save the publish and subscribe keys we had generated with the PubNub admin portal. Then, the code creates a new Pubnub instance with publish_key and subscribe_key as the arguments, and saves the reference for the new instance in the pubnub attribute. Finally, the code calls the subscribe method for the new instance to subscribe to data on the channel saved in the channel attribute. Under the hoods, the subscribe method makes the client create an open TCP socket to the PubNub network that includes an MQTT broker and starts listening to messages on the specified channel. The call to this method specifies many methods declared in the MessageChannel class for the following named arguments:
callback: Specifies the function that will be called when there is a new message received from the channel
error: Specifies the function that will be called on an error event
connect: Specifies the function that will be called when a successful connection is established with the PubNub cloud
reconnect: Specifies the function that will be called when a successful re-connection is completed with the PubNub cloud
disconnect: Specifies the function that will be called when the client disconnects from the PubNub cloud
This way, whenever one of the previously enumerated events occur, the specified method will be executed. The callback method receives two arguments: message and channel. First, the method checks whether the received channel matches the value in the channel attribute. In this case, whenever the callback method is executed, the value in the channel argument will always match the value in the channel attribute because we just subscribed to one channel. However, in case we subscribe to more than one channel, is is always necessary to check the channel in which the message was sent and in which we are receiving the message.
Then, the code checks whether the command_key class attribute is included in the message dictionary. If the expression evaluates to True, it means that the message includes a command that we have to process. However, before we can process the command, we have to check which is the command, and therefore, it is necessary to retrieve the value associated with the key equivalent to the command_key class attribute. The code is capable of running code when the value is any of the following two commands:
print_temperature_fahrenheit: The command must specify the temperature value expressed in degrees Fahrenheit in the value of the temperature_fahrenheit key. The code calls the self.temperature_servo.print_temperature method with the temperature value retrieved from the dictionary as an argument. This way, the code moves the servo's shaft-based on the specified temperature value in the message that includes the command.
print_information_message: The command must specify the line of text that has to be displayed at the bottom of the OLED matrix in the value of the print_information_message key. The code calls the self.oled.print_line method with 11 and the text value retrieved from the dictionary as arguments. This way, the code displays the text received in the message that includes the command at the bottom of the OLED matrix.
No matter whether the message included a valid command or not, the method prints the raw message that it received in the console output.
The connect method prints a message indicating that a connection has been established with the channel. Then, the method prints the results of calling the self.pubnub.publish method that publishes a message in the channel name saved in self.channel with the following message: "Listening to messages in the Intel Galileo Gen 2 board". In this case, the call to this method runs with a synchronous execution. We will work with asynchronous execution for this method in our next example.
Currently, we are already subscribed to this channel, and therefore, we will receive the previously published message and the callback method will be executed with this message as an argument. However, as the message doesn't include the key that identifies a command, the code in the callback method will just display the received message and it won't process any of the previously analyzed commands.
The other methods declared in the MessageChannel class just display information to the console output about the event that has occurred.
Now, we will use the previously coded MessageChannel class to create a new version of the __main__ method that uses the PubNub cloud to receive and process commands. The new version doesn't rotate the servo's shaft when the ambient temperature changes, instead, it will do this when it receives the appropriate command from any device connected to PubNub cloud. The following lines show the new version of the __main__ method. The code file for the sample is iot_python_chapter_09_02.py (you will find this in the code bundle).
if __name__ == "__main__":
temperature_and_humidity_sensor =
TemperatureAndHumiditySensor(0)
oled = TemperatureAndHumidityOled(0)
temperature_servo = TemperatureServo(3)
message_channel = MessageChannel("temperature",
temperature_servo, oled)
while True:
temperature_and_humidity_sensor.
measure_temperature_and_humidity()
oled.print_temperature(
temperature_and_humidity_sensor
.temperature_fahrenheit,
temperature_and_humidity_sensor.temperature_celsius)
oled.print_humidity(
temperature_and_humidity_sensor.humidity)
print("Ambient temperature in degrees Celsius: {0}".
format(temperature_and_humidity_sensor
.temperature_celsius))
print("Ambient temperature in degrees Fahrenheit: {0}".
format(temperature_and_humidity_sensor
.temperature_fahrenheit))
print("Ambient humidity: {0}".
format(temperature_and_humidity_sensor.humidity))
# Sleep 10 seconds (10000 milliseconds)
time.sleep(10)
The highlighted line creates an instance of the previously coded MessageChannel class with "temperature", temperature_servo and oled as the arguments. The constructor will subscribe to the temperature channel in the PubNub cloud, and therefore, we must send the messages to this channel in order to send the commands that the code will process with an asynchronous execution. The loop will read the values from the sensor and print the values to the console similar to the previous version of the code, and therefore, we will have code running in the loop and listening to the messages in the temperature channel in the PubNub cloud. We will start the example later because we want to subscribe to the channel in the PubNub debug console before we run the code in the board.
Publishing messages with commands through the PubNub cloud
Now, we will take advantage of the PubNub console to send messages with commands to the temperature channel and make the Python code running on the board process these commands. In case you have logged out of PubNub, login again and click on the Temperature Control pane in the Admin Portal. PubNub will display the Demo Keyset pane.
Click on the Demo Keyset pane and PubNub will display the publish, subscribe and secret keys. This way, we select the keyset that we want to use for our PubNub application.
Click on Debug Console on the sidebar located the left-hand side of the screen. PubNub will create a client for a default channel and subscribe to this channel using the secret keys we have selected in the previous step. We want to subscribe to the temperature channel, and therefore, enter temperature in the Default Channel textbox within a pane that includes the Add client button at the bottom. Then, click on Add client and PubNub will add a new pane with a random client name as a title and the channel name, temperature, in the second line. PubNub makes the client subscribe to this channel and we will be able to receive messages published to this channel and send messages to this channel. The following picture shows the pane for the generated client named Client-ot7pi, subscribed to the temperature channel. Notice that the client name will be different when you follow the explained steps.
The client pane displays the output generated when PubNub subscribed the client to the channel. PubNub returns a formatted response for each command. In this case, it indicates that the status is equal to Subscribed and the channel name is temperature.
[1,"Subscribed","temperature"]
Now, it is time to start running the example in the Intel Galileo Gen 2 board. The following line will start the example in the SSH console.
python iot_python_chapter_09_02.py
After you run the example, go to the Web browser in which you are working with the PubNub debug console. You will see the following message listed in the previously created client:
"Listening to messages in the Intel Galileo Gen 2 board"
The Python code running in the board published this message, specifically, the connect method in the MessageChannel class sent this message after the application established a connection with the PubNub cloud. The following picture shows the message listed in the previously created client. Notice that the icon at the left-hand side of the text indicates it is a message. The other message was a debug message with the results of subscribing to the channel.
At the bottom of the client pane, you will see the following text and the SEND button at the right-hand side:
{"text":"Enter Message Here"}
Now, we will replace the previously shown text with a message. Enter the following JSON code and click SEND:
{"command":"print_temperature_fahrenheit", "temperature_fahrenheit": 50 }
The text editor where you enter the message has some issues in certain browsers. Thus, it is convenient to use your favorite text editor to enter the JSON code, copy it and then past it to replace the text that is included by default in the text for the message to be sent.
After you click SEND, the following lines will appear in the client log. The first line is a debug message with the results of publishing the message and indicates that the message has been sent. The formatted response includes a number (1 message), the status (Sent) and a time token. The second line is the message that arrives to the channel because we are subscribed to the temperature channel, that is, we also receive the message we sent.
[1,"Sent","14594756860875537"]
{
"command": "print_temperature_fahrenheit",
"temperature_fahrenheit": 50
}
The following picture shows the messages and debug messages log for the PubNub client after we clicked the SEND button.
After you publish the previous message, you will see the following output in the SSH console for the Intel Galileo Gen 2 board. You will notice the servo's shaft rotates to 50 degrees.
I've received the following message: {u'command': u'print_temperature_fahrenheit', u'temperature_fahrenheit': 50}
Now, enter the following JSON code and click SEND:
{"command":"print_information_message", "text": "Client ready"}
After you click SEND, the following lines will appear in the client log. The first line is a debug message with the previously explained formatted response with the results of publishing the message and indicates that the message has been sent. The second line is the message that arrives to the channel because we are subscribed to the temperature channel, that is, we also receive the message we sent.
[1,"Sent","14594794434885921"]
{
"command": "print_information_message",
"text": "Client ready"
}
The following picture shows the messages and debug messages log for the PubNub client after we clicked the SEND button.
After you publish the previous message, you will see the following output in the SSH console for the Intel Galileo Gen 2 board. You will see the following text displayed at the bottom of the OLED matrix: Client ready.
I've received the following message: {u'text': u'Client ready', u'command': u'print_information_message'}
When we published the two messages with the commands, we have definitely noticed a problem. We don't know whether the command was processed or not in the code that is running on the IoT device, that is, in the Intel Galileo Gen 2 board. We know that the board started listening messages in the temperature channel, but we don't receive any kind of response from the IoT device after the command has been processed.
Working with bi-directional communications
We can easily add a few lines of code to publish a message to the same channel in which we are receiving messages to indicate that the command has been successfully processed. We will use our previous example as a baseline and we will create a new version of the MessageChannel class. The code file was iot_python_chapter_09_02.py (you will find this in the code bundle). Don't forget to replace the strings assigned to the publish_key and subscribe_key local variables in the __init__ method with the values you have retrieved from the previously explained PubNub key generation process. The following lines show the new version of the MessageChannel class that publishes a message after a command has been successfully processed. The code file for the sample is iot_python_chapter_09_03.py (you will find this in the code bundle).
import time
from pubnub import Pubnub
class MessageChannel:
command_key = "command"
successfully_processed_command_key =
"successfully_processed_command"
def __init__(self, channel, temperature_servo, oled):
self.temperature_servo = temperature_servo
self.oled = oled
self.channel = channel
# Do not forget to replace the string with your publish key
publish_key = "pub-c-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# Subscribe key is the one that usually starts with the
"sub-c" prefix
# Do not forget to replace the string with your subscribe key
subscribe_key = "sub-c-xxxxxxxx-xxxx-xxxx-xxxx-
xxxxxxxxxxxx"
self.pubnub = Pubnub(publish_key=publish_key,
subscribe_key=subscribe_key)
self.pubnub.subscribe(channels=self.channel,
callback=self.callback,
error=self.callback,
connect=self.connect,
reconnect=self.reconnect,
disconnect=self.disconnect)
def callback_response_message(self, message):
print("I've received the following response from PubNub
cloud: {0}".format(message))
def error_response_message(self, message):
print("There was an error when working with the PubNub cloud:
{0}".format(message))
def publish_response_message(self, message):
response_message = {
self.__class__.successfully_processed_command_key:
message[self.__class__.command_key]}
self.pubnub.publish(
channel=self.channel,
message=response_message,
callback=self.callback_response_message,
error=self.error_response_message)
def callback(self, message, channel):
if channel == self.channel:
print("I've received the following message: {0}".format(message))
if self.__class__.command_key in message:
if message[self.__class__.command_key] == "print_temperature_fahrenheit":
self.temperature_servo.print_temperature
(message["temperature_fahrenheit"])
self.publish_response_message(message)
elif message[self.__class__.command_key] ==
"print_information_message":
self.oled.print_line(11, message["text"])
self.publish_response_message(message)
def error(self, message):
print("Error: " + str(message))
def connect(self, message):
print("Connected to the {0} channel".
format(self.channel))
print(self.pubnub.publish(
channel=self.channel,
message="Listening to messages in the Intel Galileo
Gen 2 board"))
def reconnect(self, message):
print("Reconnected to the {0} channel".
format(self.channel))
def disconnect(self, message):
print("Disconnected from the {0} channel".
format(self.channel))
The highlighted lines in the previous code for the new version of the MessageChannel class show the changes we made in the code. First, the code declares the successfully_processed_command_key class attribute that defines the key string that defines what the code will use as a successfully processed command key in a response message published to the channel. Whenever we publish a message that includes the specified key string, we know that the value associated to this key in the dictionary will indicate the command that the board has successfully processed.
The code declares the following three new methods:
callback_response_message: This method will be used as the callback that will be executed when a successfully processed command response message is published to the channel. The method just prints the formatted response that PubNub returns when a message has been successfully published in the channel. In this case, the message argument doesn't hold the original message that has been published, it holds the formatted response. We use message for the argument name to keep consistency with the PubNub API.
error_response_message: This method will be used as the callback that will be executed when an error occurs when trying to publish a successfully processed command response message to the channel. The method just prints the error message that PubNub returns when a message hasn't been successfully published in the channel.
publish_response_message: This method receives the message with the command that was successfully processed in the message argument. The code creates a response_message dictionary with the successfully_processed_command_key class attribute as the key and the value of the key specified in the command_key class attribute for the message dictionary as the value. Then, the code calls the self.pubnub.publish method to publish the response_message dictionary to the channel saved in the channel attribute. The call to this method specifies self.callback_response_message as the callback to be executed when the message is successfully published and self.error_response_message as the callback to be executed when an error occurred during the publishing process. When we specify a callback, the publish method works with an asynchronous execution, and therefore, the execution is non-blocking. The publication of the message and the callbacks that are specified will run in a different thread.
Now, the callback method defined in the MessageChannel class adds a call to the publish_response_message method with the message that included the command that has been successfully processed (message) as an argument. As previously explained, the publish_response_message method is non-blocking and will return immediately while the successfully processed message is published in another thread.
Now, it is time to start running the example in the Intel Galileo Gen 2 board. The following line will start the example in the SSH console.
python iot_python_chapter_09_03.py
After you run the example, go to the Web browser in which you are working with the PubNub debug console. You will see the following message listed in the previously created client:
"Listening to messages in the Intel Galileo Gen 2 board"
Enter the following JSON code and click SEND:
{"command":"print_temperature_fahrenheit", "temperature_fahrenheit": 90 }
After you click SEND, the following lines will appear in the client log. The last message was published by the board to the channel indicates that the print_temperature_fahrenheit command has been successfully processed.
[1,"Sent","14595406989121047"]
{
"command": "print_temperature_fahrenheit",
"temperature_fahrenheit": 90
}
{
"successfully_processed_command": "print_temperature_fahrenheit"
}
The following picture shows the messages and debug messages log for the PubNub client after we clicked the SEND button:
After you publish the previous message, you will see the following output in the SSH console for the Intel Galileo Gen 2 board. You will notice the servo's shaft rotates to 90 degrees. The board also receives the successfully processed command message because it is subscribed to the channel in which the message has been published.
I've received the following message: {u'command': u'print_temperature_fahrenheit', u'temperature_fahrenheit': 90}
I've received the following response from PubNub cloud: [1, u'Sent', u'14595422426124592']
I've received the following message: {u'successfully_processed_command': u'print_temperature_fahrenheit'}
Now, enter the following JSON code and click SEND:
{"command":"print_information_message", "text": "2nd message"}
After you click Send, the following lines will appear in the client log. The last message published by the board to the channel indicates that the print_information_message command has been successfully processed.
[1,"Sent","14595434708640961"]
{
"command": "print_information_message",
"text": "2nd message"
}
{
"successfully_processed_command": "print_information_message"
}
The following picture shows the messages and debug messages log for the PubNub client after we clicked the SEND button:
After you publish the previous message, you will see the following output in the SSH console for the Intel Galileo Gen 2 board. You will see the following text displayed at the bottom of the OLED matrix: 2nd message. The board also receives the successfully processed command message because it is subscribed to the channel in which the message has been published.
I've received the following message: {u'text': u'2nd message', u'command': u'print_information_message'}
2nd message
I've received the following response from PubNub cloud: [1, u'Sent', u'14595434710438777']
I've received the following message: {u'successfully_processed_command': u'print_information_message'}
We can work with the different SDKs provided by PubNub to subscribe and publish to a channel. We can also make different IoT devices talk to themselves by publishing messages to channels and processing them. In this case, we just created a few commands and we didn't add detailed information about the device that has to process the command or the device that has generated a specific message. A more complex API would require commands that include more information and security.
Publishing messages to the cloud with a Python PubNub client
So far, we have been using the PubNub debug console to publish messages to the temperature channel and make the Python code running in the Intel Galileo Gen 2 board process them. Now, we are going to code a Python client that will publish messages to the temperature channel. This way, we will be able to design applications that can talk to IoT devices with Python code in the publisher and in the subscriber devices.
We can run the Python client on another Intel Galileo Gen 2 board or in any device that has Python 2.7.x installed. In addition, the code will run with Python 3.x. For example, we can run the Python client in our computer. We just need to make sure that we install the pubnub module we have previously installed with pip in the Python version that is running in the Yocto Linux for the board.
We will create a Client class to represent a PubNub client, configure the PubNub subscription, make it easy to publish a message with a command and the required values for the command and declare the code for the callbacks that are going to be executed when certain events are fired. The code file for the sample is iot_python_chapter_09_04.py (you will find this in the code bundle). Don't forget to replace the strings assigned to the publish_key and subscribe_key local variables in the __init__ method with the values you have retrieved from the previously explained PubNub key generation process. The following lines show the code for the Client class:
import time
from pubnub import Pubnub
class Client:
command_key = "command"
def __init__(self, channel):
self.channel = channel
# Publish key is the one that usually starts with the
"pub-c-" prefix
publish_key = "pub-c-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# Subscribe key is the one that usually starts with the
"sub-c" prefix
# Do not forget to replace the string with your subscribe key
subscribe_key = "sub-c-xxxxxxxx-xxxx-xxxx-xxxx-
xxxxxxxxxxxx"
self.pubnub = Pubnub(publish_key=publish_key,
subscribe_key=subscribe_key)
self.pubnub.subscribe(channels=self.channel,
callback=self.callback,
error=self.callback,
connect=self.connect,
reconnect=self.reconnect,
disconnect=self.disconnect)
def callback_command_message(self, message):
print("I've received the following response from PubNub
cloud: {0}".format(message))
def error_command_message(self, message):
print("There was an error when working with the PubNub
cloud: {0}".format(message))
def publish_command(self, command_name, key, value):
command_message = {
self.__class__.command_key: command_name,
key: value}
self.pubnub.publish(
channel=self.channel,
message=command_message,
callback=self.callback_command_message,
error=self.error_command_message)
def callback(self, message, channel):
if channel == self.channel:
print("I've received the following message:
{0}".format(message))
def error(self, message):
print("Error: " + str(message))
def connect(self, message):
print("Connected to the {0} channel".
format(self.channel))
print(self.pubnub.publish(
channel=self.channel,
message="Listening to messages in the PubNub Python
Client"))
def reconnect(self, message):
print("Reconnected to the {0} channel".
format(self.channel))
def disconnect(self, message):
print("Disconnected from the {0} channel".
format(self.channel))
The Client class declares the command_key class attribute that defines the key string that defines what the code understands as a command in the messages. Our main goal is to build and publish command messages to a specified channel. We have to specify the PubNub channel name in the channel-required argument. The constructor, that is, the __init__ method, saves the received argument in an attribute with the same name. We will be both a subscriber and a publisher for this channel.
Then, the constructor declares two local variables: publish_key and subscribe_key. These local variables save the publish and subscribe keys we had generated with the PubNub Admin portal. Then, the code creates a new Pubnub instance with publish_key and subscribe_key as the arguments, and saves the reference for the new instance in the pubnub attribute. Finally, the code calls the subscribe method for the new instance to subscribe to data on the channel saved in the channel attribute. The call to this method specifies many methods declared in the Client class as we did for our previous examples.
The publish_command method receives a command name, the key and the value that provide the necessary information to execute the command in the command_name, key and value required arguments. In this case, we don't target the command to a specific IoT device and all the devices that subscribe to the channel and run the code in our previous example will process the commands that we publish. We can use the code as a baseline to work with more complex examples in which generate commands that target specific IoT devices. Obviously, it is also necessary to improve the security.
The method creates a dictionary and saves it in the command_message local variable. The command_key class attribute is the first key for the dictionary and the command_name received as an argument, the value that composes the first key-value pair. Then, the code calls the self.pubnub.publish method to publish the command_message dictionary to the channel saved in the channel attribute. The call to this method specifies self.callback_command_message as the callback to be executed when the message is published successfully and self.error_command_message as the callback to be executed when an error occurred during the publishing process. As happened in our previous example, when we specify a callback, the publish method works with an asynchronous execution.
Now, we will use the previously coded Client class to write a __main__ method that uses the PubNub cloud to publish two commands that our board will process. The following lines show the code for the __main__ method. The code file for the sample is iot_python_chapter_09_04.py (you will find this in the code bundle).
if __name__ == "__main__":
client = Client("temperature")
client.publish_command(
"print_temperature_fahrenheit",
"temperature_fahrenheit",
45)
client.publish_command(
"print_information_message",
"text",
"Python IoT"
)
# Sleep 60 seconds (60000 milliseconds)
time.sleep(60)
The code in the __main__ method is very easy to understand. The code creates an instance of the Client class with "temperature" as an argument to become both a subscriber and a publisher for this channel in the PubNub cloud. The code saves the new instances in the client local variable.
The code calls the publish_command method with the necessary arguments to build and publish the print_temperature_fahrenheit command with a temperature value of 45. The method will publish the command with an asynchronous execution. Then, the code calls the publish_command method again with the necessary arguments to build and publish the print_information_message command with a text value of "Python IoT". The method will publish the second command with an asynchronous execution.
Finally, the code sleeps for 1 minute (60 seconds) in order to make it possible for the asynchronous executions to publish the commands successfully. The different callbacks defined in the Client class will be executed as the different events fire. As we are also subscribed to the channel, we will also receive the messages we publish in the temperature channel.
Keep the Python code we executed in our previous example running on the board. We want the board to process our commands. In addition, keep the Web browser in which you are working with the PubNub debug console opened because we also want to see all the messages in the log.
The following line will start the example for the Python client in any computer or device that you want to use as a client. It is possible to run the code in another SSH terminal in case you want to use the same board as a client.
python iot_python_chapter_09_04.py
After you run the example, you will see the following output in the Python console that runs the Python client, that is, the iot_python_chapter_09_04.py (you will find this in the code bundle)Python script.
Connected to the temperature channel
I've received the following response from PubNub cloud: [1, u'Sent', u'14596508980494876']
I've received the following response from PubNub cloud: [1, u'Sent', u'14596508980505581']
[1, u'Sent', u'14596508982165140']
I've received the following message: {u'text': u'Python IoT', u'command': u'print_information_message'}
I've received the following message: {u'command': u'print_temperature_fahrenheit', u'temperature_fahrenheit': 45}
I've received the following message: Listening to messages in the PubNub Python Client
I've received the following message: {u'successfully_processed_command': u'print_information_message'}
I've received the following message: {u'successfully_processed_command': u'print_temperature_fahrenheit'}
The code used the PubNub Python SDK to build and publish the following two command messages in the temperature channel:
{"command":"print_temperature_fahrenheit", "temperature_fahrenheit": "45"}
{"command":"print_information_message", "text": "Python IoT"}
As we are also subscribed to the temperature channel, we receive the messages we sent with an asynchronous execution. Then, we received the successfully processed command messages for the two command messages. The board has processed the commands and published the messages to the temperature channel.
After you run the example, go to the Web browser in which you are working with the PubNub debug console. You will see the following messages listed in the previously created client:
[1,"Subscribed","temperature"]
"Listening to messages in the Intel Galileo Gen 2 board"
{
"text": "Python IoT",
"command": "print_information_message"
}
{
"command": "print_temperature_fahrenheit",
"temperature_fahrenheit": 45
}
"Listening to messages in the PubNub Python Client"
{
"successfully_processed_command": "print_information_message"
}
{
"successfully_processed_command": "print_temperature_fahrenheit"
}
The following picture shows the last messages displayed in the log for the PubNub client after we run the previous example:
You will see the following text displayed at the bottom of the OLED matrix: Python IoT. In addition, the servo's shaft will rotate to 45 degrees.
We can use the PubNub SDKs available in different programming languages to create applications and apps that publish and receive messages in the PubNub cloud and interact with IoT devices. In this case, we worked with the Python SDK to create a client that publishes commands. It is possible to create mobile apps that publish commands and easily build an app that can interact with our IoT device.
Summary
In this article, we combined many cloud-based services that allowed us to easily publish data collected from sensors and visualize it in a Web-based dashboard. We realized that there is always a Python API, and therefore, it is easy to write Python code that interacts with popular cloud-based services.
Resources for Article:
Further resources on this subject:
Internet of Things with BeagleBone[article]
The Internet of Things[article]
Python Data Structures[article]
Read more