From a1a3ded7f46767a73852c4bd5acb44ddb5b2c0d2 Mon Sep 17 00:00:00 2001 From: Gus Class Date: Tue, 26 Sep 2017 13:47:15 -0700 Subject: [PATCH] Final additions in private beta (IoT) (#861) * Final additions in private beta --- iot/api-client/http_example/README.md | 60 +++++++ iot/api-client/http_example/pom.xml | 62 +++++++ .../cloud/iot/examples/HttpExample.java | 158 ++++++++++++++++++ .../iot/examples/HttpExampleOptions.java | 151 +++++++++++++++++ iot/api-client/manager/README.md | 11 +- iot/api-client/manager/pom.xml | 9 +- .../manager/resources/ec_public.pem | 4 + iot/api-client/manager/resources/rsa_cert.pem | 18 ++ .../iot/examples/DeviceRegistryExample.java | 129 ++++++-------- .../DeviceRegistryExampleOptions.java | 1 + .../example/cloud/iot/examples/ManagerIT.java | 130 ++++++++++++-- iot/api-client/mqtt_example/pom.xml | 15 ++ .../cloud/iot/examples/MqttExample.java | 96 +++++++---- .../iot/examples/MqttExampleOptions.java | 25 +++ 14 files changed, 735 insertions(+), 134 deletions(-) create mode 100644 iot/api-client/http_example/README.md create mode 100644 iot/api-client/http_example/pom.xml create mode 100644 iot/api-client/http_example/src/main/java/com/google/cloud/iot/examples/HttpExample.java create mode 100644 iot/api-client/http_example/src/main/java/com/google/cloud/iot/examples/HttpExampleOptions.java create mode 100644 iot/api-client/manager/resources/ec_public.pem create mode 100644 iot/api-client/manager/resources/rsa_cert.pem diff --git a/iot/api-client/http_example/README.md b/iot/api-client/http_example/README.md new file mode 100644 index 00000000000..079a72c81f5 --- /dev/null +++ b/iot/api-client/http_example/README.md @@ -0,0 +1,60 @@ +# Cloud IoT Core Java HTTP example + +This sample app publishes data to Cloud Pub/Sub using the HTTP bridge provided +as part of Google Cloud IoT Core. + +Note that before you can run the sample, you must configure a Google Cloud +PubSub topic for Cloud IoT Core and register a device as described in the +[parent README](../README.md). + +## Setup + +Run the following command to install the dependencies using Maven: + + mvn clean compile + +## Running the sample + +The following command summarizes the sample usage: + +``` + mvn exec:java \ + -Dexec.mainClass="com.google.cloud.iot.examples.HttpExample" \ + -Dexec.args="-project_id=my-iot-project \ + -registry_id=my-registry \ + -device_id=my-device \ + -private_key_file=rsa_private_pkcs8 \ + -algorithm=RS256" +``` + +For example, if your project ID is `blue-jet-123`, your service account +credentials are stored in your home folder in creds.json and you have generated +your credentials using the [`generate_keys.sh`](../generate_keys.sh) script +provided in the parent folder, you can run the sample as: + +``` + mvn exec:java \ + -Dexec.mainClass="com.google.cloud.iot.examples.HttpExample" \ + -Dexec.args="-project_id=blue-jet-123 \ + -registry_id=my-registry \ + -device_id=my-java-device \ + -private_key_file=../rsa_private_pkcs8 \ + -algorithm=RS256" +``` + +## Reading the messages written by the sample client + +1. Create a subscription to your topic. + +``` + gcloud beta pubsub subscriptions create \ + projects/your-project-id/subscriptions/my-subscription \ + --topic device-events +``` + +2. Read messages published to the topic + +``` + gcloud beta pubsub subscriptions pull --auto-ack \ + projects/my-iot-project/subscriptions/my-subscription +``` diff --git a/iot/api-client/http_example/pom.xml b/iot/api-client/http_example/pom.xml new file mode 100644 index 00000000000..a60c66ba065 --- /dev/null +++ b/iot/api-client/http_example/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + com.google.cloud.iot.examples + cloudiot-http-example + jar + 1.0 + cloudiot-http-example + http://maven.apache.org + + + 1.7 + 1.7 + + + + + doc-samples + com.google.cloud + 1.0.0 + ../../../ + + + + + io.jsonwebtoken + jjwt + 0.7.0 + + + joda-time + joda-time + 2.1 + + + commons-cli + commons-cli + 1.3 + + + org.json + json + 20090211 + + + + diff --git a/iot/api-client/http_example/src/main/java/com/google/cloud/iot/examples/HttpExample.java b/iot/api-client/http_example/src/main/java/com/google/cloud/iot/examples/HttpExample.java new file mode 100644 index 00000000000..d7dd27fa482 --- /dev/null +++ b/iot/api-client/http_example/src/main/java/com/google/cloud/iot/examples/HttpExample.java @@ -0,0 +1,158 @@ +/** + * Copyright 2017, Google, Inc. + * + *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + *

http://www.apache.org/licenses/LICENSE-2.0 + * + *

Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.iot.examples; + +import io.jsonwebtoken.JwtBuilder; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyFactory; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import org.joda.time.DateTime; +import org.json.JSONObject; + +/** + * Java sample of connecting to Google Cloud IoT Core vice via HTTP, using JWT. + * + *

This example connects to Google Cloud IoT Core via HTTP Bridge, using a JWT for device + * authentication. After connecting, by default the device publishes 100 messages at a rate of one + * per second, and then exits. You can change The behavior to set state instead of events by using + * flag -message_type to 'state'. + * + *

To run this example, follow the instructions in the README located in the sample's parent + * folder. + */ +public class HttpExample { + /** Create a Cloud IoT Core JWT for the given project id, signed with the given private key. */ + private static String createJwtRsa(String projectId, String privateKeyFile) throws Exception { + 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(); + } + + private static String createJwtEs(String projectId, String privateKeyFile) throws Exception { + 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("ES256"); + + return jwtBuilder.signWith(SignatureAlgorithm.ES256, kf.generatePrivate(spec)).compact(); + } + + public static void main(String[] args) throws Exception { + HttpExampleOptions options = HttpExampleOptions.fromFlags(args); + if (options == null) { + // Could not parse the flags. + System.exit(1); + } + + // 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", + options.projectId, options.cloudRegion, options.registryId, options.deviceId); + + // This describes the operation that is going to be perform with the device. + String urlSuffix = options.messageType.equals("event") ? "publishEvent" : "setState"; + + String urlPath = + String.format( + "%s/%s/%s:%s", options.httpBridgeAddress, options.apiVersion, devicePath, urlSuffix); + URL url = new URL(urlPath); + System.out.format("Using URL: '%s'\n", urlPath); + + // Create the corresponding JWT depending on the selected algorithm. + String token; + if (options.algorithm.equals("RS256")) { + token = createJwtRsa(options.projectId, options.privateKeyFile); + } else if (options.algorithm.equals("ES256")) { + token = createJwtEs(options.projectId, options.privateKeyFile); + } else { + throw new IllegalArgumentException( + "Invalid algorithm " + options.algorithm + ". Should be one of 'RS256' or 'ES256'."); + } + + // Data sent through the wire has to be base64 encoded. + Base64.Encoder encoder = Base64.getEncoder(); + + // Publish numMessages messages to the HTTP bridge. + 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); + String encPayload = encoder.encodeToString(payload.getBytes("UTF-8")); + + HttpURLConnection httpCon = (HttpURLConnection) url.openConnection(); + httpCon.setDoOutput(true); + httpCon.setRequestMethod("POST"); + + // Adding headers. + httpCon.setRequestProperty("Authorization", String.format("Bearer %s", token)); + httpCon.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + + // Adding the post data. The structure of the data send depends on whether it is event or a + // state message. + JSONObject data = new JSONObject(); + if (options.messageType.equals("event")) { + data.put("binary_data", encPayload); + } else { + JSONObject state = new JSONObject(); + state.put("binary_data", encPayload); + data.put("state", state); + } + httpCon.getOutputStream().write(data.toString().getBytes("UTF-8")); + httpCon.getOutputStream().close(); + + // This will perform the connection as well. + System.out.println(httpCon.getResponseCode()); + System.out.println(httpCon.getResponseMessage()); + + if (options.messageType.equals("event")) { + // Frequently send event payloads (every second) + Thread.sleep(1000); + } else { + // Update state with low frequency (once every 5 seconds) + Thread.sleep(5000); + } + } + System.out.println("Finished loop successfully. Goodbye!"); + } +} diff --git a/iot/api-client/http_example/src/main/java/com/google/cloud/iot/examples/HttpExampleOptions.java b/iot/api-client/http_example/src/main/java/com/google/cloud/iot/examples/HttpExampleOptions.java new file mode 100644 index 00000000000..8d028da8df0 --- /dev/null +++ b/iot/api-client/http_example/src/main/java/com/google/cloud/iot/examples/HttpExampleOptions.java @@ -0,0 +1,151 @@ +/** + * Copyright 2017, Google, Inc. + * + *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + *

http://www.apache.org/licenses/LICENSE-2.0 + * + *

Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.iot.examples; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +/** Command line options for the HTTP example. */ +public class HttpExampleOptions { + String projectId; + String registryId; + String deviceId; + String privateKeyFile; + String algorithm; + String cloudRegion = "us-central1"; + int numMessages = 100; + String httpBridgeAddress = "https://cloudiot-device.googleapis.com"; + String apiVersion = "v1beta1"; + String messageType = "event"; + + /** Construct an HttpExampleOptions class from command line flags. */ + public static HttpExampleOptions fromFlags(String[] args) { + Options options = new Options(); + // Required arguments + options.addOption( + Option.builder() + .type(String.class) + .longOpt("project_id") + .hasArg() + .desc("GCP cloud project name.") + .required() + .build()); + options.addOption( + Option.builder() + .type(String.class) + .longOpt("registry_id") + .hasArg() + .desc("Cloud IoT Core registry id.") + .required() + .build()); + options.addOption( + Option.builder() + .type(String.class) + .longOpt("device_id") + .hasArg() + .desc("Cloud IoT Core device id.") + .required() + .build()); + options.addOption( + Option.builder() + .type(String.class) + .longOpt("private_key_file") + .hasArg() + .desc("Path to private key file.") + .required() + .build()); + options.addOption( + Option.builder() + .type(String.class) + .longOpt("algorithm") + .hasArg() + .desc("Encryption algorithm to use to generate the JWT. Either 'RS256' or 'ES256'.") + .required() + .build()); + + // Optional arguments. + options.addOption( + Option.builder() + .type(String.class) + .longOpt("cloud_region") + .hasArg() + .desc("GCP cloud region.") + .build()); + options.addOption( + Option.builder() + .type(Number.class) + .longOpt("num_messages") + .hasArg() + .desc("Number of messages to publish.") + .build()); + options.addOption( + Option.builder() + .type(String.class) + .longOpt("http_bridge_address") + .hasArg() + .desc("HTTP bridge address.") + .build()); + options.addOption( + Option.builder() + .type(String.class) + .longOpt("api_version") + .hasArg() + .desc("The version to use of the API.") + .build()); + options.addOption( + Option.builder() + .type(String.class) + .longOpt("message_type") + .hasArg() + .desc("Indicates whether message is a telemetry event or a device state message") + .build()); + + CommandLineParser parser = new DefaultParser(); + CommandLine commandLine; + try { + commandLine = parser.parse(options, args); + HttpExampleOptions res = new HttpExampleOptions(); + + res.projectId = commandLine.getOptionValue("project_id"); + res.registryId = commandLine.getOptionValue("registry_id"); + res.deviceId = commandLine.getOptionValue("device_id"); + res.privateKeyFile = commandLine.getOptionValue("private_key_file"); + res.algorithm = commandLine.getOptionValue("algorithm"); + if (commandLine.hasOption("cloud_region")) { + res.cloudRegion = commandLine.getOptionValue("cloud_region"); + } + if (commandLine.hasOption("num_messages")) { + res.numMessages = ((Number) commandLine.getParsedOptionValue("num_messages")).intValue(); + } + if (commandLine.hasOption("http_bridge_address")) { + res.httpBridgeAddress = commandLine.getOptionValue("http_bridge_address"); + } + if (commandLine.hasOption("api_version")) { + res.apiVersion = commandLine.getOptionValue("api_version"); + } + if (commandLine.hasOption("message_type")) { + res.messageType = commandLine.getOptionValue("message_type"); + } + return res; + } catch (ParseException e) { + System.err.println(e.getMessage()); + return null; + } + } +} diff --git a/iot/api-client/manager/README.md b/iot/api-client/manager/README.md index 1c80a53f695..daf4c47125a 100644 --- a/iot/api-client/manager/README.md +++ b/iot/api-client/manager/README.md @@ -11,7 +11,7 @@ Manually install [the provided client library](https://cloud.google.com/iot/reso for Cloud IoT Core to Maven: mvn install:install-file -Dfile=cloud-iot-core-library.jar -DgroupId=com.example.apis \ - -DartifactId=google-api-services-cloudiot -Dversion=v1beta1-rev20170418-1.22.0-SNAPSHOT \ + -DartifactId=google-api-services-cloudiot -Dversion=v1beta1-rev20170926-1.22.0-SNAPSHOT \ -Dpackaging=jar Run the following command to install the libraries and build the sample with @@ -96,7 +96,14 @@ Create a device registry: java -cp target/cloudiot-manager-demo-1.0-jar-with-dependencies.jar \ com.example.cloud.iot.examples.DeviceRegistryExample \ -project_id=blue-jet-123 -pubsub_topic=hello-java \ - -registry_name=hello-java -command=create-registry \ + -registry_name=hello-java -command=create-registry + +Delete a device registry: + + java -cp target/cloudiot-manager-demo-1.0-jar-with-dependencies.jar \ + com.example.cloud.iot.examples.DeviceRegistryExample \ + -project_id=blue-jet-123 -pubsub_topic=hello-java \ + -registry_name=hello-java -command=delete-registry Get a device registry: diff --git a/iot/api-client/manager/pom.xml b/iot/api-client/manager/pom.xml index 7769b851300..8c060ff5f91 100644 --- a/iot/api-client/manager/pom.xml +++ b/iot/api-client/manager/pom.xml @@ -40,18 +40,23 @@ com.google.apis google-api-services-cloudiot - v1beta1-rev20170418-1.22.0-SNAPSHOT + v1beta1-rev20170926-1.22.0-SNAPSHOT com.google.cloud google-cloud-pubsub - 0.21.1-beta + 0.24.0-beta com.google.oauth-client google-oauth-client 1.22.0 + + com.google.guava + guava + 23.0 + com.google.api-client google-api-client diff --git a/iot/api-client/manager/resources/ec_public.pem b/iot/api-client/manager/resources/ec_public.pem new file mode 100644 index 00000000000..d14a5b6fc3f --- /dev/null +++ b/iot/api-client/manager/resources/ec_public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAECfwA4OrF9Pcr1W5mXUa+Dx8hpPYd ++pQ5153zNtSSaeEnA/4hrY2AKxUHmKIPJXYRkZrxTxsFElkkpLcoi/CUNQ== +-----END PUBLIC KEY----- diff --git a/iot/api-client/manager/resources/rsa_cert.pem b/iot/api-client/manager/resources/rsa_cert.pem new file mode 100644 index 00000000000..ccaf7eded0a --- /dev/null +++ b/iot/api-client/manager/resources/rsa_cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC+DCCAeCgAwIBAgIJAJW4zZX4mjtpMA0GCSqGSIb3DQEBCwUAMBExDzANBgNV +BAMMBnVudXNlZDAeFw0xNzA5MjUyMjM3MzhaFw0xNzEwMjUyMjM3MzhaMBExDzAN +BgNVBAMMBnVudXNlZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKaB +voBJf6cr+Q+S1l/n5Bp0i7BEYeeCnUta+MIZle38y0E3TzXQvjMr8uk081RcWqFq +wkRjY+OM7hLIMbs3C+Qvg6uxbjaJM0LE+gjwnU8Wg76Y4jjhl+tPYP9njWxRZF0d +7WGRMpaztKpukEpgUszC75YM9XVQCal6m3eegu5BraiXrAjngGOAninBe56jhw/b +HIqF85PXczI9BbUoJeq4VycRsdUa2dJSqMxKoF7T2blYLiLBFTyo72ZF6m8SAIzv +eMw78pgtwJK4ZIzONrSe2PaPtctyRmFQBGnnZaMGi3ToiYgQ/pQ4LkuPN1sCCv7y +n3ljavj+QM+IiM5DvZMCAwEAAaNTMFEwHQYDVR0OBBYEFFK2kAhd7rYZInQxdk8T +MZrXXBCrMB8GA1UdIwQYMBaAFFK2kAhd7rYZInQxdk8TMZrXXBCrMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKCB1nQ2rFsnQpRRtvjDqlI03Opc +8sfHQ4GxJ3L7ZuXx6MzH37k0+g5/dgGYhRM+Zm9fDnxD6a1c5fek+0iGGIHOg1Cw +9lwqZN15w7SXxMiwxDVYoMBvx7JQEZSFeMfP3ZcKdwSaFRYXNRtbeC45VS70MwhM +CgqqkGDi2hM/JGYxv+UCvIm5+JrF+4SGOFtZeIT8mayq+ZOiD3+Sqo1++mRNXkSr +C8+QUjxW9y2CObE6d7Y/fryfO0mlWUnJS8Ed5H+12GqFWc7HudV2EIPS4RgthdZs +odK3woxB+18j28C1toSaSzzUtaS6hxo+vNsRqfXeK9hCm3RxU3bWRUuQj1U= +-----END CERTIFICATE----- diff --git a/iot/api-client/manager/src/main/java/com/example/cloud/iot/examples/DeviceRegistryExample.java b/iot/api-client/manager/src/main/java/com/example/cloud/iot/examples/DeviceRegistryExample.java index 2415db8aa74..9e63932fe49 100644 --- a/iot/api-client/manager/src/main/java/com/example/cloud/iot/examples/DeviceRegistryExample.java +++ b/iot/api-client/manager/src/main/java/com/example/cloud/iot/examples/DeviceRegistryExample.java @@ -20,16 +20,17 @@ import com.google.api.client.json.JsonFactory; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.client.util.Charsets; -import com.google.api.services.cloudiot.v1beta1.CloudIot; -import com.google.api.services.cloudiot.v1beta1.CloudIotScopes; -import com.google.api.services.cloudiot.v1beta1.model.Device; -import com.google.api.services.cloudiot.v1beta1.model.DeviceConfig; -import com.google.api.services.cloudiot.v1beta1.model.DeviceConfigData; -import com.google.api.services.cloudiot.v1beta1.model.DeviceCredential; -import com.google.api.services.cloudiot.v1beta1.model.DeviceRegistry; -import com.google.api.services.cloudiot.v1beta1.model.ModifyCloudToDeviceConfigRequest; -import com.google.api.services.cloudiot.v1beta1.model.NotificationConfig; -import com.google.api.services.cloudiot.v1beta1.model.PublicKeyCredential; +import com.google.api.services.cloudiot.v1.CloudIot; +import com.google.api.services.cloudiot.v1.CloudIotScopes; +import com.google.api.services.cloudiot.v1.model.Device; +import com.google.api.services.cloudiot.v1.model.DeviceConfig; +import com.google.api.services.cloudiot.v1.model.DeviceCredential; +import com.google.api.services.cloudiot.v1.model.DeviceRegistry; +import com.google.api.services.cloudiot.v1.model.DeviceState; +import com.google.api.services.cloudiot.v1.model.EventNotificationConfig; +import com.google.api.services.cloudiot.v1.model.ListDeviceStatesResponse; +import com.google.api.services.cloudiot.v1.model.ModifyCloudToDeviceConfigRequest; +import com.google.api.services.cloudiot.v1.model.PublicKeyCredential; import com.google.cloud.Role; import com.google.cloud.pubsub.v1.TopicAdminClient; import com.google.common.io.Files; @@ -80,7 +81,7 @@ public class DeviceRegistryExample { /** Creates a topic and grants the IoT service account access. */ - public static void createIotTopic(String projectId, String topicId) throws Exception { + public static Topic createIotTopic(String projectId, String topicId) throws Exception { // Create a new topic final TopicName topicName = TopicName.create(projectId, topicId); @@ -98,6 +99,7 @@ public static void createIotTopic(String projectId, String topicId) throws Excep topicAdminClient.setIamPolicy(topicName.toString(), updatedPolicy); System.out.println("Setup topic / policy for: " + topic.getName()); + return topic; } } @@ -116,9 +118,11 @@ public static void createRegistry(String cloudRegion, String projectId, String r final String fullPubsubPath = "projects/" + projectId + "/topics/" + pubsubTopicPath; DeviceRegistry registry = new DeviceRegistry(); - NotificationConfig notificationConfig = new NotificationConfig(); + EventNotificationConfig notificationConfig = new EventNotificationConfig(); notificationConfig.setPubsubTopicName(fullPubsubPath); - registry.setEventNotificationConfig(notificationConfig); + List notificationConfigs = new ArrayList(); + notificationConfigs.add(notificationConfig); + registry.setEventNotificationConfigs(notificationConfigs); registry.setId(registryName); DeviceRegistry reg = service.projects().locations().registries().create(projectPath, @@ -331,6 +335,28 @@ public static Device getDevice(String deviceId, String projectId, String cloudRe return service.projects().locations().registries().devices().get(devicePath).execute(); } + /** Retrieves device metadata from a registry. **/ + public static List getDeviceStates( + 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); + CloudIot service = new CloudIot(GoogleNetHttpTransport.newTrustedTransport(), jsonFactory, + init); + + String registryPath = "projects/" + projectId + "/locations/" + cloudRegion + "/registries/" + + registryName; + + String devicePath = registryPath + "/devices/" + deviceId; + System.out.println("Retrieving device states " + devicePath); + + ListDeviceStatesResponse resp = service.projects().locations().registries().devices().states() + .list(devicePath).execute(); + return resp.getDeviceStates(); + } + /** Retrieves registry metadata from a project. **/ public static DeviceRegistry getRegistry( String projectId, String cloudRegion, String registryName) @@ -376,7 +402,7 @@ public static void listDeviceConfigs( for (DeviceConfig config : deviceConfigs) { System.out.println("Config version: " + config.getVersion()); - System.out.println("Contents: " + config.getData().getBinaryData()); + System.out.println("Contents: " + config.getBinaryData()); System.out.println(); } } @@ -432,10 +458,8 @@ public static void modifyCloudToDeviceConfig(String deviceId, String configData, + "/registries/" + registryName; final String devicePath = registryPath + "/devices/" + deviceId; ModifyCloudToDeviceConfigRequest request = new ModifyCloudToDeviceConfigRequest(); - DeviceConfigData data = new DeviceConfigData(); - data.setBinaryData(DatatypeConverter.printBase64Binary(configData.getBytes(Charsets.UTF_8))); request.setVersionToUpdate(0L); // 0L indicates update all versions - request.setData(data); + request.setBinaryData(DatatypeConverter.printBase64Binary(configData.getBytes(Charsets.UTF_8))); DeviceConfig config = service .projects() @@ -575,6 +599,14 @@ public static void main(String[] args) throws Exception { options.registryName) .toPrettyString()); break; + case "get-device-state": + System.out.println("Get device state"); + List states = getDeviceStates(options.deviceId, options.projectId, + options.cloudRegion, options.registryName); + for (DeviceState state: states) { + System.out.println(state.toPrettyString()); + } + break; case "get-registry": System.out.println("Get registry"); System.out.println(getRegistry(options.projectId, options.cloudRegion, @@ -603,68 +635,5 @@ public static void main(String[] args) throws Exception { System.out.println("Wrong, wrong, wrong. Usage is like this:"); // TODO: break; } - - - /* - // Simple example of interacting with the Cloud IoT API. - String registryName = "cloudiot_device_manager_example_registry_" + System.currentTimeMillis(); - - // Create a new registry with the above name. - DeviceRegistryExample registry = - new DeviceRegistryExample( - options.projectId, options.cloudRegion, registryName, options.pubsubTopic); - - // List the devices in the registry. Since we haven't created any yet, this should be empty. - registry.listDevices(); - - // Create a device that is authenticated using RSA. - String rs256deviceId = "rs256-device"; - registry.createDeviceWithRs256(rs256deviceId, options.rsaCertificateFile); - - // Create a device without an authentication credential. We'll patch it to use elliptic curve - // cryptography. - String es256deviceId = "es256-device"; - registry.createDeviceWithNoAuth(es256deviceId); - - // List the devices again. This should show the above two devices. - registry.listDevices(); - - // Give the device without an authentication credential an elliptic curve credential. - registry.patchEs256ForAuth(es256deviceId, options.ecPublicKeyFile); - - // List the devices in the registry again, still showing the two devices. - registry.listDevices(); - - // List the device configs for the RSA authenticated device. Since we haven't pushed any, this - // list will only contain the default empty config. - registry.listDeviceConfigs(rs256deviceId); - - // Push two new configs to the device. - registry.modifyCloudToDeviceConfig(rs256deviceId, "config v1"); - registry.modifyCloudToDeviceConfig(rs256deviceId, "config v2"); - - // List the configs again. This will show the two configs that we just pushed. - registry.listDeviceConfigs(rs256deviceId); - - // Delete the elliptic curve device. - registry.deleteDevice(es256deviceId); - - // Since we deleted the elliptic curve device, this will only show the RSA device. - registry.listDevices(); - - try { - // Try to delete the registry. However, since the registry is not empty, this will fail and - // throw an exception. - registry.deleteRegistry(); - } catch (IOException e) { - System.out.println("Exception: " + e.getMessage()); - } - - // Delete the RSA device. The registry is now empty. - registry.deleteDevice(rs256deviceId); - - // Since the registry has no devices in it, the delete will succeed. - registry.deleteRegistry(); - */ } } diff --git a/iot/api-client/manager/src/main/java/com/example/cloud/iot/examples/DeviceRegistryExampleOptions.java b/iot/api-client/manager/src/main/java/com/example/cloud/iot/examples/DeviceRegistryExampleOptions.java index 2ff9e68de7c..cc3e5de8ef7 100644 --- a/iot/api-client/manager/src/main/java/com/example/cloud/iot/examples/DeviceRegistryExampleOptions.java +++ b/iot/api-client/manager/src/main/java/com/example/cloud/iot/examples/DeviceRegistryExampleOptions.java @@ -60,6 +60,7 @@ public static DeviceRegistryExampleOptions fromFlags(String[] args) { + "\n\tdelete-device" + "\n\tdelete-registry" + "\n\tget-device" + + "\n\tget-device-state" + "\n\tget-registry" + "\n\tlist-devices" + "\n\tlist-registries" diff --git a/iot/api-client/manager/src/test/java/com/example/cloud/iot/examples/ManagerIT.java b/iot/api-client/manager/src/test/java/com/example/cloud/iot/examples/ManagerIT.java index 0f3e8616781..3716bb2517c 100644 --- a/iot/api-client/manager/src/test/java/com/example/cloud/iot/examples/ManagerIT.java +++ b/iot/api-client/manager/src/test/java/com/example/cloud/iot/examples/ManagerIT.java @@ -16,11 +16,14 @@ package com.example.cloud.iot.examples; +import com.google.cloud.pubsub.v1.TopicAdminClient; +import com.google.pubsub.v1.Topic; + import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.io.PrintStream; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -34,54 +37,149 @@ public class ManagerIT { private ByteArrayOutputStream bout; private PrintStream out; private DeviceRegistryExample app; + + private static final String CLOUD_REGION = "us-central1"; + private static final String ES_PATH = "resources/ec_public.pem"; private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); - private static final String BUCKET = PROJECT_ID; + private static final String REGISTRY_ID = "java-reg-" + (System.currentTimeMillis() / 1000L); + private static final String RSA_PATH = "resources/rsa_cert.pem"; + private static final String TOPIC_ID = "java-pst-" + (System.currentTimeMillis() / 1000L); + + private static Topic topic; @Before - public void setUp() throws IOException { + public void setUp() throws Exception { bout = new ByteArrayOutputStream(); out = new PrintStream(bout); System.setOut(out); - app = new DeviceRegistryExample(); - // TODO: Create IoT PubSub Topic - // TODO: Create Registry } @After - public void tearDown() { - // TODO: Delete registry - // TODO: Remove PubSub Topic + public void tearDown() throws Exception { System.setOut(null); } @Test public void testCreateDeleteUnauthDevice() throws Exception { - // TODO: Implement when library is live + final String deviceName = "noauth-device"; + topic = DeviceRegistryExample.createIotTopic( + PROJECT_ID, + TOPIC_ID); + + DeviceRegistryExample.createRegistry(CLOUD_REGION, PROJECT_ID, REGISTRY_ID, TOPIC_ID); + DeviceRegistryExample.createDeviceWithNoAuth(deviceName, PROJECT_ID, CLOUD_REGION, REGISTRY_ID); + + String got = bout.toString(); + Assert.assertTrue(got.contains("Created device: {")); + + DeviceRegistryExample.deleteDevice(deviceName, PROJECT_ID, CLOUD_REGION, REGISTRY_ID); + DeviceRegistryExample.deleteRegistry(CLOUD_REGION, PROJECT_ID, REGISTRY_ID); + try (TopicAdminClient topicAdminClient = TopicAdminClient.create()) { + topicAdminClient.deleteTopic(topic.getNameAsTopicName()); + } } @Test public void testCreateDeleteEsDevice() throws Exception { - // TODO: Implement when library is live + final String deviceName = "es-device"; + topic = DeviceRegistryExample.createIotTopic( + PROJECT_ID, + TOPIC_ID); + DeviceRegistryExample.createRegistry(CLOUD_REGION, PROJECT_ID, REGISTRY_ID, TOPIC_ID); + DeviceRegistryExample.createDeviceWithEs256(deviceName, ES_PATH, PROJECT_ID, CLOUD_REGION, + REGISTRY_ID); + DeviceRegistryExample.getDeviceStates(deviceName, PROJECT_ID, CLOUD_REGION, REGISTRY_ID); + + String got = bout.toString(); + Assert.assertTrue(got.contains("Created device: {")); + + DeviceRegistryExample.deleteDevice(deviceName, PROJECT_ID, CLOUD_REGION, REGISTRY_ID); + DeviceRegistryExample.deleteRegistry(CLOUD_REGION, PROJECT_ID, REGISTRY_ID); + try (TopicAdminClient topicAdminClient = TopicAdminClient.create()) { + topicAdminClient.deleteTopic(topic.getNameAsTopicName()); + } } @Test public void testCreateDeleteRsaDevice() throws Exception { - // TODO: Implement when library is live + final String deviceName = "rsa-device"; + topic = DeviceRegistryExample.createIotTopic( + PROJECT_ID, + TOPIC_ID); + DeviceRegistryExample.createRegistry(CLOUD_REGION, PROJECT_ID, REGISTRY_ID, TOPIC_ID); + DeviceRegistryExample.createDeviceWithRs256( + deviceName, RSA_PATH, PROJECT_ID, CLOUD_REGION, REGISTRY_ID); + DeviceRegistryExample.getDeviceStates(deviceName, PROJECT_ID, CLOUD_REGION, REGISTRY_ID); + + String got = bout.toString(); + Assert.assertTrue(got.contains("Created device: {")); + + DeviceRegistryExample.deleteDevice(deviceName, PROJECT_ID, CLOUD_REGION, REGISTRY_ID); + DeviceRegistryExample.deleteRegistry(CLOUD_REGION, PROJECT_ID, REGISTRY_ID); + try (TopicAdminClient topicAdminClient = TopicAdminClient.create()) { + topicAdminClient.deleteTopic(topic.getNameAsTopicName()); + } } @Test public void testCreateGetDevice() throws Exception { - // TODO: Implement when library is live + final String deviceName = "rsa-device"; + topic = DeviceRegistryExample.createIotTopic( + PROJECT_ID, + TOPIC_ID); + DeviceRegistryExample.createRegistry(CLOUD_REGION, PROJECT_ID, REGISTRY_ID, TOPIC_ID); + DeviceRegistryExample.createDeviceWithRs256( + deviceName, RSA_PATH, PROJECT_ID, CLOUD_REGION, REGISTRY_ID); + DeviceRegistryExample.getDevice(deviceName, PROJECT_ID, CLOUD_REGION, REGISTRY_ID); + + String got = bout.toString(); + Assert.assertTrue(got.contains("Created device: {")); + Assert.assertTrue(got.contains("Retrieving device")); + + DeviceRegistryExample.deleteDevice(deviceName, PROJECT_ID, CLOUD_REGION, REGISTRY_ID); + DeviceRegistryExample.deleteRegistry(CLOUD_REGION, PROJECT_ID, REGISTRY_ID); + try (TopicAdminClient topicAdminClient = TopicAdminClient.create()) { + topicAdminClient.deleteTopic(topic.getNameAsTopicName()); + } } - @Test public void testCreateListDevices() throws Exception { - // TODO: Implement when library is live + final String deviceName = "rsa-device"; + topic = DeviceRegistryExample.createIotTopic( + PROJECT_ID, + TOPIC_ID); + DeviceRegistryExample.createRegistry(CLOUD_REGION, PROJECT_ID, REGISTRY_ID, TOPIC_ID); + DeviceRegistryExample.createDeviceWithRs256( + deviceName, RSA_PATH, PROJECT_ID, CLOUD_REGION, REGISTRY_ID); + DeviceRegistryExample.listDevices(PROJECT_ID, CLOUD_REGION, REGISTRY_ID); + + String got = bout.toString(); + Assert.assertTrue(got.contains("Created device: {")); + Assert.assertTrue(got.contains("Found")); + + DeviceRegistryExample.deleteDevice(deviceName, PROJECT_ID, CLOUD_REGION, REGISTRY_ID); + DeviceRegistryExample.deleteRegistry(CLOUD_REGION, PROJECT_ID, REGISTRY_ID); + try (TopicAdminClient topicAdminClient = TopicAdminClient.create()) { + topicAdminClient.deleteTopic(topic.getNameAsTopicName()); + } } @Test public void testCreateGetRegistry() throws Exception { - // TODO: Implement when library is live + + topic = DeviceRegistryExample.createIotTopic( + PROJECT_ID, + TOPIC_ID); + DeviceRegistryExample.createRegistry(CLOUD_REGION, PROJECT_ID, REGISTRY_ID, TOPIC_ID); + DeviceRegistryExample.getRegistry(PROJECT_ID, CLOUD_REGION, REGISTRY_ID); + + String got = bout.toString(); + Assert.assertFalse(got.contains("eventNotificationConfigs")); + + DeviceRegistryExample.deleteRegistry(CLOUD_REGION, PROJECT_ID, REGISTRY_ID); + try (TopicAdminClient topicAdminClient = TopicAdminClient.create()) { + topicAdminClient.deleteTopic(topic.getNameAsTopicName()); + } } } diff --git a/iot/api-client/mqtt_example/pom.xml b/iot/api-client/mqtt_example/pom.xml index 09b95f4966e..90cfdaa7396 100644 --- a/iot/api-client/mqtt_example/pom.xml +++ b/iot/api-client/mqtt_example/pom.xml @@ -1,3 +1,18 @@ + 4.0.0 diff --git a/iot/api-client/mqtt_example/src/main/java/com/google/cloud/iot/examples/MqttExample.java b/iot/api-client/mqtt_example/src/main/java/com/google/cloud/iot/examples/MqttExample.java index 91e8c2246cc..c74b38b0330 100644 --- a/iot/api-client/mqtt_example/src/main/java/com/google/cloud/iot/examples/MqttExample.java +++ b/iot/api-client/mqtt_example/src/main/java/com/google/cloud/iot/examples/MqttExample.java @@ -1,32 +1,45 @@ +/** + * Copyright 2017, Google, Inc. + * + *

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + *

http://www.apache.org/licenses/LICENSE-2.0 + * + *

Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.google.cloud.iot.examples; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyFactory; +import java.security.spec.PKCS8EncodedKeySpec; import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import org.joda.time.DateTime; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.KeyFactory; -import java.security.PrivateKey; -import java.security.spec.PKCS8EncodedKeySpec; - /** * Java sample of connecting to Google Cloud IoT Core vice via MQTT, using JWT. * *

This example connects to Google Cloud IoT Core via MQTT, using a JWT for device - * authentication. After connecting, by default the device publishes 100 messages to - * the device's MQTT topic at a rate of one per second, and then exits. + * authentication. After connecting, by default the device publishes 100 messages to the device's + * MQTT topic at a rate of one per second, and then exits. To set state instead of publishing + * telemetry events, set the `-message_type` flag to `state.` * - *

To run this example, first create your credentials and register your device as - * described in the README located in the sample's parent folder. + *

To run this example, first create your credentials and register your device as described in + * the README located in the sample's parent folder. * - *

After you have registered your device and generated your credentials, compile and - * run with the corresponding algorithm flag, for example: + *

After you have registered your device and generated your credentials, compile and run with the + * corresponding algorithm flag, for example: * *

  *   $ mvn compile
@@ -86,12 +99,12 @@ public static void main(String[] args) throws Exception {
     // 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.
-    String mqttServerAddress =
+    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.
-    String mqttClientId =
+    final String mqttClientId =
         String.format(
             "projects/%s/locations/%s/registries/%s/devices/%s",
             options.projectId, options.cloudRegion, options.registryId, options.deviceId);
@@ -121,27 +134,42 @@ public static void main(String[] args) throws Exception {
 
     // Create a client, and connect to the Google MQTT bridge.
     MqttClient client = new MqttClient(mqttServerAddress, mqttClientId, new MemoryPersistence());
-    client.connect(connectOptions);
-
-    // 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/events", options.deviceId);
-
-    // 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 message %d/%d: '%s'\n", i, options.numMessages, payload);
-
-      // 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);
-      Thread.sleep(1000);
+    try {
+      client.connect(connectOptions);
+
+      // 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);
+
+        // 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);
+        }
+      }
+    } finally {
+      // Disconnect the client and finish the run.
+      client.disconnect();
     }
-    // Disconnect the client and finish the run.
-    client.disconnect();
     System.out.println("Finished loop successfully. Goodbye!");
   }
 }
diff --git a/iot/api-client/mqtt_example/src/main/java/com/google/cloud/iot/examples/MqttExampleOptions.java b/iot/api-client/mqtt_example/src/main/java/com/google/cloud/iot/examples/MqttExampleOptions.java
index 472ee15bb47..102368acb7a 100644
--- a/iot/api-client/mqtt_example/src/main/java/com/google/cloud/iot/examples/MqttExampleOptions.java
+++ b/iot/api-client/mqtt_example/src/main/java/com/google/cloud/iot/examples/MqttExampleOptions.java
@@ -1,3 +1,17 @@
+/**
+ * Copyright 2017, Google, Inc.
+ *
+ * 

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + *

http://www.apache.org/licenses/LICENSE-2.0 + * + *

Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.google.cloud.iot.examples; import org.apache.commons.cli.CommandLine; @@ -18,6 +32,7 @@ public class MqttExampleOptions { int numMessages = 100; String mqttBridgeHostname = "mqtt.googleapis.com"; short mqttBridgePort = 8883; + String messageType = "event"; /** Construct an MqttExampleOptions class from command line flags. */ public static MqttExampleOptions fromFlags(String[] args) { @@ -93,6 +108,13 @@ public static MqttExampleOptions fromFlags(String[] args) { .hasArg() .desc("MQTT bridge port.") .build()); + options.addOption( + Option.builder() + .type(String.class) + .longOpt("message_type") + .hasArg() + .desc("Indicates whether the message is a telemetry event or a device state message") + .build()); CommandLineParser parser = new DefaultParser(); CommandLine commandLine; @@ -118,6 +140,9 @@ public static MqttExampleOptions fromFlags(String[] args) { res.mqttBridgePort = ((Number) commandLine.getParsedOptionValue("mqtt_bridge_port")).shortValue(); } + if (commandLine.hasOption("message_type")) { + res.messageType = commandLine.getOptionValue("message_type"); + } return res; } catch (ParseException e) { System.err.println(e.getMessage());