Google Cloud IoT Core は 2023 年 8 月 16 日に廃止されます。詳細については、担当の Google Cloud アカウント チームにお問い合わせください。

デバイスの構成

Cloud IoT Core では、デバイス構成を変更することでそのデバイスを制御できます。デバイス構成は、任意のユーザー定義のデータ blob です。構成がデバイスに適用されると、デバイスから Cloud IoT Core に状態を報告できます。

デバイス構成は、MQTT ブリッジと HTTP ブリッジで異なる働きをします。詳しくは、以下をご覧ください。

詳細については、デバイス、構成、状態をご覧ください。

上限

構成の更新は、デバイスごとに毎秒 1 回の更新に制限されます。ただし、最良の結果を得るには、デバイス構成の更新頻度をさらに少なくして、多くても 10 秒ごとの更新にします。

更新レートは、最新のサーバー確認応答と次の更新リクエストの間の時間として計算されます。

プロトコルの違い

MQTT

MQTT を使用するデバイスは、構成を更新するための特別な MQTT トピックに登録できます。

/devices/{device-id}/config

デバイスがその構成トピックに登録すると、MQTT ブリッジによって MQTT SUBACK メッセージが返されます。このメッセージには、構成トピックに対して付与された QoS(0 または 1。エラーが発生した場合は 128)が含まれます。

最初の登録後、デバイスはメッセージのペイロードで最新の構成を受信し、Cloud IoT Core に push される際に追加の構成更新を受信します。

次のサンプルでは、MQTT を使用してデバイス上の構成の更新を取得する方法を示します。

Java

static MqttCallback mCallback;
static long MINUTES_PER_HOUR = 60;

/** Create a Cloud IoT Core JWT for the given project id, signed with the given RSA key. */
private static String createJwtRsa(String projectId, String privateKeyFile)
    throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
  Instant now = Instant.now();
  // Create a JWT to authenticate this device. The device will be disconnected after the token
  // expires, and will have to reconnect with a new token. The audience field should always be set
  // to the GCP project id.
  JwtBuilder jwtBuilder =
      Jwts.builder()
          .setIssuedAt(Date.from(now))
          .setExpiration(Date.from(now.plusSeconds(20 * 60)))
          .setAudience(projectId);

  byte[] keyBytes = Files.readAllBytes(Paths.get(privateKeyFile));
  PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
  KeyFactory kf = KeyFactory.getInstance("RSA");

  return jwtBuilder.signWith(SignatureAlgorithm.RS256, kf.generatePrivate(spec)).compact();
}

/** Create a Cloud IoT Core JWT for the given project id, signed with the given ES key. */
private static String createJwtEs(String projectId, String privateKeyFile)
    throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
  Instant now = Instant.now();
  // Create a JWT to authenticate this device. The device will be disconnected after the token
  // expires, and will have to reconnect with a new token. The audience field should always be set
  // to the GCP project id.
  JwtBuilder jwtBuilder =
      Jwts.builder()
          .setIssuedAt(Date.from(now))
          .setExpiration(Date.from(now.plusSeconds(20 * 60)))
          .setAudience(projectId);

  byte[] keyBytes = Files.readAllBytes(Paths.get(privateKeyFile));
  PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
  KeyFactory kf = KeyFactory.getInstance("EC");

  return jwtBuilder.signWith(SignatureAlgorithm.ES256, kf.generatePrivate(spec)).compact();
}

