Connect Raspberry Pi to Kaa IoT Cloud

In Blog
By

This tutorial provides a step-by-step instruction on how to connect a Raspberry Pi to your free Kaa IoT Cloud account. Kaa Cloud account is essentially your personal isolated sandbox space in Kaa IoT platform where you can connect smart things, visualize data, build automation, manage devices and more.

The following diagram illustrates what we will have in our final solution:

Connect Raspberry Pi to Kaa Cloud use case

We will be using a BME280 sensor for measuring temperature, humidity, and pressure values. But even if you don't have a sensor like that, you will still be able to randomly generate measurement values.

Kaa Cloud Account setup

Create a free Kaa Cloud account if you don’t have it yet.

Then go to the "Device Management" dashboard in your account and add a new device specifying a token that we will use later to identify a Raspberry Pi in the Kaa Cloud. Also, go to the added device page from the "Device Management" dashboard and copy the application version name. It looks like this:

871c9b3a-38f5-4f4c-a2a6-ce5792b3906b-v1

We will need both the application version and token to connect a Raspberry Pi to the Kaa Cloud.

Now that we have a device’s digital twin created in Kaa as well as obtained its token and application version, let's work with the Raspberry Pi.

BME280 Sensor and WiFi Setup

Connect the BME280 sensor to the Raspberry Pi by referring to the following table:

Module PCB Desc GPIO Header Pins
VCC 3.3V P1-01
GND Ground P1-06
SCL I2C SCL P1-05
SDA I2C SDA P1-03

Here’s the diagram of a breadboard setup.


Breadboard setup for BME280

If you don't have the BME280 sensor, then skip the above step and move on.

Connect the Raspberry Pi to the WiFi.

Once the Raspberry Pi is connected to the WiFi, install the BME Python library:

$ pip install bme280

Sending Data Over MQTT

Install Eclipse Paho MQTT Python client library:

$ pip install paho-mqtt

Create somewhere on the Raspberry Pi filesystem a file called "client.py" and open it.

Copy the following Python code that reads temperature, humidity, and pressure from a BME280 sensor and sends it into the Kaa Cloud. Then paste the code into the "client.py" file. If you don't have a BME280 sensor, jump to the next code snippet.

import argparse
import json
import logging
import os
import random
import signal
import string
import sys
import time
import paho.mqtt.client as mqtt
import bme280

DEFAULT_KPC_HOST = os.getenv('DEFAULT_KPC_HOST', 'cloud.kaaiot.com')
DEFAULT_KPC_PORT = os.getenv('DEFAULT_KPC_PORT', 1883)

EPMX_INSTANCE_NAME = os.getenv('EPMX_INSTANCE_NAME', 'epmx')
DCX_INSTANCE_NAME = os.getenv('DCX_INSTANCE_NAME', 'dcx')


def killhandle(signum, frame):
  logger.info("SIGTERM detected, shutting down")
  disconnectFromServer(client=client, host=host, port=port)
  sys.exit(0)


signal.signal(signal.SIGINT, killhandle)
signal.signal(signal.SIGTERM, killhandle)

# Configure logging
logger = logging.getLogger('mqtt-client')
logger.setLevel(logging.DEBUG)

hdl = logging.StreamHandler()
hdl.setLevel(logging.DEBUG)
hdl.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))

logger.addHandler(hdl)

# Parse command line arguments and get device name
parser = argparse.ArgumentParser(description="MQTT client for demo application")
parser.add_argument("-d", "--deviceName", action="store", dest="deviceName", default="BME/BMP 280",
                    required=False, help="Name of connected device")
parser.add_argument("-a", "--appversion", action="store", dest="appversion", required=True,
                    help="Application version")
parser.add_argument("-t", "--token", action="store", dest="token", required=True,
                    help="Device token")
parser.add_argument("-s", "--host", action="store", dest="host", default=DEFAULT_KPC_HOST,
                    help="Server host to connect to")
parser.add_argument("-p", "--port", action="store", dest="port", default=DEFAULT_KPC_PORT,
                    help="Server port to connect to")
args = parser.parse_args()
appversion = args.appversion
token = args.token
client_id = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6))
host = args.host
port = args.port
logger.info("Using EP token {0}, server at {1}:{2}".format(token, host, port))


def connectToServer(client, host, port):
  logger.info("Connecting to KPC instance at {0}:{1}...".format(host, port))
  client.connect(host, port, 60)
  logger.info("Successfully connected")


def disconnectFromServer(client, host, port):
  logger.info("Disconnecting from server at {0}:{1}.".format(host, port))
  time.sleep(4)  # wait
  client.loop_stop()  # stop the loop
  client.disconnect()
  logger.info("Successfully disconnected")


