Configuring devices

With Cloud IoT Core, you can control a device by modifying its configuration. A device configuration is an arbitrary, user-defined blob of data. After a configuration has been applied to a device, the device can report its state to Cloud IoT Core.

Device configuration works differently in the MQTT and HTTP bridges. See below for details.

For more information, see Devices, Configuration, and State.

Limits

Configuration updates are limited to 1 update per second, per device. However, for best results, device configuration should be updated much less often — at most, once every 10 seconds.

The update rate is calculated as the time between the most recent server acknowledgment and the next update request.

Protocol differences

MQTT

Devices using MQTT can subscribe to a special MQTT topic for configuration updates:

/devices/{device-id}/config

When a device subscribes to the configuration topic, the MQTT bridge responds with an MQTT SUBACK message, which contains the granted QoS for the config topic (0 or 1), or 128 if an error occurred.

After initially subscribing, the device receives the latest configuration in a message's payload, and will receive additional configuration updates as they are pushed to Cloud IoT Core.

The following samples illustrate how to retrieve configuration updates on a device over MQTT:

Java

static MqttCallback mCallback;

/** 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 {
  DateTime now = new DateTime();
  // 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(now.toDate())
          .setExpiration(now.plusMinutes(20).toDate())
          .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 {
  DateTime now = new DateTime();
  // 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(now.toDate())
          .setExpiration(now.plusMinutes(20).toDate())
          .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. */
public 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");

  DateTime iat = new DateTime();
  if (algorithm.equals("RS256")) {
    connectOptions.setPassword(createJwtRsa(projectId, privateKeyFile).toCharArray());
  } else if (algorithm.equals("ES256")) {
    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(mqttClientId));

  // 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 (!client.isConnected() && totalRetryTimeMs < maxConnectRetryTimeElapsedMillis) {
    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;
}

public static void sendDataFromDevice(
    MqttClient client, String deviceId, String messageType, String data) throws MqttException {
  if (!messageType.equals("events") && !messageType.equals("state")) {
    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());
  message.setQos(1);
  client.publish(dataTopic, message);
  System.out.println("Data sent");
}

/** Sends data on behalf of a bound device using the Gateway. */
public 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);
  DeviceRegistryExample.attachDeviceToGateway(client, deviceId);
  sendDataFromDevice(client, deviceId, messageType, telemetryData);
  DeviceRegistryExample.detachDeviceFromGateway(client, deviceId);
}

public 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.
  DeviceRegistryExample.attachDeviceToGateway(client, deviceId);
  attachCallback(client, deviceId);

  DeviceRegistryExample.detachDeviceFromGateway(client, deviceId);
}