/** Connects the gateway to the MQTT bridge. */
protected static MqttClient startMqtt(
    String mqttBridgeHostname,
    int mqttBridgePort,
    String projectId,
    String cloudRegion,
    String registryId,
    String gatewayId,
    String privateKeyFile,
    String algorithm)
    throws NoSuchAlgorithmException, IOException, MqttException, InterruptedException,
        InvalidKeySpecException {

  // Build the connection string for Google's Cloud IoT Core MQTT server. Only SSL
  // connections are accepted. For server authentication, the JVM's root certificates
  // are used.
  final String mqttServerAddress =
      String.format("ssl://%s:%s", mqttBridgeHostname, mqttBridgePort);

  // Create our MQTT client. The mqttClientId is a unique string that identifies this device. For
  // Google Cloud IoT Core, it must be in the format below.
  final String mqttClientId =
      String.format(
          "projects/%s/locations/%s/registries/%s/devices/%s",
          projectId, cloudRegion, registryId, gatewayId);

  MqttConnectOptions connectOptions = new MqttConnectOptions();
  // Note that the Google Cloud IoT Core only supports MQTT 3.1.1, and Paho requires that we
  // explictly set this. If you don't set MQTT version, the server will immediately close its
  // connection to your device.
  connectOptions.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);

  Properties sslProps = new Properties();
  sslProps.setProperty("com.ibm.ssl.protocol", "TLSv1.2");
  connectOptions.setSSLProperties(sslProps);

  // With Google Cloud IoT Core, the username field is ignored, however it must be set for the
  // Paho client library to send the password field. The password field is used to transmit a JWT
  // to authorize the device.
  connectOptions.setUserName("unused");

  if ("RS256".equals(algorithm)) {
    connectOptions.setPassword(createJwtRsa(projectId, privateKeyFile).toCharArray());
  } else if ("ES256".equals(algorithm)) {
    connectOptions.setPassword(createJwtEs(projectId, privateKeyFile).toCharArray());
  } else {
    throw new IllegalArgumentException(
        "Invalid algorithm " + algorithm + ". Should be one of 'RS256' or 'ES256'.");
  }

  System.out.println(String.format("%s", mqttClientId));

  // Create a client, and connect to the Google MQTT bridge.
  try (MqttClient client =
      new MqttClient(mqttServerAddress, mqttClientId, new MemoryPersistence())) {
    // Both connect and publish operations may fail. If they do, allow retries but with an
    // exponential backoff time period.
    long initialConnectIntervalMillis = 500L;
    long maxConnectIntervalMillis = 6000L;
    long maxConnectRetryTimeElapsedMillis = 900000L;
    float intervalMultiplier = 1.5f;

    long retryIntervalMs = initialConnectIntervalMillis;
    long totalRetryTimeMs = 0;

    while (totalRetryTimeMs < maxConnectRetryTimeElapsedMillis && !client.isConnected()) {
      try {
        client.connect(connectOptions);
      } catch (MqttException e) {
        int reason = e.getReasonCode();

        // If the connection is lost or if the server cannot be connected, allow retries, but with
        // exponential backoff.
        System.out.println("An error occurred: " + e.getMessage());
        if (reason == MqttException.REASON_CODE_CONNECTION_LOST
            || reason == MqttException.REASON_CODE_SERVER_CONNECT_ERROR) {
          System.out.println("Retrying in " + retryIntervalMs / 1000.0 + " seconds.");
          Thread.sleep(retryIntervalMs);
          totalRetryTimeMs += retryIntervalMs;
          retryIntervalMs *= intervalMultiplier;
          if (retryIntervalMs > maxConnectIntervalMillis) {
            retryIntervalMs = maxConnectIntervalMillis;
          }
        } else {
          throw e;
        }
      }
    }

    attachCallback(client, gatewayId);

    // The topic gateways receive error updates on. QoS must be 0.
    String errorTopic = String.format("/devices/%s/errors", gatewayId);
    System.out.println(String.format("Listening on %s", errorTopic));

    client.subscribe(errorTopic, 0);

    return client;
  }
}

protected static void sendDataFromDevice(
    MqttClient client, String deviceId, String messageType, String data)
    throws MqttException, UnsupportedEncodingException {
  if (!"events".equals(messageType) && !"state".equals(messageType)) {
    System.err.println("Invalid message type, must ether be 'state' or events'");
    return;
  }
  final String dataTopic = String.format("/devices/%s/%s", deviceId, messageType);
  MqttMessage message = new MqttMessage(data.getBytes(StandardCharsets.UTF_8.name()));
  message.setQos(1);
  client.publish(dataTopic, message);
  System.out.println("Data sent");
}

/** Sends data on behalf of a bound device using the Gateway. */
protected static void sendDataFromBoundDevice(
    String mqttBridgeHostname,
    short mqttBridgePort,
    String projectId,
    String cloudRegion,
    String registryName,
    String gatewayId,
    String privateKeyFile,
    String algorithm,
    String deviceId,
    String messageType,
    String telemetryData)
    throws MqttException, IOException, InvalidKeySpecException, InterruptedException,
        NoSuchAlgorithmException {
  MqttClient client =
      startMqtt(
          mqttBridgeHostname,
          mqttBridgePort,
          projectId,
          cloudRegion,
          registryName,
          gatewayId,
          privateKeyFile,
          algorithm);
  attachDeviceToGateway(client, deviceId);
  sendDataFromDevice(client, deviceId, messageType, telemetryData);
  detachDeviceFromGateway(client, deviceId);
}

protected static void listenForConfigMessages(
    String mqttBridgeHostname,
    short mqttBridgePort,
    String projectId,
    String cloudRegion,
    String registryName,
    String gatewayId,
    String privateKeyFile,
    String algorithm,
    String deviceId)
    throws MqttException, IOException, InvalidKeySpecException, InterruptedException,
        NoSuchAlgorithmException {
  // Connect the Gateway
  MqttClient client =
      startMqtt(
          mqttBridgeHostname,
          mqttBridgePort,
          projectId,
          cloudRegion,
          registryName,
          gatewayId,
          privateKeyFile,
          algorithm);
  // Connect the bound device and listen for configuration messages.
  attachDeviceToGateway(client, deviceId);
  attachCallback(client, deviceId);

  detachDeviceFromGateway(client, deviceId);
}

protected static void attachDeviceToGateway(MqttClient client, String deviceId)
    throws MqttException, UnsupportedEncodingException {
  final String attachTopic = String.format("/devices/%s/attach", deviceId);
  System.out.println(String.format("Attaching: %s", attachTopic));
  String attachPayload = "{}";
  MqttMessage message = new MqttMessage(attachPayload.getBytes(StandardCharsets.UTF_8.name()));
  message.setQos(1);
  client.publish(attachTopic, message);
}

/** Detaches a bound device from the Gateway. */
protected static void detachDeviceFromGateway(MqttClient client, String deviceId)
    throws MqttException, UnsupportedEncodingException {
  final String detachTopic = String.format("/devices/%s/detach", deviceId);
  System.out.println(String.format("Detaching: %s", detachTopic));
  String attachPayload = "{}";
  MqttMessage message = new MqttMessage(attachPayload.getBytes(StandardCharsets.UTF_8.name()));
  message.setQos(1);
  client.publish(detachTopic, message);
}

