Skip to main content

Google IoT Core

Latest Version: 1.0.0

This library allows your agent code to work with Google IoT Core.

Note The Google IoT Core integration is currently in public Beta. Before proceeding, please sign up for access to the Google IoT Core integration using this link.

This version of the library supports the following functionality:

The library is designed to work with different types of transports (HTTP or MQTT), but only MQTT transport is implemented at this point.

You can view the library’s source code on GitHub. Click here to see information on other versions of this library.

To add this library to your project, add #require "GoogleIoTCore.agent.lib.nut:1.0.0" to the top of your agent code.

Library Usage

The library API specification is described here, and a set of working examples are provided to help you make use of the library.

Prerequisites

Before using the library you need to have a Google IoT Core account and the following information:

Google IoT Core setup is described in the instructions for the examples.

The Project ID, Cloud Region and Registry ID may be the same for all of your devices and be hardcoded into your application. Alternatively, you may choose to obtain them — for example, from your own server — when the application is first used.

For every device you also need to have:

  • A Device ID.
  • At least one public/private key pair.

The Registry ID-Device ID combination must be unique for every device in your Project. For more information on public/private keys and Device IDs, please see the Authentication And Registration section.

Finally, you should decide which transport your application/device is going to use for communication with Google IoT Core. By default, MQTT (Message Queuing Telemetry Transport) with default options is used. If you want to configure MQTT yourself, you need to create an instance of the GoogleIoTCore.MqttTransport class.

Instantiation

To start working with the library, you should create an instance of the GoogleIoTCore.Client class. All of the settings discussed in the Prerequisites section above are passed into the client's constructor. In addition, the constructor has further options which control the behavior of the library.

It is possible to instantiate several clients but note that Google IoT Core supports only one connection per device.

Example

#require "GoogleIoTCore.agent.lib.nut:1.0.0"

const GOOGLE_IOT_CORE_PROJECT_ID   = "<YOUR_GOOGLE_IOT_CORE_PROJECT_ID>";
const GOOGLE_IOT_CORE_CLOUD_REGION = "<YOUR_GOOGLE_IOT_CORE_CLOUD_REGION>";
const GOOGLE_IOT_CORE_REGISTRY_ID  = "<YOUR_GOOGLE_IOT_CORE_REGISTRY_ID>";
const GOOGLE_IOT_CORE_DEVICE_ID    = "<YOUR_GOOGLE_IOT_CORE_DEVICE_ID>";
const GOOGLE_IOT_CORE_PRIVATE_KEY  = "<YOUR_GOOGLE_IOT_CORE_PRIVATE_KEY>";

// Instantiate a client
client <- GoogleIoTCore.Client(GOOGLE_IOT_CORE_PROJECT_ID,
                               GOOGLE_IOT_CORE_CLOUD_REGION,
                               GOOGLE_IOT_CORE_REGISTRY_ID,
                               GOOGLE_IOT_CORE_DEVICE_ID,
                               GOOGLE_IOT_CORE_PRIVATE_KEY);

Authentication And Registration

Google IoT Core security is described here. A public/private RSA key pair must exist for every device, and the device must be registered with Google IoT Core. Elliptic Curve (ES) keys are also supported by Google IoT Core, but are not supported by the library.

The public key is saved inside Google IoT Core, so it is used only when registering a device.

The private key is used on the client side to create JSON Web Tokens which are required to authenticate the device to Google IoT Core.

An example of how a public/private RSA key pair can be created is described here.

It is recommended that every device should have its own public/private key pair. Moreover, several key pairs may exist for the same device and be rotated periodically. A key pair may have an expiration time. These and other security recommendations from Google are described here.

Assuming your project has a server, a device initialization process may look like this:

  1. When your application starts for the first time it connects to your server and, optionally, passes the Device ID. It can be generated from the agent ID or any other unique ID in accordance with Google‘s requirements. The first character of the Device ID must be a letter.
  2. If a Device ID is not received from the device's agent, the server generates the Device ID.
  3. The server creates one or more public/private RSA key pairs for the device.
  4. The server registers the device with Google IoT Core.
  5. The server passes the private key(s), the Device ID (if it was not received from the device), as well as other settings mentioned in the Prerequisites section and which are not hardcoded, to your application.
  6. The application initializes the library by passing the settings to the GoogleIoTCore.Client constructor.
  7. The settings must be passed to the library every time the application restarts. For all non-hardcoded settings, you may decide either to obtain them from the server after every restart, or save them locally in agent persistent storage or device-side non-volatile memory.
  8. A new private key should be obtained from the server if the existing key has an expiration time and is about to expire.

