デバイスへのコマンドの送信

Cloud IoT Core を使用すると、デバイスにコマンドを送信できます。コマンドは、Cloud IoT Core に接続され、コマンド トピックに登録されたデバイスに送信される一時的な 1 回限りのディレクティブです。

デバイス構成と比較して、他の Cloud IoT Core 機能に依存しないため、コマンドはより速く、より頻繁に送信できます。コマンドと構成を選択する際は、時間の経過に伴って永続性や知識が必要かどうか(構成)、速度または時間方向のディレクティブ(コマンド)を優先するかどうかを検討してください。

コマンドは次のような場合に役立ちます。

  • 特定の時間に多数のデバイスにすばやくメッセージを送信する
  • 特定の時間に多数のデバイスに多数のメッセージを送信する
  • 有効期限が必要な時間制限のあるディレクティブを送信する
  • 増分デバイス設定を送信する

コマンドには次の特徴があります。

  • 登録済みの接続されているデバイスに直接送信される
  • Cloud IoT Core で保持されない
  • 登録されていないデバイスとコマンドの送信時に接続されていたデバイスはドロップされる
  • 一意ではない(重複する情報が送信される場合があるものの、その可能性は低い)
  • 特定の順序で送信されない(ただし、ほぼ送信順に配信される)
  • 任意の形式(データの blob)

現在、Cloud IoT Core では MQTT 経由のコマンドのみがサポートされています(HTTP ではサポートされていません)。

コマンドと構成の比較

Cloud IoT Core はデバイスの構成もサポートしています。構成はコマンドよりも整合性があり、永続的です。構成は Cloud IoT Core に保持され、MQTT を使用すると、最終的にサブスクライブしたデバイスであっても、最終的にはサブスクライブされたすべてのデバイスに最新の構成が配信されます。

次の表は、コマンドと構成のどちらを使用するかを決定する際に役立ちます。

構成 コマンド
最新の構成が配信されるまで再試行される(MQTT) QoS 1 が再試行されますが、配信される保証はありません
持続的であるため、後でデバイスが接続(MQTT)またはポーリング(HTTP)しても、最新の構成が引き続き配信されます 永続的ではありません。コマンドの送信時に接続したデバイスにのみ配信されます
新しいバージョンが以前のバージョンに置き換わります コマンド間に関係や順序がない
「ある」べき状態にデバイスに指示します。これは Cloud IoT Core の状態に対応します。 特定の時刻に「行う」ことをデバイスに指示します
高レイテンシ 低レイテンシ
通常は小さいサイズ(最大 64 KB) 最大 256 KB
ユーザー定義の任意の blob ユーザー定義の任意の blob
デバイス 1 台あたり毎秒 1 件の更新 1 プロジェクトあたり 1,000 回/秒(構成可能)
レジストリごとに 1 秒あたり 2,500 回

コマンドは Cloud IoT Core では保持されず、無期限に再試行されないため、デバイスのテレメトリーや状態データに特定のコマンドが反映されることはありません。デバイスがコマンドを受信していないか、後続のコマンドまたは構成を受信している可能性があります。コマンドは一時的なものであり、長期的なデバイスデータの一部ではありません。

コマンドの順序と重複を管理するには、デバイス ロジックまたはクライアント アプリケーションを使用します。

コマンドを送信する

デバイスにコマンドを送信するには、Cloud Console、gcloud、または Cloud IoT Core API を使用します。

Console

デバイスにコマンドを送信するには:

  1. Cloud Console で、[レジストリ] ページに移動します。

    [レジストリ] ページに移動

  2. デバイスのレジストリの ID をクリックします。
  3. 左側のレジストリ メニューで、[デバイス] をクリックします。
  4. コマンドを送信するデバイスの ID をクリックします。
  5. ページの上部にある [コマンドを送信] をクリックします。
  6. コマンドの形式を選択します。

    • テキスト
    • Base64
  7. [Command data] フィールドに、コマンドを入力します。

  8. オプションの [サブフォルダ] フィールドに、このコマンドのサブフォルダの名前を入力します。ワイルドカード トピックに登録されたデバイスは、サブフォルダに送信されたコマンドを受信します。

  9. [コマンドを送信] をクリックします。