protected static void mqttDeviceDemo(MqttExampleOptions options)
    throws NoSuchAlgorithmException, IOException, InvalidKeySpecException, MqttException,
        InterruptedException {
  // Build the connection string for Google's Cloud IoT Core MQTT server. Only SSL
  // connections are accepted. For server authentication, the JVM's root certificates
  // are used.
  final String mqttServerAddress =
      String.format("ssl://%s:%s", options.mqttBridgeHostname, options.mqttBridgePort);

  // Create our MQTT client. The mqttClientId is a unique string that identifies this device. For
  // Google Cloud IoT Core, it must be in the format below.
  final String mqttClientId =
      String.format(
          "projects/%s/locations/%s/registries/%s/devices/%s",
          options.projectId, options.cloudRegion, options.registryId, options.deviceId);

  MqttConnectOptions connectOptions = new MqttConnectOptions();
  // Note that the Google Cloud IoT Core only supports MQTT 3.1.1, and Paho requires that we
  // explictly set this. If you don't set MQTT version, the server will immediately close its
  // connection to your device.
  connectOptions.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);

  Properties sslProps = new Properties();
  sslProps.setProperty("com.ibm.ssl.protocol", "TLSv1.2");
  connectOptions.setSSLProperties(sslProps);

  // With Google Cloud IoT Core, the username field is ignored, however it must be set for the
  // Paho client library to send the password field. The password field is used to transmit a JWT
  // to authorize the device.
  connectOptions.setUserName("unused");

  Instant iat = Instant.now();
  if ("RS256".equals(options.algorithm)) {
    connectOptions.setPassword(
        createJwtRsa(options.projectId, options.privateKeyFile).toCharArray());
  } else if ("ES256".equals(options.algorithm)) {
    connectOptions.setPassword(
        createJwtEs(options.projectId, options.privateKeyFile).toCharArray());
  } else {
    throw new IllegalArgumentException(
        "Invalid algorithm " + options.algorithm + ". Should be one of 'RS256' or 'ES256'.");
  }

  // Create a client, and connect to the Google MQTT bridge.
  MqttClient client = new MqttClient(mqttServerAddress, mqttClientId, new MemoryPersistence());

  // Both connect and publish operations may fail. If they do, allow retries but with an
  // exponential backoff time period.
  long initialConnectIntervalMillis = 500L;
  long maxConnectIntervalMillis = 6000L;
  long maxConnectRetryTimeElapsedMillis = 900000L;
  float intervalMultiplier = 1.5f;

  long retryIntervalMs = initialConnectIntervalMillis;
  long totalRetryTimeMs = 0;

  while (totalRetryTimeMs < maxConnectRetryTimeElapsedMillis && !client.isConnected()) {
    try {
      client.connect(connectOptions);
    } catch (MqttException e) {
      int reason = e.getReasonCode();

      // If the connection is lost or if the server cannot be connected, allow retries, but with
      // exponential backoff.
      System.out.println("An error occurred: " + e.getMessage());
      if (reason == MqttException.REASON_CODE_CONNECTION_LOST
          || reason == MqttException.REASON_CODE_SERVER_CONNECT_ERROR) {
        System.out.println("Retrying in " + retryIntervalMs / 1000.0 + " seconds.");
        Thread.sleep(retryIntervalMs);
        totalRetryTimeMs += retryIntervalMs;
        retryIntervalMs *= intervalMultiplier;
        if (retryIntervalMs > maxConnectIntervalMillis) {
          retryIntervalMs = maxConnectIntervalMillis;
        }
      } else {
        throw e;
      }
    }
  }

  attachCallback(client, options.deviceId);

  // Publish to the events or state topic based on the flag.
  String subTopic = "event".equals(options.messageType) ? "events" : options.messageType;

  // The MQTT topic that this device will publish telemetry data to. The MQTT topic name is
  // required to be in the format below. Note that this is not the same as the device registry's
  // Cloud Pub/Sub topic.
  String mqttTopic = String.format("/devices/%s/%s", options.deviceId, subTopic);

  // Publish numMessages messages to the MQTT bridge, at a rate of 1 per second.
  for (int i = 1; i <= options.numMessages; ++i) {
    String payload = String.format("%s/%s-payload-%d", options.registryId, options.deviceId, i);
    System.out.format(
        "Publishing %s message %d/%d: '%s'%n",
        options.messageType, i, options.numMessages, payload);

    // Refresh the connection credentials before the JWT expires.
    long secsSinceRefresh = (Instant.now().toEpochMilli() - iat.toEpochMilli()) / 1000;
    if (secsSinceRefresh > (options.tokenExpMins * MINUTES_PER_HOUR)) {
      System.out.format("\tRefreshing token after: %d seconds%n", secsSinceRefresh);
      iat = Instant.now();
      if ("RS256".equals(options.algorithm)) {
        connectOptions.setPassword(
            createJwtRsa(options.projectId, options.privateKeyFile).toCharArray());
      } else if ("ES256".equals(options.algorithm)) {
        connectOptions.setPassword(
            createJwtEs(options.projectId, options.privateKeyFile).toCharArray());
      } else {
        throw new IllegalArgumentException(
            "Invalid algorithm " + options.algorithm + ". Should be one of 'RS256' or 'ES256'.");
      }
      client.disconnect();
      client.connect(connectOptions);
      attachCallback(client, options.deviceId);
    }

    // Publish "payload" to the MQTT topic. qos=1 means at least once delivery. Cloud IoT Core
    // also supports qos=0 for at most once delivery.
    MqttMessage message = new MqttMessage(payload.getBytes(StandardCharsets.UTF_8.name()));
    message.setQos(1);
    client.publish(mqttTopic, message);

    if ("event".equals(options.messageType)) {
      // Send telemetry events every second
      Thread.sleep(1000);
    } else {
      // Note: Update Device state less frequently than with telemetry events
      Thread.sleep(5000);
    }
  }

  // Wait for commands to arrive for about two minutes.
  for (int i = 1; i <= options.waitTime; ++i) {
    System.out.print('.');
    Thread.sleep(1000);
  }
  System.out.println("");

  // Disconnect the client if still connected, and finish the run.
  if (client.isConnected()) {
    client.disconnect();
  }

  System.out.println("Finished loop successfully. Goodbye!");
  client.close();
}