The GoogleIoTCore.Client constructor accepts only one private key. At any time your application can call setPrivateKey() to change the current private key, eg. for rotation, or when the key has expired.

See also the Refreshing JSON Web Tokens Automatically section.

Device Self-Registration

The library includes a register() method to self-register a device with Google IoT Core. It may be used for quick prototypes, proof-of-concepts and demos. It is not recommended for production applications.

The register() method requires additional settings to be hardcoded or obtained by an application, eg. from your server:

register() does not require the library to be connected to Google IoT Core. It requires the OAuth 2.0 library.

Example

const GOOGLE_ISS = "<YOUR_GOOGLE_ISS>";
const GOOGLE_SECRET_KEY = "<YOUR_GOOGLE_SECRET_KEY>";
const GOOGLE_IOT_CORE_PUBLIC_KEY = "<YOUR_GOOGLE_IOT_CORE_PUBLIC_KEY>";

function onRegistered(err) {
    if (err != 0) {
        server.error("Registration error: code = " + err);
        return;
    }

    server.log("Successfully registered");
    client.connect();
}

client.register(GOOGLE_ISS, GOOGLE_SECRET_KEY, GOOGLE_IOT_CORE_PUBLIC_KEY, onRegistered);

Connection

Tasks such as publishing telemetry data, reporting device state and receiving device configurations require the library to be connected to Google IoT Core.

To connect a GoogleIoTCore.Client instance, call the connect() method. Google IoT Core supports only one connection per device.

Your application can monitor a connection state using the isConnected() method, or the optional onConneced and onDisconnected callbacks. The callbacks may be specified in the GoogleIoTCore.Client constructor or set/reset later using the setOnConnected() and/or setOnDisconnected() methods.

You can disconnect from Google IoT Core at any time by calling the disconnect() method, and reconnect by calling connect() again.

Note Google IoT Core can autonomously disconnect your device: for example, if the JSON Web Token expires (see Refreshing JSON Web Tokens Automatically).

Example

const GOOGLE_IOT_CORE_PROJECT_ID    = "<YOUR_GOOGLE_IOT_CORE_PROJECT_ID>";
const GOOGLE_IOT_CORE_CLOUD_REGION  = "<YOUR_GOOGLE_IOT_CORE_CLOUD_REGION>";
const GOOGLE_IOT_CORE_REGISTRY_ID   = "<YOUR_GOOGLE_IOT_CORE_REGISTRY_ID>";
const GOOGLE_IOT_CORE_DEVICE_ID     = "<YOUR_GOOGLE_IOT_CORE_DEVICE_ID>";
const GOOGLE_IOT_CORE_PRIVATE_KEY   = "<YOUR_GOOGLE_IOT_CORE_PRIVATE_KEY>";

function onConnected(err) {
    if (err != 0) {
        server.error("Connect failed: " + err);
        return;
    }

    server.log("Connected");
    // Here is a good place to enable configuration reception
}

function onDisconnected(err) {
    if (err != 0) {
        server.error("Disconnected unexpectedly with code: " + err);

        // Reconnect if disconnection was not initiated by the application
        client.connect();
    } else {
        server.log("Disconnected by application");
    }
}

// Instantiate and connect a client
client <- GoogleIoTCore.Client(GOOGLE_IOT_CORE_PROJECT_ID,
                               GOOGLE_IOT_CORE_CLOUD_REGION,
                               GOOGLE_IOT_CORE_REGISTRY_ID,
                               GOOGLE_IOT_CORE_DEVICE_ID,
                               GOOGLE_IOT_CORE_PRIVATE_KEY,
                               onConnected,
                               onDisconnected);
client.connect();

Refreshing JSON Web Tokens Automatically

