Latest Version: 1.0.0
This library allows your agent code to work with Google IoT Core. 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 include this library in your project, add
#require "GoogleIoTCore.agent.lib.nut:1.0.0"
at the top of your agent code.
The library API specification is described here, and a set of working examples are provided to help you make use of the library.
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:
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.
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.
#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);
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:
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.
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.
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);
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).
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();
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:
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:
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.
// 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);
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.
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!");
}
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:
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.
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);
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.
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 |
This method returns a new GoogleIoTCore.Client instance.
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) |
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 |
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 |
This method sets the onConnected callback.
Parameter | Data Type | Required | Description |
---|---|---|---|
callback | Function | Yes | The function to be called when the client has connected |
Nothing.
This method sets the onDisconnected callback.
Parameter | Data Type | Required | Description |
---|---|---|---|
callback | Function | Yes | The function to be called when the client has disconnected |
Nothing.
This method sets the private key.
Parameter | Data Type | Required | Description |
---|---|---|---|
privateKey | String | Yes | The private key value |
Nothing.
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.
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" |
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 |
Nothing. The result of the operation may be obtained via the onRegistered callback, if specified.
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.
Nothing. The result of the operation may be obtained via the onConnected callback specified in the client's constructor or set by calling setOnConnected().
This method closes the connection to Google IoT Core. It does nothing if the connection is already closed.
Nothing. When the disconnection is completed, the onDisconnected callback is called, if specified in the client's constructor or set by calling setOnDisconnected().
This method checks if the client is connected to Google IoT Core.
Boolean — true
if the client is connected, otherwise false
.
This method publishes a telemetry event to Google IoT Core.
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 |
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 |
Nothing. The result of the operation may be obtained via the onPublished callback, if specified.
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 |
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 |
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 |
Nothing. The result of the operation may be obtained via the onDone callback, if specified.
This method reports a device's state to Google IoT Core.
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 |
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 |
Nothing. The result of the operation may be obtained via the onReported callback, if specified.
This method enables (value is true
) or disables (value is false
) the client debug output (including error logging). It is disabled by default.
Nothing.
This method returns a new GoogleIoTCore.MqttTransport instance.
Parameter | Data Type | Required | Description |
---|---|---|---|
options | Table | Optional | Instance settings |
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.
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 |
This library is licensed under the MIT License.