/** Attaches the callback used when configuration changes occur. */
protected static void attachCallback(MqttClient client, String deviceId)
    throws MqttException, UnsupportedEncodingException {
  mCallback =
      new MqttCallback() {
        @Override
        public void connectionLost(Throwable cause) {
          // Do nothing...
        }

        @Override
        public void messageArrived(String topic, MqttMessage message) {
          try {
            String payload = new String(message.getPayload(), StandardCharsets.UTF_8.name());
            System.out.println("Payload : " + payload);
            // TODO: Insert your parsing / handling of the configuration message here.
            //
          } catch (UnsupportedEncodingException uee) {
            System.err.println(uee);
          }
        }

        @Override
        public void deliveryComplete(IMqttDeliveryToken token) {
          // Do nothing;
        }
      };

  String commandTopic = String.format("/devices/%s/commands/#", deviceId);
  System.out.println(String.format("Listening on %s", commandTopic));

  String configTopic = String.format("/devices/%s/config", deviceId);
  System.out.println(String.format("Listening on %s", configTopic));

  client.subscribe(configTopic, 1);
  client.subscribe(commandTopic, 1);
  client.setCallback(mCallback);
}

Node.js

デバイスを構成トピックに登録する手順を、以下にハイライトで示します。

// const deviceId = `myDevice`;
// const registryId = `myRegistry`;
// const region = `us-central1`;
// const algorithm = `RS256`;
// const privateKeyFile = `./rsa_private.pem`;
// const serverCertFile = `./roots.pem`;
// const mqttBridgeHostname = `mqtt.googleapis.com`;
// const mqttBridgePort = 8883;
// const messageType = `events`;
// const numMessages = 5;

// The mqttClientId is a unique string that identifies this device. For Google
// Cloud IoT Core, it must be in the format below.
const mqttClientId = `projects/${projectId}/locations/${region}/registries/${registryId}/devices/${deviceId}`;

// With Google Cloud IoT Core, the username field is ignored, however it must be
// non-empty. The password field is used to transmit a JWT to authorize the
// device. The "mqtts" protocol causes the library to connect using SSL, which
// is required for Cloud IoT Core.
const connectionArgs = {
  host: mqttBridgeHostname,
  port: mqttBridgePort,
  clientId: mqttClientId,
  username: 'unused',
  password: createJwt(projectId, privateKeyFile, algorithm),
  protocol: 'mqtts',
  secureProtocol: 'TLSv1_2_method',
  ca: [readFileSync(serverCertFile)],
};

// Create a client, and connect to the Google MQTT bridge.
const iatTime = parseInt(Date.now() / 1000);
const client = mqtt.connect(connectionArgs);

// Subscribe to the /devices/{device-id}/config topic to receive config updates.
// Config updates are recommended to use QoS 1 (at least once delivery)
client.subscribe(`/devices/${deviceId}/config`, {qos: 1});

// Subscribe to the /devices/{device-id}/commands/# topic to receive all
// commands or to the /devices/{device-id}/commands/<subfolder> to just receive
// messages published to a specific commands folder; we recommend you use
// QoS 0 (at most once delivery)
client.subscribe(`/devices/${deviceId}/commands/#`, {qos: 0});

// The MQTT topic that this device will publish data to. The MQTT topic name is
// required to be in the format below. The topic name must end in 'state' to
// publish state and 'events' to publish telemetry. Note that this is not the
// same as the device registry's Cloud Pub/Sub topic.
const mqttTopic = `/devices/${deviceId}/${messageType}`;

client.on('connect', success => {
  console.log('connect');
  if (!success) {
    console.log('Client not connected...');
  } else if (!publishChainInProgress) {
    publishAsync(mqttTopic, client, iatTime, 1, numMessages, connectionArgs);
  }
});

client.on('close', () => {
  console.log('close');
  shouldBackoff = true;
});

client.on('error', err => {
  console.log('error', err);
});

client.on('message', (topic, message) => {
  let messageStr = 'Message received: ';
  if (topic === `/devices/${deviceId}/config`) {
    messageStr = 'Config message received: ';
  } else if (topic.startsWith(`/devices/${deviceId}/commands`)) {
    messageStr = 'Command message received: ';
  }

  messageStr += Buffer.from(message, 'base64').toString('ascii');
  console.log(messageStr);
});

client.on('packetsend', () => {
  // Note: logging packet send is very verbose
});

// Once all of the messages have been published, the connection to Google Cloud
// IoT will be closed and the process will exit. See the publishAsync method.

Python