A JSON Web Token always has an expiration time, which is not the same as a private/public key expiration time. If the token has expired, Google IoT Core will disconnect the device. To prevent the disconnection, the token must be updated before its expiration.

The library implements token updating, which is enabled by default. For MQTT connections, the token is updated as follows:

  1. A timer fires when the current token is near to expiration.
  2. The library waits for all current MQTT operations to be completed.
  3. The library generates a new token using the current private key.
  4. The library disconnects from the MQTT broker.
  5. The library re-connects to the MQTT broker using the new token as thee MQTT client's password.
  6. The library subscribes to the topics to which it was subscribed before the reconnection.
  7. The library sets a new timer to fire just before the new token is due to expire.

The library performs all these operations automatically and invisibly to an application. The onDisconnected and onConnected callbacks are not called. Any API calls made by the application during the update process are retained in a queue and processed once the token has been successfully updated. If the token can’t be updated, the onDisconnected callback is executed if set.

To generate a new token, the library uses the private key provided to the client. At any time the key can be updated by an application by calling the setPrivateKey() method. The new key will be used the next time the token needs to be updated.

To stop the token being updated automatically, you can set the tokenAutoRefresh option in the GoogleIoTCore.Client constructor to false. You may need to do this if you wish to, for example, rotate the private key with every token update. In this case, your application may implement the following logic:

  1. Set onConnected and onDisconnected() callbacks.
  2. When the current JSON Web token has expired, Google IoT Core disconnects the device.
  3. The onDisconnected() callback is executed by the library.
  4. Call setPrivateKey() to change the current private key.
  5. Call connect().
  6. When the device is connected, the onConnected callback is executed by the library.
  7. Re-enable configuration reception, if needed.

Publishing Telemetry

Telemetry events can be published as soon as the client has successfully connected.

Call publish() to send any application-specific data to Google IoT Core.

Example

// Publish a telemetry event without a callback
client.publish("some data", null);

function onPublished(data, err) {
    if (err != 0) {
        server.error("Publish telemetry error: code = " + err);

        // Trying to publish again in case of any error
        client.publish("some data", null, onPublished);
        return;
    }

    server.log("Telemetry has been published. Data = " + data);
}

// Publish a telemetry event with a callback
client.publish("some data", null, onPublished);

Reporting State

A device’s state can be reported as soon as the client has successfully connected.

Call reportState() to send an application-specific device state message to Google IoT Core.

This functionality may work with configuration reception or be used independently.

Example

client.reportState("some state", onReported);

function onReported(state, err) {
    if (err != 0) {
        server.error("Report state error: code = " + err);
        return;
    }

    server.log("State has been reported!");
}

Configuration Reception

Receiving configuration information is disabled by default and should be enabled every time that the client has successfully connected. Call enableCfgReceiving() to do this, or to disable the functionality manually.

Configuration reception may be used to transfer application-specific data from Google IoT Core to a device. For example:

  • A new configuration (settings, firmware, etc.);
  • A command to execute (reboot, etc.);
  • Any other data (messages, etc.).

If a request (eg. a configuration or a command) from Google IoT Core expects an answer from a device, then device state reports can be used to send the response. However, this is entirely application-specific.

Example

function onConfigReceived(config) {
    server.log("Configuration received: " + config.tostring());
}

function onDone(err) {
    if (err != 0) {
        server.error("Enabling configuration receiving failed: " + err);
    } else {
        server.log("Configuration reception enabled successfully");
    }
}

client.enableCfgReceiving(onConfigReceived, onDone);

Pending Requests

Telemetry data and device state report requests are made asynchronously, so several operations can be processed concurrently. But only limited number of pending operations of the same type is allowed. This number can be changed in the GoogleIoTCore.Client constructor's options. If you exceed this limit, the GOOGLE_IOT_CORE_ERROR_OP_NOT_ALLOWED_NOW error will be returned in response to your call.

Error Processing

Most of the library’s methods return results via callbacks. Every callback includes an error parameter which indicates if the operation has been executed successfully (error is 0) or has failed. An error code indicates the reason for the failure:

Error Code Error Name Description
-99..-1 and 128 N/A MQTT-specific errors
1000 GOOGLE_IOT_CORE_ERROR_NOT_CONNECTED The client is not connected
1001 GOOGLE_IOT_CORE_ERROR_ALREADY_CONNECTED The client is already connected
1002 GOOGLE_IOT_CORE_ERROR_OP_NOT_ALLOWED_NOW The operation is not allowed now. For example, the same operation is already in flight
1003 GOOGLE_IOT_CORE_ERROR_TOKEN_REFRESHING An error occurred while refreshing the token. This error code can only be passed into the onDisconnected callback
1004 GOOGLE_IOT_CORE_ERROR_ALREADY_REGISTERED Another device is already registered with the same Device ID
1010 GOOGLE_IOT_CORE_ERROR_GENERAL A general error

GoogleIoTCore Library Specification

GoogleIoTCore.Client Class Usage

Constructor: GoogleIoTCore.Client(projectId, cloudRegion, registryId, deviceId, privateKey[, onConnected][, onDisconnected][, transport][, options])

This method returns a new GoogleIoTCore.Client instance.

Parameters

Parameter Data Type Required Description
projectId String Yes The Project ID
cloudRegion String Yes The Cloud region
registryId String Yes The Registry ID
deviceId String Yes The Device ID
privateKey String Yes The private key
onConnected Function Optional A callback executed every time the client is connected. It is a good place to call enableCfgReceiving() if this functionality is needed
onDisconnected Function Optional A callback executed every time the client is disconnected
transport GoogleIoTCore.*Transport
instance
Optional The default transport is a GoogleIoTCore.MqttTransport instance with default MQTT options
options Table Optional Additional instance settings (see below)

Options Table Keys

These additional settings affect the client's behavior and therefore the operations it is asked to perform. Every setting listed below is optional and has a default value.

Key Value Type Description
maxPendingSetStateRequests Integer Maximum number of pending state report operations allowed. Default: 3
maxPendingPublishTelemetryRequests Integer Maximum amount of pending telemetry publishing operations allowed. Default: 3
tokenTTL Integer A JWT token's lifetime in seconds. Default: 3600
tokenAutoRefresh Boolean Enable automatic JWT refreshing. Default: true

Callbacks

The callbacks that may be passed into onConnected and/or onDisconnect have one parameter of their own:

Parameter Data Type Description
error Integer 0 if the operation completed successfully, otherwise an error code

GoogleIoTCore.Client Class Methods

setOnConnected(callback)

This method sets the onConnected callback.

Parameters

Parameter Data Type Required Description
callback Function Yes The function to be called when the client has connected

Return Value

Nothing.

setOnDisconnected(callback)

This method sets the onDisconnected callback.

Parameters

Parameter Data Type Required Description
callback Function Yes The function to be called when the client has disconnected

Return Value

Nothing.

setPrivateKey(privateKey)

This method sets the private key.

Parameters

Parameter Data Type Required Description
privateKey String Yes The private key value

Return Value

Nothing.

register(iss, secret, publicKey[, onRegistered][, name][, keyFormat])

This method registers the device in Google IoT Core. It performs a minimal registration: only one private-public key pair, without expiration, is registered.

The method attempts to see if there is a device with the same ID as the one specified in the client’s constructor. If it finds a match, it compares the device's public key with the supplied key. If the keys match, the methods succeeds — it is assumed that the specified device has already been registered. Otherwise, it is assumed that another device is registered with the same ID, and the method returns the GOOGLE_IOT_CORE_ERROR_ALREADY_REGISTERED error.

If no device match is found, the method tries to register a new device.

Important If you intend to use this method, you must add #require "OAuth2.agent.lib.nut:2.0.0" to the top of your agent code.

Parameters

Parameter Data Type Required Description
iss String Yes The JWT issuer
secret String Yes The JWT sign secret key
publicKey String Yes The device's public key. It must correspond to the private key set for the client
onRegistered Function Optional The callback executed when the device is registered or an error occurs
name String Optional The device's name
keyFormat String Optional The public key format. Default: "RSA_X509_PEM"

onRegistered

The onRegistered parameter takes a function that has one parameter of its own:

Parameter Data Type Description
error Integer 0 if the operation completed successfully, otherwise an error code

Return Value

Nothing. The result of the operation may be obtained via the onRegistered callback, if specified.

connect()

This method opens a connection to Google IoT Core.

If the client is already connected, the onConnected callback will be called with the GOOGLE_IOT_CORE_ERROR_ALREADY_CONNECTED error.

Google IoT Core supports only one connection per device.

Return Value

Nothing. The result of the operation may be obtained via the onConnected callback specified in the client's constructor or set by calling setOnConnected().

disconnect()

This method closes the connection to Google IoT Core. It does nothing if the connection is already closed.

Return Value

Nothing. When the disconnection is completed, the onDisconnected callback is called, if specified in the client's constructor or set by calling setOnDisconnected().

isConnected()

This method checks if the client is connected to Google IoT Core.

Return Value

Boolean — true if the client is connected, otherwise false.

publish(data[, subfolder][, onPublished])

This method publishes a telemetry event to Google IoT Core.

Parameters

Parameter Data Type Required Description
data String or blob Yes Application-specific data. The application can use the Serializer library to convert Squirrel objects to blobs
subfolder String Optional The sub-folder can be used as an event category or classification. For more information, please see here
onPublished Function Optional A callback executed when the data is considered as published or an error occurs

onPublished

The onPublished parameter takes a function that has two parameters of its own:

Parameter Data Type Description
data String or blob The original data passed into publish()
error Integer 0 if the operation completed successfully, otherwise an error code

Return Value

Nothing. The result of the operation may be obtained via the onPublished callback, if specified.

enableCfgReceiving(onReceive[, onDone])

This method enables/disables the reception of configuration data from Google IoT Core. It is disabled by default and after every successful call to connect().

To enable the feature, specify the onReceive callback. To disable the feature, pass null as that callback.

Parameter Data Type Required? Description
onReceive Function Yes A Callback called every time a configuration is received from Google IoT Core
onDone Function Optional A Callback called when the operation is complete or an error occurs

onReceive

The onReceive parameter takes a function that has one parameter of its own:

Parameter Data Type Description
configuration Blob The configuration received. The application can use the Serializer library to convert blobs to Squirrel objects

onDone

The onDone parameter takes a function that has one parameter of its own:

Parameter Data Type Description
error Integer 0 if the operation completed successfully, otherwise an error code

Return Value

Nothing. The result of the operation may be obtained via the onDone callback, if specified.

reportState(state[, onReported])

This method reports a device's state to Google IoT Core.

Parameters

Parameter Data Type Required Description
state String or blob Yes A device state record. The application can use the Serializer library to convert Squirrel objects to blobs
onReported Function Optional A callback executed when the operation is completed or an error occurs

onReported

The onReported parameter takes a function that has two parameter of its own:

Parameter Data Type Description
state String or blob The original state passed into reportState()
error Integer 0 if the operation completed successfully, otherwise an error code

Return Value

Nothing. The result of the operation may be obtained via the onReported callback, if specified.

setDebug(value)

This method enables (value is true) or disables (value is false) the client debug output (including error logging). It is disabled by default.

Return Value

Nothing.

GoogleIoTCore.MqttTransport Class Usage

Constructor: GoogleIoTCore.MqttTransport([options])

This method returns a new GoogleIoTCore.MqttTransport instance.

Parameters

Parameter Data Type Required Description
options Table Optional Instance settings

Options

These settings affect the transport's behavior and the operations. Every setting is optional and has a default value.

Key Value Type Description
url String MQTT broker URL formatted as ssl://<hostname>:<port>. Default: "ssl://mqtt.googleapis.com:8883"
qos Integer The MQTT quality of service setting. Google IoT Core supports QoS 0 and 1 only. Default: 0
keepAlive Integer The MQTT keep-alive time in seconds. For more information, please see here. Default: 60

Note Google IoT Core does not support the retain MQTT flag.

Release History

The Electric Imp Dev Center documents the latest version of the library. For past versions, please see the Electric Imp public GitHub repos listed below.

Version Source Code Notes
1.0.0 GitHub Initial release

License

This library is licensed under the MIT License.