public 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");

  DateTime iat = new DateTime();
  if (options.algorithm.equals("RS256")) {
    connectOptions.setPassword(
        createJwtRsa(options.projectId, options.privateKeyFile).toCharArray());
  } else if (options.algorithm.equals("ES256")) {
    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 (!client.isConnected() && totalRetryTimeMs < maxConnectRetryTimeElapsedMillis) {
    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 = options.messageType.equals("event") ? "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 = ((new DateTime()).getMillis() - iat.getMillis()) / 1000;
    if (secsSinceRefresh > (options.tokenExpMins * 60)) {
      System.out.format("\tRefreshing token after: %d seconds\n", secsSinceRefresh);
      iat = new DateTime();
      if (options.algorithm.equals("RS256")) {
        connectOptions.setPassword(
            createJwtRsa(options.projectId, options.privateKeyFile).toCharArray());
      } else if (options.algorithm.equals("ES256")) {
        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();
      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());
    message.setQos(1);
    client.publish(mqttTopic, message);

    if (options.messageType.equals("event")) {
      // 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. */
public static void attachCallback(MqttClient client, String deviceId) throws MqttException {
  mCallback =
      new MqttCallback() {
        @Override
        public void connectionLost(Throwable cause) {
          // Do nothing...
        }

        @Override
        public void messageArrived(String topic, MqttMessage message) throws Exception {
          String payload = new String(message.getPayload());
          System.out.println("Payload : " + payload);
          // TODO: Insert your parsing / handling of the configuration message here.
        }

        @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

The step where the device subscribes to the configuration topic is highlighted below:
// const deviceId = `myDevice`;
// const registryId = `myRegistry`;
// const region = `us-central1`;
// const algorithm = `RS256`;
// const privateKeyFile = `./rsa_private.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.
let connectionArgs = {
  host: mqttBridgeHostname,
  port: mqttBridgePort,
  clientId: mqttClientId,
  username: 'unused',
  password: createJwt(projectId, privateKeyFile, algorithm),
  protocol: 'mqtts',
  secureProtocol: 'TLSv1_2_method',
};

// Create a client, and connect to the Google MQTT bridge.
let iatTime = parseInt(Date.now() / 1000);
let 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 === `/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

The step where the device subscribes to the configuration topic is highlighted below:
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 = mqtt.Client(
            client_id=('projects/{}/locations/{}/registries/{}/devices/{}'
                       .format(
                               project_id,
                               cloud_region,
                               registry_id,
                               device_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

If you're using the HTTP bridge, devices must explicitly request new configurations.

The following samples illustrate how to retrieve configuration updates on a device over HTTP:

Java

/** Publish an event or state message using Cloud IoT Core via the HTTP API. */
public static void getConfig(String urlPath, String token, String projectId,
    String cloudRegion, String registryId, String deviceId, String version)
    throws UnsupportedEncodingException, IOException, JSONException, ProtocolException {
  // 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());
  InputStream in = res.getContent();

  System.out.println(CharStreams.toString(new InputStreamReader(in, Charsets.UTF_8)));
}

Node.js

function getConfig(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',
    },
    json: true,
    retries: 5,
    shouldRetryFn: function(incomingHttpMessage) {
      console.log('Retry?');
      return incomingHttpMessage.statusMessage !== 'OK';
    },
  };
  console.log(JSON.stringify(request.RetryStrategies));
  request(options, function(error, response, body) {
    if (error) {
      console.error('Received error: ', error);
    } else if (response.body.error) {
      console.error(`Received error: ${JSON.stringify(response.body.error)}`);
    } else {
      console.log('Received config', JSON.stringify(body));
    }
  });
}

Python

def get_config(
        version, message_type, base_url, project_id, cloud_region, registry_id,
        device_id, jwt_token):
    headers = {
            'authorization': 'Bearer {}'.format(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('Error getting config: {}, retrying'.format(resp.status_code))
        raise AssertionError('Not OK response: {}'.format(resp.status_code))

    return resp

Updating and reverting device configuration

You can update and revert device configuration using Cloud Platform Console, the Cloud IoT Core API, or gcloud.

Console

To update the device configuration:

  1. Go to the Registries page in GCP Console.

    Go to the Registries page

  2. Click the ID of the registry that contains the device.

  3. In the registry menu on the left, click Devices.

  4. Click the ID of the device whose configuration you want to update.

  5. At the top of the page, click Update config.

  6. Select a format for the configuration and paste the data in the Configuration box.

  7. Click Send to device.

See Protocol differences for information on how devices receive the new configuration.

To revert the device configuration to a previous version:

  1. Go to the Registries page in GCP Console.

    Go to the Registries page

  2. Click the ID of the registry that contains the device whose configuration you want to revert.

  3. In the registry menu on the left, click Devices.

  4. Click the ID of the device whose configuration you want to revert.

  5. Click Configuration & state history. Use the checkboxes to display configuration history, state history, or both. By default, both are shown.

    • A green checkmark indicates that the device has acknowledged the configuration (MQTT only).
    • A yellow warning symbol indicates that the device has not yet acknowledged the configuration (MQTT only).
    • Click a row to get the full configuration or state data in JSON, as well as the timestamp and version.
  6. Click Compare to compare the configuration data with the state data. This view can help you debug configurations and, if you are using MQTT, make sure devices have acknowledged specific configuration versions. (The HTTP bridge does not support acknowledgment of configurations.)

  7. Click the version you want to revert to.

    To see a text version of the configuration details, select Text in the Format list.

  8. Click Revert, ensure you've selected the correct version, then click Revert.

gcloud

To update or revert a device configuration, run the gcloud iot devices configs update command:

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]

Devices will be updated according to the protocol they use.

API

To update or revert device configuration via the API, use the Device modifyCloudToDeviceConfig method, specifying the new or previous configuration in the config field. You can also specify a configuration when creating a device, and then use modifyCloudToDeviceConfig to change it later.

The following samples illustrate how to update a device's configuration:

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). */
public static void setDeviceConfiguration(
    String deviceId,
    String projectId,
    String cloudRegion,
    String registryName,
    String data,
    long version)
    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);

  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("UTF-8"));
  req.setBinaryData(encPayload);

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

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

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 data = 'test-data';
// const version = 0;
const parentName = `projects/${projectId}/locations/${cloudRegion}`;
const registryName = `${parentName}/registries/${registryId}`;

const binaryData = Buffer.from(data).toString('base64');
const request = {
  name: `${registryName}/devices/${deviceId}`,
  versionToUpdate: version,
  binaryData: binaryData,
};

client.projects.locations.registries.devices.modifyCloudToDeviceConfig(
  request,
  (err, data) => {
    if (err) {
      console.log('Could not update config:', deviceId);
      console.log('Message: ', err);
    } else {
      console.log('Success :', data);
    }
  }
);

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

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

config_body = {
    'versionToUpdate': version,
    'binaryData': base64.urlsafe_b64encode(
            config.encode('utf-8')).decode('ascii')
}

return client.projects(
    ).locations().registries(
    ).devices().modifyCloudToDeviceConfig(
    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 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!"

Reviewing device configuration

You can review the last 10 versions of a device configuration using Cloud Platform Console, the API, or gcloud.

Console

  1. Go to the Registries page in GCP Console.

    Go to the Registries page

  2. Click the ID of the registry that contains the device whose configuration you want to update.

  3. In the registry menu on the left, click Devices.

  4. Click the ID of the device whose configuration you want to update.

  5. Click Configuration & state history. Use the checkboxes to display configuration history, state history, or both. By default, both are shown.

    • A green checkmark indicates that the device has acknowledged the configuration (MQTT only).
    • A yellow warning symbol indicates that the device has not yet acknowledged the configuration (MQTT only).
    • Click a row to get the full configuration or state data in JSON, as well as the timestamp and version.
  6. Click Compare to compare the configuration data with the state data. This view can help you debug configurations and, if you are using MQTT, make sure devices have acknowledged specific configuration versions. (The HTTP bridge does not support acknowledgment of configurations.)

gcloud

To get recent configurations, run the gcloud iot devices configs list and describe commands:

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

API

To review device configuration via the API, use a Device configVersions.list request:

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. */
public static void listDeviceConfigs(
    String deviceId, String projectId, String cloudRegion, String registryName)
    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);

  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

// 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 request = {
  name: `${registryName}/devices/${deviceId}`,
};

client.projects.locations.registries.devices.configVersions.list(
  request,
  (err, data) => {
    if (err) {
      console.log('Could not find device:', deviceId);
      console.log(err);
    } else {
      console.log('Configs:', data.data);
    }
  }
);

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

client = get_client(service_account_json)
registry_name = 'projects/{}/locations/{}/registries/{}'.format(
    project_id, cloud_region, registry_id)

device_name = '{}/devices/{}'.format(registry_name, device_id)
devices = client.projects().locations().registries().devices()
configs = devices.configVersions().list(
    name=device_name).execute().get(
    'deviceConfigs', [])

for config in configs:
    print('version: {}\n\tcloudUpdateTime: {}\n\t binaryData: {}'.format(
        config.get('version'),
        config.get('cloudUpdateTime'),
        config.get('binaryData')))

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

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

Send feedback about...

Cloud IoT Core Documentation