デバイスを構成トピックに登録する手順を、以下にハイライトで示します。
def error_str(rc):
    """Convert a Paho error to a human readable string."""
    return "{}: {}".format(rc, mqtt.error_string(rc))

def on_connect(unused_client, unused_userdata, unused_flags, rc):
    """Callback for when a device connects."""
    print("on_connect", mqtt.connack_string(rc))

    # After a successful connect, reset backoff time and stop backing off.
    global should_backoff
    global minimum_backoff_time
    should_backoff = False
    minimum_backoff_time = 1

def on_disconnect(unused_client, unused_userdata, rc):
    """Paho callback for when a device disconnects."""
    print("on_disconnect", error_str(rc))

    # Since a disconnect occurred, the next loop iteration will wait with
    # exponential backoff.
    global should_backoff
    should_backoff = True

def on_publish(unused_client, unused_userdata, unused_mid):
    """Paho callback when a message is sent to the broker."""
    print("on_publish")

def on_message(unused_client, unused_userdata, message):
    """Callback when the device receives a message on a subscription."""
    payload = str(message.payload.decode("utf-8"))
    print(
        "Received message '{}' on topic '{}' with Qos {}".format(
            payload, message.topic, str(message.qos)
        )
    )

def get_client(
    project_id,
    cloud_region,
    registry_id,
    device_id,
    private_key_file,
    algorithm,
    ca_certs,
    mqtt_bridge_hostname,
    mqtt_bridge_port,
):
    """Create our MQTT client.

    The client_id is a unique string that identifies this device.
    For Google Cloud IoT Core, it must be in the format below.
    """
    client_id = "projects/{}/locations/{}/registries/{}/devices/{}".format(
        project_id, cloud_region, registry_id, device_id
    )
    print("Device client_id is '{}'".format(client_id))

    client = mqtt.Client(client_id=client_id)

    # With Google Cloud IoT Core, the username field is ignored, and the
    # password field is used to transmit a JWT to authorize the device.
    client.username_pw_set(
        username="unused", password=create_jwt(project_id, private_key_file, algorithm)
    )

    # Enable SSL/TLS support.
    client.tls_set(ca_certs=ca_certs, tls_version=ssl.PROTOCOL_TLSv1_2)

    # Register message callbacks. https://eclipse.org/paho/clients/python/docs/
    # describes additional callbacks that Paho supports. In this example, the
    # callbacks just print to standard out.
    client.on_connect = on_connect
    client.on_publish = on_publish
    client.on_disconnect = on_disconnect
    client.on_message = on_message

    # Connect to the Google MQTT bridge.
    client.connect(mqtt_bridge_hostname, mqtt_bridge_port)

    # This is the topic that the device will receive configuration updates on.
    mqtt_config_topic = "/devices/{}/config".format(device_id)

    # Subscribe to the config topic.
    client.subscribe(mqtt_config_topic, qos=1)

    # The topic that the device will receive commands on.
    mqtt_command_topic = "/devices/{}/commands/#".format(device_id)

    # Subscribe to the commands topic, QoS 1 enables message acknowledgement.
    print("Subscribing to {}".format(mqtt_command_topic))
    client.subscribe(mqtt_command_topic, qos=0)

    return client

HTTP

HTTP ブリッジを使用する場合、デバイスは明示的に新しい構成をリクエストする必要があります。

次のサンプルでは、HTTP を使用してデバイス上の構成の更新を取得する方法を示します。

Java

/** Publish an event or state message using Cloud IoT Core via the HTTP API. */
protected static void getConfig(
    String urlPath,
    String token,
    String projectId,
    String cloudRegion,
    String registryId,
    String deviceId,
    String version)
    throws IOException {
  // Build the resource path of the device that is going to be authenticated.
  String devicePath =
      String.format(
          "projects/%s/locations/%s/registries/%s/devices/%s",
          projectId, cloudRegion, registryId, deviceId);
  urlPath = urlPath + devicePath + "/config?local_version=" + version;

  HttpRequestFactory requestFactory =
      HTTP_TRANSPORT.createRequestFactory(
          new HttpRequestInitializer() {
            @Override
            public void initialize(HttpRequest request) {
              request.setParser(new JsonObjectParser(JSON_FACTORY));
            }
          });

  final HttpRequest req = requestFactory.buildGetRequest(new GenericUrl(urlPath));
  HttpHeaders heads = new HttpHeaders();

  heads.setAuthorization(String.format("Bearer %s", token));
  heads.setContentType("application/json; charset=UTF-8");
  heads.setCacheControl("no-cache");

  req.setHeaders(heads);
  ExponentialBackOff backoff =
      new ExponentialBackOff.Builder()
          .setInitialIntervalMillis(500)
          .setMaxElapsedTimeMillis(900000)
          .setMaxIntervalMillis(6000)
          .setMultiplier(1.5)
          .setRandomizationFactor(0.5)
          .build();
  req.setUnsuccessfulResponseHandler(new HttpBackOffUnsuccessfulResponseHandler(backoff));
  HttpResponse res = req.execute();
  System.out.println(res.getStatusCode());
  System.out.println(res.getStatusMessage());
  try (InputStream in = res.getContent()) {
    System.out
        .println(CharStreams.toString(new InputStreamReader(in, StandardCharsets.UTF_8.name())));
  }
}

Node.js

