Sending Commands to Devices

You can use Cloud IoT Core to send commands to devices. Commands are transitory, one-time directives sent to devices that are connected to Cloud IoT Core and subscribed to the commands topic.

Compared to device configurations, commands are faster, can be sent more frequently, and are independent of other Cloud IoT Core features. When choosing between commands and configurations, consider whether you need persistence/knowledge over time (configurations) or prefer speed and/or time-bound directives (commands).

Commands can be useful when you want to:

  • Send messages quickly to many devices at a specific time
  • Send large-volume messages to many devices at a specific time
  • Send time-bound directives that should expire
  • Send incremental device settings

Commands have the following characteristics:

  • Directly sent to subscribed, connected devices
  • Not persisted in Cloud IoT Core
  • Dropped for devices that are not subscribed and connected at the time the command is sent
  • Not unique (duplicates may be sent, though this is unlikely)
  • Not sent in any particular order (but delivered roughly in the send order)
  • In any format (blob of data)

Currently, Cloud IoT Core supports commands over MQTT only (not HTTP).

Commands compared to configurations

Cloud IoT Core also supports device configurations. Configurations are more consistent and permanent than commands. Configurations are persisted in Cloud IoT Core and, when using MQTT, the latest configuration is eventually delivered to all subscribed devices — even devices that subscribe later.

The following table can help you decide whether to use a command or a configuration:

Configurations Commands
Latest config is retried until delivered (MQTT) Retried for QoS 1, but not guaranteed to be delivered
Persistent, so if a device connects (MQTT) or polls (HTTP) later, the most recent configuration will still be delivered Not persistent; delivered only to devices connected at the time the command is sent
Each new version replaces the previous version No relationship or order among commands
Tells a device what to "be"; corresponds to state in Cloud IoT Core Tells a device what to "do" at a specific time
Higher latency Lower latency
Typically small (max 64 KB) in size Up to 256 KB
Arbitrary, user-defined blob Arbitrary, user-defined blob
1 update per sec, per device 1,000 per sec, per project (configurable)
2,500 per sec, per registry

Because commands are not persisted in Cloud IoT Core and are not retried indefinitely, you should not expect device telemetry or state data to reflect a particular command. The device may not have received the command, or it may have received subsequent commands or configurations. Commands are intended to be transitory and are not part of long-term device data.

To manage command ordering and duplicates, use device logic or client applications.

Sending a command

To send a command to a device, use GCP Console, gcloud, or the Cloud IoT Core API.

Console

To send a command to a device:

  1. Go to the Registries page in GCP Console.

    Go to the Registries page

  2. Click the ID of the registry for the device.
  3. On the Registry details page, click the ID of the device you want to send the command to.
  4. At the top of the page, click Send command.
  5. Select the format of the command:

    • Text
    • Base64
  6. In the Command data field, enter the command.

  7. In the optional Subfolder field, enter the name of a subfolder for this command. Devices that are subscribed to the wildcard topic will receive commands sent to subfolders.
  8. Click Send command.

gcloud

To send a command to a device, run the gcloud iot devices commands send command:

gcloud iot devices commands send \
    { --command-file=COMMAND_FILE | --command-data=COMMAND_DATA } \
    --region=REGION  \
    --registry=REGISTRY_ID \
    --device=DEVICE_ID \
    [--subfolder=SUBFOLDER]\

If the command data contains special characters, use --command-file rather than --command-data.

API

Use the SendCommandToDevice method to send a command.

C#

public static object SendCommand(string deviceId, string projectId,
    string cloudRegion, string registryName, string data)
{
    var cloudIot = CreateAuthorizedClient();

    var devicePath = String.Format("projects/{0}/locations/{1}/registries/{2}/devices/{3}",
        projectId, cloudRegion, registryName, deviceId);
    // Data sent through the wire has to be base64 encoded.
    SendCommandToDeviceRequest req = new SendCommandToDeviceRequest()
    {
        BinaryData = Convert.ToBase64String(Encoding.Unicode.GetBytes(data))
    };

    Console.WriteLine("Sending command to {0}\n", devicePath);

    var res =
        cloudIot.Projects.Locations.Registries.Devices.SendCommandToDevice(req, devicePath).Execute();


    Console.WriteLine("Command response: " + res.ToString());
    return 0;
}

Go

// sendCommand sends a command to a device listening for commands.
func sendCommand(w io.Writer, projectID string, region string, registry string, deviceID string, sendData string) (*cloudiot.SendCommandToDeviceResponse, error) {
	// Authorize the client using Application Default Credentials.
	// See https://g.co/dv/identity/protocols/application-default-credentials
	ctx := context.Background()
	httpClient, err := google.DefaultClient(ctx, cloudiot.CloudPlatformScope)
	if err != nil {
		return nil, err
	}
	client, err := cloudiot.New(httpClient)
	if err != nil {
		return nil, err
	}

	req := cloudiot.SendCommandToDeviceRequest{
		BinaryData: b64.StdEncoding.EncodeToString([]byte(sendData)),
	}

	name := fmt.Sprintf("projects/%s/locations/%s/registries/%s/devices/%s", projectID, region, registry, deviceID)

	response, err := client.Projects.Locations.Registries.Devices.SendCommandToDevice(name, &req).Do()
	if err != nil {
		return nil, err
	}

	fmt.Fprintln(w, "Sent command to device")

	return response, err
}

Java

