Command is a short-lived message sent to a connected endpoint from the Kaa platform. With the commands, you can toggle the lights on/off, open a car trunk, or request an immediate endpoint state report.
Any commands may be in either pending or executed state. Pending state means that the command was sent to an endpoint but no execution result for that command is known yet. Executed state is assigned to the command that has gotten an endpoint response, meaning an endpoint received the command, executed it, and sent the execution result back to the platform.
Consider the following example.
Let’s assume you just sent a command to your Tesla car to park itself in a parking slot. The platform immediately assigns pending to that command, while your Tesla receives the command and starts parking. After finishing with the task, the car reports the execution result of that command back to the platform and Kaa assigns executed to that command.
Every command has an execution status code and reason phrase that endpoints can specify at the moment of reporting the execution result back to the platform.
If, for example, your Tesla couldn’t finish the parking because it was taken by another vehicle, it may report the execution result specifying 409
status code and some meaningful reason phrase, which will be displayed for you in the Kaa UI.
Command type represents the type of command that you want to execute on an endpoint e.g. reboot
, close-door
, switch-light
, or in the case of Tesla—park
.
An endpoint can handle as many command types as you define in its firmware.
Now, let’s jump into action and do some practice.
We assume that you have already created an application, application version, and endpoint with a token while following the “connecting your first device” tutorial. You can reuse them or create new ones.
Start by logging into your Kaa Cloud account.
When the user invokes a command (e.g., from Kaa UI) on a device that connects to the platform over a synchronous protocol (e.g., HTTP), there is no way for the platform to push such command to the device. Instead, Kaa persists the command and waits until the device requests it for execution. This means that for devices with synchronous protocols it is their responsibility to periodically poll the platform for new commands.
So let’s invoke some command and make it available for execution by an endpoint.
Go to the “Device management” -> “Devices” -> select your device -> “Commands” tab.
In the “Command execution” widget fill out Command type field with reboot
and set Maximum command retention for delivery to 1 hour.
Maximum command retention for delivery defines the time of how long the command is available for execution by an endpoint.
Click Run
.
Once the command is invoked, it appears in a response to the polling during the time specified in the Maximum command retention for delivery field (1 hour in our case).
To poll the platform for new commands with the reboot
command type, execute the bellow cURL
.
The last part of the URL designates the command type, which in our case is reboot
.
Replace <app-version-name>
with the actual application version used by the endpoint and <endpoint-token>
with the endpoint token.
curl --location --request POST 'https://connect.cloud.kaaiot.com:443/kp1/<app-version-name>/cex/<endpoint-token>/command/reboot' \
--data-raw '{}'
You just retrieved the earlier invoked command.
Capture the id
from the response.
It’s an internal command ID used by Kaa to uniquely identify the command.
Note that the command is still in the Pending
state in the “Commands history” widget on Kaa UI.
Let’s send the command execution result back to Kaa.
Specify the application version for <app-version-name>
, the endpoint token for <endpoint-token>
and the command ID for <command-ID>
from the earlier received response body.
curl --location --request POST 'https://connect.cloud.kaaiot.com:443/kp1/<app-version-name>/cex/<endpoint-token>/result/reboot' \
--data-raw '[{
"id": <command-ID>,
"statusCode": 200,
"reasonPhrase": "OK",
"payload": "Success"
}]'
When the platform receives the execution result for the command with the specific command ID, it marks the command as executed and stops returning it in a response for the command polling.
To run the below MQTT client on your PC, you will need Python 3 installed. To speed things up a little, you can also just open and run it on Replit.com.
Initialize the ENDPOINT_TOKEN
and the APPLICATION_VERSION
variables with endpoint token and application version respectively.
# Simple MQTT-based command execution client for the Kaa IoT platform.
# Handles "reboot" and "zero" command types.
# See https://docs.kaaiot.io/KAA/docs/current/Tutorials/sending-commands-to-device/
import json
import paho.mqtt.client as mqtt
import random
import signal
import string
import time
KPC_HOST = "mqtt.cloud.kaaiot.com" # Kaa Cloud plain MQTT host
KPC_PORT = 1883 # Kaa Cloud plain MQTT port
ENDPOINT_TOKEN = "" # Paste endpoint token
APPLICATION_VERSION = "" # Paste application version
class DataCollectionClient:
def __init__(self, client):
self.client = client
self.data_collection_topic = f'kp1/{APPLICATION_VERSION}/dcx/{ENDPOINT_TOKEN}/json/32'
command_reboot_topic = f'kp1/{APPLICATION_VERSION}/cex/{ENDPOINT_TOKEN}/command/reboot/status'
self.client.message_callback_add(command_reboot_topic, self.handle_reboot_command)
self.command_reboot_result_topik = f'kp1/{APPLICATION_VERSION}/cex/{ENDPOINT_TOKEN}/result/reboot'
command_zero_topic = f'kp1/{APPLICATION_VERSION}/cex/{ENDPOINT_TOKEN}/command/zero/status'
self.client.message_callback_add(command_zero_topic, self.handle_zero_command)
self.command_zero_result_topik = f'kp1/{APPLICATION_VERSION}/cex/{ENDPOINT_TOKEN}/result/zero'
def connect_to_server(self):
print(f'Connecting to Kaa server at {KPC_HOST}:{KPC_PORT} using application version {APPLICATION_VERSION} and endpoint token {ENDPOINT_TOKEN}')
self.client.connect(KPC_HOST, KPC_PORT, 60)
print('Successfully connected')
def disconnect_from_server(self):
print(f'Disconnecting from Kaa server at {KPC_HOST}:{KPC_PORT}...')
self.client.loop_stop()
self.client.disconnect()
print('Successfully disconnected')
def handle_reboot_command(self, client, userdata, message):
print(f'<--- Received "reboot" command on topic {message.topic} \nRebooting...')
command_result = self.compose_command_result_payload(message)
print(f'command result {command_result}')
client.publish(topic=self.command_reboot_result_topik, payload=command_result)
# With below approach we don't receive the command confirmation on the server side.
# self.client.disconnect()
# time.sleep(5) # Simulate the reboot
# self.connect_to_server()
def handle_zero_command(self, client, userdata, message):
print(f'<--- Received "zero" command on topic {message.topic} \nSending zero values...')
command_result = self.compose_command_result_payload(message)
client.publish(topic=self.data_collection_topic, payload=self.compose_data_sample(0, 0, 0))
client.publish(topic=self.command_zero_result_topik, payload=command_result)
def compose_command_result_payload(self, message):
command_payload = json.loads(str(message.payload.decode("utf-8")))
print(f'command payload: {command_payload}')
command_result_list = []
for command in command_payload:
commandResult = {"id": command['id'], "statusCode": 200, "reasonPhrase": "OK", "payload": "Success"}
command_result_list.append(commandResult)
return json.dumps(
command_result_list
)
def compose_data_sample(self, fuelLevel, minTemp, maxTemp):
return json.dumps({
'timestamp': int(round(time.time() * 1000)),
'fuelLevel': fuelLevel,
'temperature': random.randint(minTemp, maxTemp),
})
def on_message(client, userdata, message):
print(f'Message received: topic {message.topic}\nbody {str(message.payload.decode("utf-8"))}')
def main():
# Initiate server connection
client = mqtt.Client(client_id=''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)))
data_collection_client = DataCollectionClient(client)
data_collection_client.connect_to_server()
client.on_message = on_message
# Start the loop
client.loop_start()
fuelLevel, minTemp, maxTemp = 100, 95, 100
# Send data samples in loop
listener = SignalListener()
while listener.keepRunning:
payload = data_collection_client.compose_data_sample(fuelLevel, minTemp, maxTemp)
result = data_collection_client.client.publish(topic=data_collection_client.data_collection_topic, payload=payload)
if result.rc != 0:
print('Server connection lost, attempting to reconnect')
data_collection_client.connect_to_server()
else:
print(f'--> Sent message on topic "{data_collection_client.data_collection_topic}":\n{payload}')
time.sleep(3)
fuelLevel = fuelLevel - 0.3
if fuelLevel < 1:
fuelLevel = 100
data_collection_client.disconnect_from_server()
class SignalListener:
keepRunning = True
def __init__(self):
signal.signal(signal.SIGINT, self.stop)
signal.signal(signal.SIGTERM, self.stop)
def stop(self, signum, frame):
print('Shutting down...')
self.keepRunning = False
if __name__ == '__main__':
main()
Run the python code.
Now that the simulator is running, go to the endpoint’s dashboard and send a command with type zero
to the endpoint.
Navigate to the endpoint Device telemetry
widget and see that the endpoint responded with zero telemetry data values.
Now repeat the above procedure with any other command specifying custom Command type and Command body in JSON.
You can browse command history using the Commands history
widget on Kaa UI.
You can view command status (“Pending” or “Executed”), its request and response payloads, and re-run the command.