const getConfig = async (authToken, version) => {
  console.log(`Getting config from URL: ${urlBase}`);

  const options = {
    url: `${urlBase}/config?local_version=${version}`,
    headers: {
      authorization: `Bearer ${authToken}`,
      'content-type': 'application/json',
      'cache-control': 'no-cache',
    },
    retry: true,
  };
  try {
    const res = await request(options);
    console.log('Received config', JSON.stringify(res.data));
  } catch (err) {
    console.error('Received error: ', err);
    if (err.response && err.response.data && err.response.data.error) {
      console.error(
        `Received error: ${JSON.stringify(err.response.data.error)}`
      );
    }
  }
};

Python

def get_config(
    version,
    message_type,
    base_url,
    project_id,
    cloud_region,
    registry_id,
    device_id,
    jwt_token,
):
    headers = {
        "authorization": f"Bearer {jwt_token}",
        "content-type": "application/json",
        "cache-control": "no-cache",
    }

    basepath = "{}/projects/{}/locations/{}/registries/{}/devices/{}/"
    template = basepath + "config?local_version={}"
    config_url = template.format(
        base_url, project_id, cloud_region, registry_id, device_id, version
    )

    resp = requests.get(config_url, headers=headers)

    if resp.status_code != 200:
        print(f"Error getting config: {resp.status_code}, retrying")
        raise AssertionError(f"Not OK response: {resp.status_code}")

    return resp

デバイス構成の更新と取り消し

デバイス構成は、Cloud Platform Console、Cloud IoT Core API、gcloud を使用して更新および元に戻すことができます。

Console

デバイス構成を更新するには:

  1. Google Cloud コンソールで、[レジストリ] ページに移動します。

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

  2. デバイスを含むレジストリの ID をクリックします。

  3. 左側のレジストリ メニューで、[デバイス] をクリックします。

  4. 構成を更新するデバイスの ID をクリックします。

  5. ページの上部にある [Update config.] をクリックします。

  6. 構成のフォーマットを選択して、[Configuration] ボックスにデータを貼り付けます。

  7. [Send to device] をクリックします。

デバイスが新しい構成を受信する仕組みについては、プロトコルの違いをご覧ください。

デバイス構成を以前のバージョンに戻すには:

  1. Google Cloud コンソールで、[レジストリ] ページに移動します。

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

  2. 構成を元に戻すデバイスが含まれているレジストリの ID をクリックします。

  3. 左側のレジストリ メニューで、[デバイス] をクリックします。

  4. 構成を元に戻すデバイスの ID をクリックします。

  5. [構成と状態の履歴] をクリックします。 チェックボックスを使用して、構成の履歴、状態の履歴、またはその両方を表示します。デフォルトでは、両方が表示されます。

    • 緑色のチェックマークは、デバイスが構成を確認したことを示します(MQTT のみ)。
    • 黄色の警告記号は、デバイスがまだ構成を確認していないことを示します(MQTT のみ)。
    • 行をクリックして、完全な構成や状態データ(JSON 形式)、タイムスタンプ、バージョンを取得できます。
  6. [Compare] をクリックして設定データと状態データを比較します。このビューは、構成をデバッグするのに役立ちます。また、MQTT を使用している場合は、デバイスが特定の構成バージョンを認識していることを確認するのに役立ちます。(HTTP ブリッジは構成の確認応答をサポートしていません)。

  7. 元に戻すバージョンをクリックします。

    構成の詳細のテキスト バージョンを表示するには、[形式] リストで [テキスト] を選択します。

  8. [元に戻す] をクリックし、正しいバージョンが選択されていることを確認して [元に戻す] をクリックします。

gcloud

デバイス構成を更新または元に戻すには、gcloud iot devices configs update コマンドを実行します。

gcloud iot devices configs update \
  {--config-data=CONFIG_DATA | --config-file=CONFIG_FILE} \
  --device=DEVICE_ID \
  --registry=REGISTRY_ID \
  --region=REGION \
  [--version-to-update=VERSION_TO_UPDATE]

デバイスは、使用するプロトコルに従って更新されます。

API

API を使用してデバイス構成を更新または元に戻すには、config フィールドに新しい構成または以前の構成を指定して Device modifyCloudToDeviceConfig メソッドを使用します。デバイスを作成するときに構成を指定し、後で modifyCloudToDeviceConfig を使用して変更することもできます。

次のサンプルでは、デバイスの構成を更新する方法を示します。

C#

public static object SetDeviceConfig(string projectId, string cloudRegion, string registryId, string deviceId, string data)
{
    var cloudIot = CreateAuthorizedClient();
    // The resource name of the location associated with the key rings.
    var name = $"projects/{projectId}/locations/{cloudRegion}/registries/{registryId}/devices/{deviceId}";

    try
    {
        ModifyCloudToDeviceConfigRequest req = new ModifyCloudToDeviceConfigRequest()
        {
            BinaryData = Convert.ToBase64String(Encoding.Unicode.GetBytes(data))
        };

        var res = cloudIot.Projects.Locations.Registries.Devices.ModifyCloudToDeviceConfig(req, name).Execute();

        Console.WriteLine($"Configuration updated to: {res.Version}");
    }
    catch (Google.GoogleApiException e)
    {
        Console.WriteLine(e.Message);
        if (e.Error != null) return e.Error.Code;
        return -1;
    }
    return 0;
}

Go