# METADATA section ------------------------------------
# Compose KP1 topic for metadata
metadata_request_id = random.randint(1, 99)
topic_metadata = "kp1/{application_version}/{service_instance}/{resource_path}".format(
  application_version=appversion,
  service_instance=EPMX_INSTANCE_NAME,
  resource_path="{token}/update/keys/{metadata_request_id}".format(token=token,
                                                                   metadata_request_id=metadata_request_id)
)
logger.debug("Composed metadata topic: {}".format(topic_metadata))


def composeMetadata(version):
  return json.dumps(
    {
      "model": "BME/BMP 280",
      "fwVersion": version,
      "customer": "Andrew",
      "latitude": 40.71427,
      "longitude": -74.00597,
    }
  )


# TELEMETRY section --------------------------------------
# Compose KP1 topic for data collection
data_request_id = random.randint(1, 99)
topic_data_collection = "kp1/{application_version}/{service_instance}/{resource_path}".format(
  application_version=appversion,
  service_instance=DCX_INSTANCE_NAME,
  resource_path="{token}/json/{data_request_id}".format(token=token, data_request_id=data_request_id)
)
logger.debug("Composed data collection topic: {}".format(topic_data_collection))


def composeDataSample():
  # BME/BMP 280
  temperature, pressure, humidity = bme280.readBME280All()

  payload = [
    {
      "timestamp": int(round(time.time() * 1000)),
      "temperature": round(temperature,1),
      "humidity": int(humidity),
      "pressure": round(pressure,2)
    }
  ]
  return json.dumps(payload)


def on_connect(client, userdata, flags, rc):
  if rc == 0:
    client.connected_flag = True  # set flag
    logger.info("Successfully connected to MQTT server")
  else:
    logger.info("Failed to connect to MQTT code. Returned code=", rc)


def on_message(client, userdata, message):
  logger.info("Message received: topic [{}]\nbody [{}]".format(message.topic, str(message.payload.decode("utf-8"))))


# Initiate server connection
client = mqtt.Client(client_id=client_id)
client.connected_flag = False  # create flag in class
client.on_connect = on_connect  # bind call back function

client.on_message = on_message
# Start the loop
client.loop_start()

connectToServer(client=client, host=host, port=int(port))

while not client.connected_flag:  # wait in loop
  logger.info("Waiting for connection with MQTT server")
  time.sleep(1)

# Send metadata once on the first connection
metadata = composeMetadata(version="v0.0.1")
client.publish(topic=topic_metadata, payload=metadata)
logger.info("Sent metadata: {0}\n".format(metadata))

# Send data sample in loop
while 1:
  payload = composeDataSample()
  result = client.publish(topic=topic_data_collection, payload=payload)

  if result.rc != 0:
    logger.info("Server connection lost, attempting to reconnect")
    connectToServer(client=client, host=host, port=port)
  else:
    logger.debug("{0}: Sent next data: {1}".format(token, payload))

  time.sleep(8)

If you don't have a BME280 sensor then use the following code with randomly generated temperature, humidity, and pressure values.

import argparse
import json
import logging
import os
import random
import signal
import string
import sys
import time
import paho.mqtt.client as mqtt

DEFAULT_KPC_HOST = os.getenv('DEFAULT_KPC_HOST', 'cloud.kaaiot.com')
DEFAULT_KPC_PORT = os.getenv('DEFAULT_KPC_PORT', 1883)

EPMX_INSTANCE_NAME = os.getenv('EPMX_INSTANCE_NAME', 'epmx')
DCX_INSTANCE_NAME = os.getenv('DCX_INSTANCE_NAME', 'dcx')


def killhandle(signum, frame):
  logger.info("SIGTERM detected, shutting down")
  disconnectFromServer(client=client, host=host, port=port)
  sys.exit(0)


signal.signal(signal.SIGINT, killhandle)
signal.signal(signal.SIGTERM, killhandle)

# Configure logging
logger = logging.getLogger('mqtt-client')
logger.setLevel(logging.DEBUG)

hdl = logging.StreamHandler()
hdl.setLevel(logging.DEBUG)
hdl.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))

logger.addHandler(hdl)

# Parse command line arguments and get device name
parser = argparse.ArgumentParser(description="MQTT client for demo application")
parser.add_argument("-d", "--deviceName", action="store", dest="deviceName", default="BME/BMP 280",
                    required=False, help="Name of connected device")
parser.add_argument("-a", "--appversion", action="store", dest="appversion", required=True,
                    help="Application version")
