In this tutorial, we’ll cover the basics of OTA (Over-the-Air) updates and demonstrate how to implement them using Python. This simple implementation will help you set up Kaa OTA and understand how it works.
OTA stands for “Over-the-Air update”. It’s a method of delivering firmware updates to your device wirelessly without a requirement of a direct USB connection. This is particularly useful for updating multiple IoT devices simultaneously or when the devices are located in hard-to-reach places. OTA is a crucial feature for IoT devices with internet access.
The Kaa Platform hosts and distributes firmware files, which devices can automatically download and apply. In this tutorial, the device connects to the Kaa Platform using MQTT to check for available firmware updates. When a new firmware version is detected, the device downloads the necessary files. After the download is complete, it’s up to you to update the device version and determine how to handle the file in device code, as demonstrated in this tutorial.
1.0.0
, leave the other fields as they are, and click “Create”.The version 1.0.0
should always be installed manually on your device.
That is why we didn’t upload anything for this version.
To begin, install the code below:
import paho.mqtt.client as mqtt
import json
import random
import time
import requests
# MQTT server details
# Provide 'app_version' and 'token' with your values
app_version = "<your-app-version>"
token = "<your-endpoint-token>"
mqtt_server = "mqtt.cloud.kaaiot.com"
mqtt_port = 1883
request_id = 1
# Endpoint MQTT topics
publish_topic = f"kp1/{app_version}/dcx/{token}/json/{request_id}"
response_topic = f"kp1/{app_version}/dcx/{token}/json/{request_id}/status"
error_topic = f"kp1/{app_version}/dcx/{token}/json/{request_id}/error"
# Ota MQTT topics
ota_response_topic = f"kp1/{app_version}/cmx_ota/{token}/config/json/{request_id}/status"
ota_error_topic = f"kp1/{app_version}/cmx_ota/{token}/config/json/{request_id}/error"
# Firmware details
current_version = "1.0.0"
time_sleep = 5
file_name = "file.txt"
# Callback when connected to MQTT server
def on_connect(client, userdata, flags, rc):
print("Connected with result code " + str(rc))
client.subscribe(response_topic)
client.subscribe(error_topic)
client.subscribe(ota_response_topic)
client.subscribe(ota_error_topic)
# Callback when a message is received
def on_message(client, userdata, msg):
topic = msg.topic
payload = msg.payload.decode()
print(f"\n//-----Message received on topic {topic}: {payload}")
print(f"Current firmware version: {current_version}\n")
if f"kp1/{app_version}/cmx_ota/{token}" in topic:
handle_update(payload)
client_loop(client)
print("-----//\n")
def handle_update(payload):
try:
# Parse the JSON payload
update_data = json.loads(payload)
print("Update Data:", update_data)
# Confirm that the payload is valid
status_code = update_data.get("statusCode")
if status_code != 200:
reason_phrase = update_data.get("reasonPhrase", "No reason provided")
print(f"Firmware message's status code is not 200, but: {status_code}")
print(f"Response: {reason_phrase}")
return
# Check if the current version is up-to-date
new_version = update_data.get("configId")
print(f"Current version: {current_version}")
print(f"Latest version: {new_version}")
if new_version == current_version:
print("Current version is up to date. Skipping...")
return
# Extract the firmware download link
system_file_link = update_data["config"]["system"].get("files", [None])[0]
download_link = update_data["config"].get("link")
firmware_link = system_file_link or download_link or ""
print(f"firmwareLink: {firmware_link}")
# Download the file
download_and_apply_update(firmware_link, file_name, new_version)
except json.JSONDecodeError:
print("Failed to parse JSON payload.")
except KeyError as e:
print(f"Missing expected key: {e}")
# Download the new version of the file
def download_and_apply_update(script_url, save_path, new_version):
response = requests.get(script_url)
if response.status_code == 200:
global current_version
# Update version number
print(f"Updated current version from {current_version} to {new_version}")
current_version = new_version
# Apply your custom changes here
# Just save the updated file
with open(save_path, "wb") as file:
file.write(response.content)
print(f"Updated file downloaded to {save_path}")
else:
print("Failed to download the new script.")
# Report current firmware version
def report_current_firmware_version():
report_topic = f"kp1/{app_version}/cmx_ota/{token}/applied/json"
report_payload = json.dumps({"configId": current_version})
print(f"Reporting current firmware version on topic: {report_topic} and payload: {report_payload}")
client.publish(report_topic, report_payload)
# Request new version from Kaa
def request_new_firmware():
request_id = random.randint(0, 99) # Generate a random request ID to avoid conflicts
firmware_request_topic = f"kp1/{app_version}/cmx_ota/{token}/config/json/{request_id}"
print(f"Requesting firmware using topic: {firmware_request_topic}")
client.publish(firmware_request_topic, json.dumps({"observe": True}))
# Handle infinite loop, to continuously check the firmware version from Kaa
def client_loop(client):
time.sleep(time_sleep)
# Maintain connection with Kaa endpoint
payload = {}
print(f"Publishing new data to Kaa: {payload}\n")
client.publish(publish_topic, json.dumps(payload))
# Check new version of the firmware
report_current_firmware_version()
request_new_firmware()
# Setup MQTT client
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.connect(mqtt_server, mqtt_port, 60)
# Publishing to Kaa to get response and start the program
client.publish(publish_topic, json.dumps({}))
client.loop_forever()
Create a main.py
file anywhere on your system.
Setup a python environment and install required packages:
pip install paho-mqtt==1.5.1 requests
In the code, replace token
and app_version
with the values from the endpoint you created earlier in the Kaa Platform.
Note that the variable current_version
is set to 1.0.0
, which represents the initial firmware version.
Run the code.
If token
and app_version
are set correctly, the code will connect to Kaa and begin checking for updates at regular intervals.
Since only the initial 1.0.0
update is available, no updates will be found for the current version.
To start benefiting from OTA, you’ll need to provision a new version.
To provision the new firmware, go to the Kaa platform UI and navigate to “Device Management” -> “Software OTA” -> “Add software version”.
From there:
1.1.0
.1.0.0
to specify that devices running version 1.0.0
can upgrade to 1.1.0
.Once the new firmware version 1.1.0
is submitted via the Kaa UI, the device running version 1.0.0
will automatically detect the update through MQTT.
It will then download the provided file and save it under the name defined in the file_name
variable (default is file.txt
).
In essence, this process ‘updates’ the system by replacing the existing file.txt
with the newly downloaded file.
Updating a single file may seem simple, but you can customize the logic in the download_and_apply_update
function to automatically perform specific actions each time your device receives a new update.
For instance, you could run a console command on the newly downloaded file, or unzip and execute it if the file is an archive.
A great example of an OTA (Over-the-Air) update is applying updates to IoT devices, such as demonstrated in the OTA ESP32 tutorial.
In this case, a binary file is uploaded, and the ESP32 is instructed to updates using that file.
That’s all about OTA.
You should now be able to create new OTA updates, such as from version 1.1.0 to 1.2.0, 1.3.0, and add custom logic to handle them properly in your code.
Simply follow the steps outlined in the article Defining a New Firmware Version 1.1.0.
To troubleshoot any issues related to communication between the client and the Kaa platform, you can use the following Python script:
import itertools
import json
import queue
import random
import string
import sys
import time
import paho.mqtt.client as mqtt
KPC_HOST = "mqtt.cloud.kaaiot.com" # Kaa Cloud plain MQTT host
KPC_PORT = 1883 # Kaa Cloud plain MQTT port
CURRENT_SOFTWARE_VERSION = "" # Specify software that device currently uses (e.g., 0.0.1)
APPLICATION_VERSION = "" # Paste your application version
ENDPOINT_TOKEN = "" # Paste your endpoint token
class SoftwareClient:
def __init__(self, client):
self.client = client
self.software_by_request_id = {}
self.global_request_id = itertools.count()
get_software_topic = f'kp1/{APPLICATION_VERSION}/cmx_ota/{ENDPOINT_TOKEN}/config/json/#'
self.client.message_callback_add(get_software_topic, self.handle_software)
def handle_software(self, client, userdata, message):
if message.topic.split('/')[-1] == 'status':
topic_part = message.topic.split('/')[-2]
if topic_part.isnumeric():
request_id = int(topic_part)
print(f'<--- Received software response on topic {message.topic}')
software_queue = self.software_by_request_id[request_id]
software_queue.put_nowait(message.payload)
else:
print(f'<--- Received software push on topic {message.topic}:\n{str(message.payload.decode("utf-8"))}')
else:
print(f'<--- Received bad software response on topic {message.topic}:\n{str(message.payload.decode("utf-8"))}')
def get_software(self):
request_id = next(self.global_request_id)
get_software_topic = f'kp1/{APPLICATION_VERSION}/cmx_ota/{ENDPOINT_TOKEN}/config/json/{request_id}'
software_queue = queue.Queue()
self.software_by_request_id[request_id] = software_queue
print(f'---> Requesting software by topic {get_software_topic}')
payload = {
"configId": CURRENT_SOFTWARE_VERSION
}
self.client.publish(topic=get_software_topic, payload=json.dumps(payload))
try:
software = software_queue.get(True, 5)
del self.software_by_request_id[request_id]
return str(software.decode("utf-8"))
except queue.Empty:
print('Timed out waiting for software response from server')
sys.exit()
def main():
# Initiate server connection
print(f'Connecting to Kaa server at {KPC_HOST}:{KPC_PORT} using application version {APPLICATION_VERSION} and endpoint token {ENDPOINT_TOKEN}')
client_id = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6))
client = mqtt.Client(client_id=client_id)
client.connect(KPC_HOST, KPC_PORT, 60)
client.loop_start()
software_client = SoftwareClient(client)
# Fetch available software
retrieved_software = software_client.get_software()
print(f'Retrieved software from server: {retrieved_software}')
time.sleep(5)
client.disconnect()
if __name__ == '__main__':
main()