// setConfig sends a configuration change to a device.
func setConfig(w io.Writer, projectID string, region string, registryID string, deviceID string, configData string) (*cloudiot.DeviceConfig, 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.ModifyCloudToDeviceConfigRequest{
		BinaryData: b64.StdEncoding.EncodeToString([]byte(configData)),
	}

	path := fmt.Sprintf("projects/%s/locations/%s/registries/%s/devices/%s", projectID, region, registryID, deviceID)
	response, err := client.Projects.Locations.Registries.Devices.ModifyCloudToDeviceConfig(path, &req).Do()
	if err != nil {
		return nil, err
	}

	fmt.Fprintf(w, "Config set!\nVersion now: %d\n", response.Version)

	return response, nil
}

Java

/** Set a device configuration to the specified data (string, JSON) and version (0 for latest). */
protected static void setDeviceConfiguration(
    String deviceId,
    String projectId,
    String cloudRegion,
    String registryName,
    String data,
    long version)
    throws GeneralSecurityException, IOException {
  GoogleCredentials credential =
      GoogleCredentials.getApplicationDefault().createScoped(CloudIotScopes.all());
  JsonFactory jsonFactory = GsonFactory.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);

  ModifyCloudToDeviceConfigRequest req = new ModifyCloudToDeviceConfigRequest();
  req.setVersionToUpdate(version);

  // 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);

  DeviceConfig config =
      service
          .projects()
          .locations()
          .registries()
          .devices()
          .modifyCloudToDeviceConfig(devicePath, req)
          .execute();

  System.out.println("Updated: " + config.getVersion());
}

Node.js

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

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

  const binaryData = Buffer.from(data).toString('base64');
  const request = {
    name: formattedName,
    versionToUpdate: version,
    binaryData: binaryData,
  };

  const [response] = await iotClient.modifyCloudToDeviceConfig(request);
  console.log('Success:', response);
}

modifyCloudToDeviceConfig();

PHP

use Google\Cloud\Iot\V1\DeviceManagerClient;

/**
 * Set a device's configuration.
 *
 * @param string $registryId IOT Device Registry ID
 * @param string $deviceId IOT Device ID
 * @param string $config Configuration sent to a device
 * @param string $version Version number for setting device configuration
 * @param string $projectId Google Cloud project ID
 * @param string $location (Optional) Google Cloud region
 */
function set_device_config(
    $registryId,
    $deviceId,
    $config,
    $version,
    $projectId,
    $location = 'us-central1'
) {
    print('Set device configuration' . PHP_EOL);

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

    $config = $deviceManager->modifyCloudToDeviceConfig($deviceName, $config, [
        'versionToUpdate' => $version,
    ]);

    printf('Version: %s' . PHP_EOL, $config->getVersion());
    printf('Data: %s' . PHP_EOL, $config->getBinaryData());
    printf('Update Time: %s' . PHP_EOL,
        $config->getCloudUpdateTime()->toDateTime()->format('Y-m-d H:i:s'));
}

Python

# project_id = 'YOUR_PROJECT_ID'
# cloud_region = 'us-central1'
# registry_id = 'your-registry-id'
# device_id = 'your-device-id'
# version = '0'
# config= 'your-config-data'
print("Set device configuration")
client = iot_v1.DeviceManagerClient()
device_path = client.device_path(project_id, cloud_region, registry_id, device_id)

data = config.encode("utf-8")

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

Ruby

# project_id  = "Your Google Cloud project ID"
# location_id = "The Cloud region the registry is located in"
# registry_id = "The registry to get a device from"
# device_id   = "The identifier of the device to set configurations on"
# data        = "The data, e.g. {fan: on} 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}"

config = Cloudiot::DeviceConfig.new
config.binary_data = data

# Set configuration for the provided device
iot_client.modify_cloud_to_device_config resource, config
puts "Configuration updated!"

デバイス構成を確認する

デバイス構成の最新の 10 バージョンは、Cloud Platform Console、API、gcloud を使用して確認できます。

Console

  1. Google Cloud コンソールで、[レジストリ] ページに移動します。

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

  2. 構成を更新するデバイスが含まれるレジストリの ID をクリックします。

  3. 左側のレジストリ メニューで、[デバイス] をクリックします。

  4. 構成を更新するデバイスの ID をクリックします。

  5. [構成と状態の履歴] をクリックします。 チェックボックスを使用して、構成の履歴、状態の履歴、またはその両方を表示します。デフォルトでは、両方が表示されます。

    • 緑色のチェックマークは、デバイスが構成を確認したことを示します(MQTT のみ)。
    • 黄色の警告記号は、デバイスがまだ構成を確認していないことを示します(MQTT のみ)。
    • 行をクリックして、完全な構成や状態データ(JSON 形式)、タイムスタンプ、バージョンを取得できます。
  6. [Compare] をクリックして設定データと状態データを比較します。このビューは、構成をデバッグするのに役立ちます。また、MQTT を使用している場合は、デバイスが特定の構成バージョンを認識していることを確認するのに役立ちます。(HTTP ブリッジは構成の確認応答をサポートしていません)。

gcloud

最新の構成を取得するには、gcloud iot devices configs list コマンドと describe コマンドを実行します。

gcloud iot devices configs list \
  --device=DEVICE_ID \
  --registry=REGISTRY_ID \
  --region=REGION \
  [--filter=EXPRESSION]
  [--limit=LIMIT]
  [--sort-by=[FIELD,...]]
gcloud iot devices configs describe \
  --device=DEVICE_ID \
  --registry=REGISTRY_ID \
  --region=REGION

API