gcloud

デバイスにコマンドを送信するには、gcloud iot devices commands send コマンドを実行します。

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

コマンドデータに特殊文字が含まれている場合は、--command-data ではなく --command-file を使用してください。

API

SendCommandToDevice メソッドを使用して、コマンドを送信します。

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.UTF8.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, registryID 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, registryID, 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, nil
}

Java

protected static void sendCommand(
    String deviceId, String projectId, String cloudRegion, String registryName, String data)
    throws GeneralSecurityException, IOException {
  GoogleCredentials credential =
      GoogleCredentials.getApplicationDefault().createScoped(CloudIotScopes.all());
  JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
  HttpRequestInitializer init = new HttpCredentialsAdapter(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(StandardCharsets.UTF_8.name()));
  req.setBinaryData(encPayload);
  System.out.printf("Sending command to %s%n", devicePath);

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

  System.out.println("Command response: sent");
}

Node.js

// const cloudRegion = 'us-central1';
// const deviceId = 'my-device';
// const commandMessage = 'message for device';
// const projectId = 'adjective-noun-123';
// const registryId = 'my-registry';
const iot = require('@google-cloud/iot');
const iotClient = new iot.v1.DeviceManagerClient({
  // optional auth parameters.
});

async function sendCommand() {
  // Construct request
  const formattedName = iotClient.devicePath(
    projectId,
    cloudRegion,
    registryId,
    deviceId
  );

  const binaryData = Buffer.from(commandMessage);

  const request = {
    name: formattedName,
    binaryData: binaryData,
  };

  const [response] = await iotClient.sendCommandToDevice(request);
  console.log('Sent command: ', response);
}

sendCommand();

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 = iot_v1.DeviceManagerClient()
device_path = client.device_path(project_id, cloud_region, registry_id, device_id)

# command = 'Hello IoT Core!'
data = command.encode("utf-8")

return client.send_command_to_device(
    request={"name": device_path, "binary_data": data}
)

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_device_command_to_device resource, command_req

puts "Command sent!"

コマンドを受信する

コマンドを受信するには、デバイスが次の条件を満たしている必要があります。

  • MQTT プロトコルを使用して Cloud IoT Core に接続している
  • MQTT トピック /devices/{device-id}/commands/# に登録される(# ワイルドカードは必須です)

ワイルドカード トピックをサブスクライブすることで、devices/{device-id}/commands に送信されたコマンドや、サブフォルダ(devices/{device-id}/commands/{subfolder} など)に送信されたコマンドを受信します。特定のサブフォルダへの登録はサポートされていません。

コマンドは、その時刻に接続され、登録されているデバイスに配信されます。後で接続とサブスクライブを行うデバイスのために、キューに入れられたり保持されたりすることはありません。

Quality of Service(QoS)

コマンド配信は、使用している QoS レベルによって異なります。

QoS レベル 保証
0 リクエストが OK を返しても、無保証(ベスト エフォートのみ)
1 sendCommandtoDevice リクエストが OK を返した場合、at-least-once 配信が保証されます。

つまり、QoS 0 の場合、メッセージはデバイスのレスポンスに関係なく、送信後直ちに成功とみなされます。QoS 1 の段階では、デバイスがメッセージ配信に確認応答しています。なお、「at-least-once」配信とは、デバイスがコマンドを複数回受け取る可能性があることを意味します。Cloud IoT Core は、コマンドの受信回数を追跡しません。

エラー

  • コマンドのタイムアウト(割り当てと上限で説明されているように 60 秒)に達すると、DEADLINE_EXCEEDED が返されます。

  • デバイスが接続されていないか、接続されていても MQTT ワイルドカード トピックに登録されていない場合は、FAILED_PRECONDITION が返されます。

ロギング

デバイスに送信されたコマンドとデバイスによる確認応答は Cloud Logging に記録されます。

料金

コマンドは、MQTT を介して送信される他のすべてのメッセージと同様に課金されます。詳細については、料金をご覧ください。