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;

/** 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 configTopic = String.format("/devices/%s/config", deviceId);
  client.subscribe(configTopic, 1);

  client.setCallback(mCallback);
}

Node.js

The step where the device subscribes to the configuration topic is highlighted below:
// 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/${argv.projectId}/locations/${argv.cloudRegion}/registries/${argv.registryId}/devices/${argv.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: argv.mqttBridgeHostname,
  port: argv.mqttBridgePort,
  clientId: mqttClientId,
  username: 'unused',
  password: createJwt(argv.projectId, argv.privateKeyFile, argv.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.
client.subscribe(`/devices/${argv.deviceId}/config`);

// 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/${argv.deviceId}/${argv.messageType}`;

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

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

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

client.on('message', (topic, message, packet) => {
  console.log('message received: ', Buffer.from(message, 'base64').toString('ascii'));
});

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)

    # 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)

    # Start the network loop.
    client.loop_start()

    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 device configuration

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

Console

  1. Go to the Device registries page in GCP Console.

    Go to the Device registries page

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

  3. On the Registry details page, click the ID of the device whose configuration you want to update.

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

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

  6. Click Send to device.

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

gcloud

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

gcloud beta 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 device configuration via the API, use the Device modifyCloudToDeviceConfig method, specifying the new 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:

Java

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

function setDeviceConfig (client, deviceId, registryId, projectId,
    cloudRegion, data, version) {
  // [START iot_set_device_config]
  // 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);
        }
      });
  // [END iot_set_device_config]
}

Python

def set_config(
        service_account_json, project_id, cloud_region, registry_id, device_id,
        version, config):
    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()

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 Device registries page in GCP Console.

    Go to the Device registries page

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

  3. On the Registry details page, click the ID of the device whose configuration you want to update.

  4. Click Configuration & state history.

Use the checkboxes to control whether configuration history or state history is shown, or both histories. Click Compare to see whether configuration and state match as you expect them to.

gcloud

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

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

gcloud beta 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:

Java

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

function getDeviceConfigs (client, deviceId, registryId, projectId,
    cloudRegion) {
  // [START iot_get_device_configs]
  // 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);
        }
      });
  // [END iot_get_device_configs]
}

Python

def get_config_versions(
        service_account_json, project_id, cloud_region, registry_id,
        device_id):
    """Lists versions of a device config in descending order (newest first)."""
    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

Send feedback about...

Google Cloud Internet of Things Core