API を介してデバイス構成を確認するには、Device configVersions.list リクエストを使用します。

C#

public static object GetDeviceConfigurations(string projectId, string cloudRegion, string registryId, string deviceId)
{
    var cloudIot = CreateAuthorizedClient();
    // The resource name of the location associated with the key rings.
    var name = $"projects/{projectId}/locations/{cloudRegion}/registries/{registryId}/devices/{deviceId}";

    try
    {
        Console.WriteLine("Configurations: ");
        var res = cloudIot.Projects.Locations.Registries.Devices.ConfigVersions.List(name).Execute();
        res.DeviceConfigs.ToList().ForEach(config =>
        {
            Console.WriteLine($"Version: {config.Version}");
            Console.WriteLine($"\tUpdated: {config.CloudUpdateTime}");
            Console.WriteLine($"\tDevice Ack: {config.DeviceAckTime}");
            Console.WriteLine($"\tData: {config.BinaryData}");
        });
    }
    catch (Google.GoogleApiException e)
    {
        Console.WriteLine(e.Message);
        if (e.Error != null) return e.Error.Code;
        return -1;
    }
    return 0;
}

Go


// getDeviceConfigs retrieves and lists device configurations.
func getDeviceConfigs(w io.Writer, projectID string, region string, registryID string, device string) ([]*cloudiot.DeviceConfig, 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
	}

	path := fmt.Sprintf("projects/%s/locations/%s/registries/%s/devices/%s", projectID, region, registryID, device)
	response, err := client.Projects.Locations.Registries.Devices.ConfigVersions.List(path).Do()
	if err != nil {
		return nil, err
	}

	for _, config := range response.DeviceConfigs {
		fmt.Fprintf(w, "%d : %s\n", config.Version, config.BinaryData)
	}

	return response.DeviceConfigs, nil
}

Java

/** List all of the configs for the given device. */
protected static void listDeviceConfigs(
    String deviceId, String projectId, String cloudRegion, String registryName)
    throws GeneralSecurityException, IOException {
  GoogleCredentials credential =
      GoogleCredentials.getApplicationDefault().createScoped(CloudIotScopes.all());
  JsonFactory jsonFactory = GsonFactory.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);

  System.out.println("Listing device configs for " + devicePath);
  List<DeviceConfig> deviceConfigs =
      service
          .projects()
          .locations()
          .registries()
          .devices()
          .configVersions()
          .list(devicePath)
          .execute()
          .getDeviceConfigs();

  for (DeviceConfig config : deviceConfigs) {
    System.out.println("Config version: " + config.getVersion());
    System.out.println("Contents: " + config.getBinaryData());
    System.out.println();
  }
}

Node.js

// const cloudRegion = 'us-central1';
// const deviceId = 'my-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 listDeviceConfigVersions() {
  // Construct request
  const devicePath = iotClient.devicePath(
    projectId,
    cloudRegion,
    registryId,
    deviceId
  );

  const [response] = await iotClient.listDeviceConfigVersions({
    name: devicePath,
  });
  const configs = response.deviceConfigs;

  if (configs.length === 0) {
    console.log(`No configs for device: ${deviceId}`);
  } else {
    console.log('Configs:');
  }

  for (let i = 0; i < configs.length; i++) {
    const config = configs[i];
    console.log(
      'Config:',
      config,
      '\nData:\n',
      config.binaryData.toString('utf8')
    );
  }
}

listDeviceConfigVersions();

PHP

use Google\Cloud\Iot\V1\DeviceManagerClient;

/**
 * Lists versions of a device config in descending order (newest first).
 *
 * @param string $registryId IOT Device Registry ID
 * @param string $deviceId IOT Device ID
 * @param string $projectId Google Cloud project ID
 * @param string $location (Optional) Google Cloud region
 */
function get_device_configs(
    $registryId,
    $deviceId,
    $projectId,
    $location = 'us-central1'
) {
    print('Getting device configs' . PHP_EOL);

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

    $configs = $deviceManager->listDeviceConfigVersions($deviceName);

    foreach ($configs->getDeviceConfigs() as $config) {
        print('Config:' . PHP_EOL);
        printf('    Version: %s' . PHP_EOL, $config->getVersion());
        printf('    Data: %s' . PHP_EOL, $config->getBinaryData());
        printf('    Update Time: %s' . PHP_EOL,
            $config->getCloudUpdateTime()->toDateTime()->format('Y-m-d H:i:s'));
    }
}

Python

# project_id = 'YOUR_PROJECT_ID'
# cloud_region = 'us-central1'
# registry_id = 'your-registry-id'
# device_id = 'your-device-id'
client = iot_v1.DeviceManagerClient()
device_path = client.device_path(project_id, cloud_region, registry_id, device_id)

configs = client.list_device_config_versions(request={"name": device_path})

for config in configs.device_configs:
    print(
        "version: {}\n\tcloudUpdateTime: {}\n\t data: {}".format(
            config.version, config.cloud_update_time, config.binary_data
        )
    )

return configs

Ruby

# project_id  = "Your Google Cloud project ID"
# location_id = "The Cloud region the registry is located in"
# registry_id = "The registry to get a device from"
# device_id   = "The identifier of the device to get configurations for"

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}"

# List the configurations for the provided device
configs = iot_client.list_project_location_registry_device_config_versions(
  resource
)
configs.device_configs.each do |config|
  puts "Version [#{config.version}]: #{config.binary_data}"
end