public static void sendCommand(
    String deviceId, String projectId, String cloudRegion, String registryName, String data)
    throws GeneralSecurityException, IOException {
  GoogleCredential credential =
      GoogleCredential.getApplicationDefault().createScoped(CloudIotScopes.all());
  JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
  HttpRequestInitializer init = new RetryHttpInitializerWrapper(credential);
  final CloudIot service = new CloudIot.Builder(
      GoogleNetHttpTransport.newTrustedTransport(),jsonFactory, init)
      .setApplicationName(APP_NAME).build();

  final String devicePath = String.format("projects/%s/locations/%s/registries/%s/devices/%s",
      projectId, cloudRegion, registryName, deviceId);

  SendCommandToDeviceRequest req = new SendCommandToDeviceRequest();

  // Data sent through the wire has to be base64 encoded.
  Base64.Encoder encoder = Base64.getEncoder();
  String encPayload = encoder.encodeToString(data.getBytes("UTF-8"));
  req.setBinaryData(encPayload);
  System.out.printf("Sending command to %s\n", devicePath);

  SendCommandToDeviceResponse res =
      service
          .projects()
          .locations()
          .registries()
          .devices()
          .sendCommandToDevice(devicePath, req).execute();

  System.out.println("Command response: " + res.toString());
}

Node.js

// Client retrieved in callback
// getClient(serviceAccountJson, function(client) {...});
// const cloudRegion = 'us-central1';
// const deviceId = 'my-device';
// const projectId = 'adjective-noun-123';
// const registryId = 'my-registry';
const parentName = `projects/${projectId}/locations/${cloudRegion}`;
const registryName = `${parentName}/registries/${registryId}`;

const binaryData = Buffer.from(commandMessage).toString('base64');

const request = {
  name: `${registryName}/devices/${deviceId}`,
  binaryData: binaryData,
};

client.projects.locations.registries.devices.sendCommandToDevice(
  request,
  (err, data) => {
    if (err) {
      console.log('Could not send command:', request);
      console.log('Error: ', err);
    } else {
      console.log('Success:', data.statusText);
    }
  }
);

PHP

use Google\Cloud\Iot\V1\DeviceManagerClient;

/**
 * Sends a command to a device.
 *
 * @param string $registryId IOT Device Registry ID
 * @param string $deviceId IOT Device ID
 * @param string $command The command sent to a device
 * @param string $projectId Google Cloud project ID
 * @param string $location (Optional) Google Cloud region
 */
function send_command_to_device(
    $registryId,
    $deviceId,
    $command,
    $projectId,
    $location = 'us-central1'
) {
    print('Sending command to device' . PHP_EOL);

    // Instantiate a client.
    $deviceManager = new DeviceManagerClient();
    $deviceName = $deviceManager->deviceName($projectId, $location, $registryId, $deviceId);

    // Response empty on success
    $deviceManager->sendCommandToDevice($deviceName, $command);

    printf('Command sent' . PHP_EOL);
}

Python

print('Sending command to device')
client = get_client(service_account_json)
device_path = 'projects/{}/locations/{}/registries/{}/devices/{}'.format(
        project_id, cloud_region, registry_id, device_id)

config_body = {
    'binaryData': base64.urlsafe_b64encode(
            command.encode('utf-8')).decode('ascii')
}

return client.projects(
    ).locations().registries(
    ).devices().sendCommandToDevice(
    name=device_path, body=config_body).execute()

Ruby

# project_id  = "Your Google Cloud project ID"
# location_id = "The Cloud region the registry is located in"
# registry_id = "The registry containing the device to send commands to"
# device_id   = "The identifier of the device to send commands to"
# data        = "The command, e.g. {move: forward} to send to the device"

require "google/apis/cloudiot_v1"

# Initialize the client and authenticate with the specified scope
Cloudiot   = Google::Apis::CloudiotV1
iot_client = Cloudiot::CloudIotService.new
iot_client.authorization = Google::Auth.get_application_default(
  "https://www.googleapis.com/auth/cloud-platform"
)

# The resource name of the location associated with the project
parent   = "projects/#{project_id}/locations/#{location_id}"
resource = "#{parent}/registries/#{registry_id}/devices/#{device_id}"

command_req = Cloudiot::SendCommandToDeviceRequest.new
command_req.binary_data = data

# Set configuration for the provided device
iot_client.send_project_location_registry_group_device_command_to_device resource, command_req

puts "Command sent!"

Receiving a command

To receive a command, a device must:

  • Be connected to Cloud IoT Core using the MQTT protocol
  • Be subscribed to the MQTT topic /devices/{device-id}/commands/# (the # wildcard is required)

By subscribing to the wildcard topic, the device will receive commands sent to devices/{device-id}/commands, as well as commands sent to subfolders (such as devices/{device-id}/commands/{subfolder}). Subscribing to a specific subfolder is not supported.

Commands are delivered to devices that are connected and subscribed at that specific time. They are not queued or preserved for devices that connect and subscribe at a later time.

Quality of service (QoS)

Command delivery depends on the QoS level you're using:

QoS level Guarantee
0 No guarantee (best effort only), even when the request returns OK
1 At-least-once delivery guaranteed if the sendCommandtoDevice request returns OK

In other words, at QoS 0, a message is considered successful as soon as it is sent, regardless of the device response. At QoS 1, success means the device has acknowledged the message delivery. Note that "at-least-once" delivery means the device may receive the command multiple times; Cloud IoT Core does not track how many times the command was received.

Errors

  • If the command timeout (60 sec, as noted in Quotas and Limits) is reached, DEADLINE_EXCEEDED is returned.

  • If the device is not connected, or is connected but is not subscribed to the MQTT wildcard topic, FAILED_PRECONDITION is returned.

Logging

Commands sent to devices, as well as acknowledgments by devices, are logged in Stackdriver Logging.

Pricing

Commands are billed like all other messages sent over MQTT. For details, see Pricing.

Was this page helpful? Let us know how we did:

Send feedback about...

Google Cloud Internet of Things Core