parser.add_argument("-t", "--token", action="store", dest="token", required=True,
                    help="Device token")
parser.add_argument("-s", "--host", action="store", dest="host", default=DEFAULT_KPC_HOST,
                    help="Server host to connect to")
parser.add_argument("-p", "--port", action="store", dest="port", default=DEFAULT_KPC_PORT,
                    help="Server port to connect to")
args = parser.parse_args()
appversion = args.appversion
token = args.token
client_id = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6))
host = args.host
port = args.port
logger.info("Using EP token {0}, server at {1}:{2}".format(token, host, port))


def connectToServer(client, host, port):
  logger.info("Connecting to KPC instance at {0}:{1}...".format(host, port))
  client.connect(host, port, 60)
  logger.info("Successfully connected")


def disconnectFromServer(client, host, port):
  logger.info("Disconnecting from server at {0}:{1}.".format(host, port))
  time.sleep(4)  # wait
  client.loop_stop()  # stop the loop
  client.disconnect()
  logger.info("Successfully disconnected")


# METADATA section ------------------------------------
# Compose KP1 topic for metadata
metadata_request_id = random.randint(1, 99)
topic_metadata = "kp1/{application_version}/{service_instance}/{resource_path}".format(
  application_version=appversion,
  service_instance=EPMX_INSTANCE_NAME,
  resource_path="{token}/update/keys/{metadata_request_id}".format(token=token,
                                                                   metadata_request_id=metadata_request_id)
)
logger.debug("Composed metadata topic: {}".format(topic_metadata))


def composeMetadata(version):
  return json.dumps(
    {
      "model": "BME/BMP 280",
      "fwVersion": version,
      "customer": "Andrew",
      "latitude": 40.71427,
      "longitude": -74.00597,
    }
  )


# TELEMETRY section --------------------------------------
# Compose KP1 topic for data collection
data_request_id = random.randint(1, 99)
topic_data_collection = "kp1/{application_version}/{service_instance}/{resource_path}".format(
  application_version=appversion,
  service_instance=DCX_INSTANCE_NAME,
  resource_path="{token}/json/{data_request_id}".format(token=token, data_request_id=data_request_id)
)
logger.debug("Composed data collection topic: {}".format(topic_data_collection))


def composeDataSample():
  payload = [
    {
      "timestamp": int(round(time.time() * 1000)),
      "temperature": random.randint(18, 23),
      "humidity": random.randint(40, 60),
      "pressure": random.randint(980, 1000)
    }
  ]
  return json.dumps(payload)


def on_connect(client, userdata, flags, rc):
  if rc == 0:
    client.connected_flag = True  # set flag
    logger.info("Successfully connected to MQTT server")
  else:
    logger.info("Failed to connect to MQTT code. Returned code=", rc)


def on_message(client, userdata, message):
  logger.info("Message received: topic [{}]\nbody [{}]".format(message.topic, str(message.payload.decode("utf-8"))))


# Initiate server connection
client = mqtt.Client(client_id=client_id)
client.connected_flag = False  # create flag in class
client.on_connect = on_connect  # bind call back function

client.on_message = on_message
# Start the loop
client.loop_start()

connectToServer(client=client, host=host, port=int(port))

while not client.connected_flag:  # wait in loop
  logger.info("Waiting for connection with MQTT server")
  time.sleep(1)

# Send metadata once on the first connection
metadata = composeMetadata(version="v0.0.1")
client.publish(topic=topic_metadata, payload=metadata)
logger.info("Sent metadata: {0}\n".format(metadata))

# Send data sample in loop
while 1:
  payload = composeDataSample()
  result = client.publish(topic=topic_data_collection, payload=payload)

  if result.rc != 0:
    logger.info("Server connection lost, attempting to reconnect")
    connectToServer(client=client, host=host, port=port)
  else:
    logger.debug("{0}: Sent next data: {1}".format(token, payload))

  time.sleep(8)

Close the "client.py" file and run it:

$ python client.py -a {copied application version} -t {copied token}

Go to the device page in the "Device Management" dashboard on the Kaa Cloud and observe live data from the connected Raspberry Pi.

Next Steps

Now that you have successfully connected a Raspberry Pi to the Kaa Cloud, expand your solution by adding new dashboards and widgets, sending new types of data, and exploring analytics.

If you have any questions, feel free to ask them in Gitter.

Also, here is the link to our webinar where we explained how to connect a Raspberry Pi with BME280 and Senseair S08 CO2 sensors to the Kaa Cloud in more details: Kaa 1.1 webinar.

Andrew Pasika
Software Engineer