Quickstart: IoT with Google Cloud Pub/Sub

This example comes from the wireless mesh space. Customers can purchase devices to install in their homes that they can control from anywhere. When the example service is deployed to Akka Serverless, you can interact with devices, assign them to different rooms in the house, and turn them on or off. You can even connect to a physical LIFX bulb and use the service to switch it on or off.

Available in Java and JavaScript, the example demonstrates Event Sourced Entities and shows how a service can publish events to external systems, such as Google Cloud Pub/Sub new tab.

Before you begin

Download the example project

Download the example source from GitHub for Java or for JavaScript:

Define service API and message types

Define the service API in gRPC .proto file format. As a best practice, the example uses two .proto files, one that defines the service and a second containing the domain-specific definitions for the entity. From the .proto files, the gRPC compiler creates client and server-side code that saves work for you and that enables Akka Serverless to serialize message data at runtime.

  1. View the IoT message definitions and service API:

    Javascript

    From the wirelessmesh directory, open the wirelessmeshservice.proto file.

    Java

    From the source/main/proto directory, open the wirelessmeshservice.proto file.

  2. Find the AddCustomerLocationCommand message and note that it includes a (akkaserverless.field).entity_key, which allows us to store and locate customers by a unique ID.

  3. Locate the WirelessMeshService definition and note the operations it supports for managing customers and their devices.

  4. From the same directory, open the wirelessmeshdomain.proto file and view the events that will be stored for the service.

Now, let’s look at the implementation.

Using Event Sourced Entities

The snippet below demonstrates how to use Event Sourced Entities:

JavaScript

From the file wirelessmesh.js:

const EventSourced = require("akkaserverless").EventSourced;
const eventPublisher = require("./eventPublisher.js");
const deviceClient = require("./deviceClient.js");

const entity = new EventSourced(
  ["wirelessmeshservice.proto", "wirelessmeshdomain.proto"],
  "wirelessmeshservice.WirelessmeshService",
  {
    persistenceId: "customer-location",
    snapshotEvery: 50,
    includeDirs: ["./"],
    serializeFallbackToJson: true // Enables JSON support for persistence
  }
);
Java

From the file WirelessMeshMain.java, the entity must be registered as shown in the following snippet:

public class WirelessMeshMain {

    private final static Logger LOG = LoggerFactory.getLogger(WirelessMeshMain.class);

    public static AkkaServerless wirelessMeshService =
            new AkkaServerless()
                    .registerEventSourcedEntity(
                            CustomerLocationEntity.class,
                            Wirelessmesh.getDescriptor().findServiceByName("WirelessMeshService"),
                            Wirelessmeshdomain.getDescriptor()
                    )
                    .registerAction(
                            PublishingAction.class,
                            Publishing.getDescriptor().findServiceByName("PublishingService"),
                            Wirelessmeshdomain.getDescriptor()
                    );

    public static void main(String... args) throws Exception {
        LOG.info("started");
        wirelessMeshService.start().toCompletableFuture().get();
    }
}

The CustomerLocationEntity will be seeded with the current state upon loading and thereafter will completely serve the backend needs for a particular device. If you want to compare the entities to a traditional relational database, you can look at each instance of these entities as being roughly equivalent to a row in a database, only each one is completely addressable and in memory.

The PublishingAction is triggered by each event received by the CustomerLocationEntity. This Action is in charge of publishing these events to a topic in Google Cloud Pub/Sub working in combination with the PublishingService. You can find the definition of this Service in the file publishing.proto.

How to connect to external systems

Your Akka Serverless services don’t exist in a vacuum, so you’ll need to connect from services to external systems. This IoT example needs to connect to Google Cloud Pub/Sub to publish the events generated from the Customer Location Entity.

To connect this IoT example with Google Cloud Pub/Sub you need to do the following.

  1. Create an Akkaserverless project to deploy the IoT example to. More info in here

  2. Package the project and deploy it. More info in here

  3. Configure the message broker to allow communication between the IoT example and Google Cloud Pub/Sub. More info in here

Prepare Google Cloud Pub/Sub

  1. Create a Pub/Sub topic called wirelessmesh from your Google Cloud account.

    1. From the project base, run the following command and enter the credentials to access your account:

      gcloud auth login
    2. Return a list of projects:

      gcloud projects list
    3. Set the Google Cloud Pub/Sub project using one of the listed project IDs as the <PROJECT_ID> in the following command:

      gcloud config set project  <PROJECT_ID>
    4. Generate the wirelessmesh topic:

      gcloud pubsub topics create wirelessmesh
    5. Run the following command to confirm the wirelessmesh topic is successfully created:

      gcloud pubsub topics list

      The command returns a list, including the new topic, as shown here:

      name: projects/as-test-314220/topics/wirelessmesh

      Optionally, view the result in Google Cloud Console Google Cloud Platform Dashboard new tab.

      gcp pusbub topic
  2. Follow the steps outlined in Configure a message broker - Setting up the service account to create a create a service account new tab.

Optional - Connect to a LIFX bulb

Increase your interaction with the project and connect your own LIFX bulb as a stand in for a wirelessmesh device using the following options steps:

Before you begin make sure to have an operational LIFX bulb and authorization token.
  • Use the LIFX access token returned from the customer location in The first request as follows:

    '{"customerLocationId": "my-first-location", "accessToken": "<lifx access token>"}'
  • When you activate the device in this app, make sure it has the same device id as your bulb.

See the LIFX website new tab for more information on LIFX bulbs.

Build the IoT service container

To build the service container, follow these steps:

JavaScript

From the wirelessmesh directory of the example:

  1. Set your Docker Hub username:

    export DOCKER_REGISTRY=docker.io
    export DOCKER_USER=<your dockerhub username>
  2. Build:

    npm run dockerbuild

Java
  1. From the root example directory, run:

    mvn clean install

    The command mvn clean install creates a new Docker image based on mybaseimage or on adoptopenjdk/openjdk8.

    $ mvn clean install

    The result of the command is:

    ...
    
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time:  01:31 min
    [INFO] Finished at: 2021-01-20T16:20:29-08:00
    [INFO] ------------------------------------------------------------------------

Deploy the container

Deploy the IoT container as a service in Akka Serverless:

  1. Push the container to a container registry by substituting your username and the correct URL in the following command:

    docker push -t <registry url>/<registry username>/akkaserverless-wirelessmesh-java:latest
  2. If you are not logged into Akka Serverless, log in using either the Console or the CLI:

    Console
    1. Sign in to your Akka Serverless account at: https://console.akkaserverless.com/

      The Projects page opens.

    2. Click the Add New Project card.

    3. In the Project name field, enter IoT.

    4. Click submit.

      Your project appears in the Projects list.

    CLI
    1. Log into your account:

      akkasls auth login

      The Login page launches in a browser.

    2. Enter your credentials.

      An authorization page displays.

    3. Click Authorize

    4. In the CLI, create a IoT project to deploy the IoT service:

      akkasls projects new IoT
    5. Set the IoT project to be your current project:

      akkasls config set project IoT
  3. Deploy the container using either the Console or the CLI:

    Console
    1. In the Services pane, click +.

    2. Enter wirelessmesh and click Next.

    3. Enter the path to your container image:

      <registry url>/<registry username>/akkaserverless-wirelessmesh-java:latest

    4. Click Next.

    5. Skip adding environment variables and click Next.

    6. Move the sliders to add a route and enable CORS, and click Next.

    7. Click Finish.

    8. Scroll down to find the Routes panel and copy the Hostname. This is needed for the next exercise.

    CLI
    1. Set Iot to be the current project:

      akkasls config set project IoT
    2. Deploy the service by substituting your registry url and username in the following command:

      akkasls services deploy wirelessmesh <registry url>/<registry username>/akkaserverless-wirelessmesh-java:latest
    3. Expose a route and enable CORS:

      akkasls service expose wirelessmesh --enable-cors

      Akka Serverless returns the hostname where your service is exposed:

      snowy-truth-4046.us-east1.apps.akkaserverless.com

    The exposed hostname is used the next exercise.

Now you are ready to try it out!

Exercising the example

Exercise the example by sending HTTP requests. This tutorial provides instructions for using Postman, but you should be able to extrapolate how to use curl or another HTTP client.

For the first request, all steps to use Postman’s web UI are provided. For subsequent requests, we provide only the type of method, the URL, and the body.

The first request

Create the first Postman request:

  1. You can create a request from several locations in Postman. For example, from the Get Started list in your workspace, click Create a request.

    An untitled request displays:

    new request
  2. From the method type (Get) dropdown, select the POST method.

  3. For the request URL, where <hostname> is the Hostname for your exposed service, enter:

    https://<hostname>/wirelessmesh/add-customer-location
  4. Click Body.

  5. Select Raw and from the type drop-down, select JSON.

  6. Add the body:

    '{"customerLocationId": "my-first-location", "accessToken": "my-lifx access-token-if-applicable"}'
  7. Click Send.

    A 200(OK) {} is returned. This is the expected response for all POSTs to this example.

Add a location and a device

Follow these steps to add a location and a device:

  1. Create a GET request with the following request URL:

    https://<hostname>/wirelessmesh/get-customer-location?customerLocationId=my-first-location'

    A JSON response containing your customer location and no devices is returned.

  2. Create a POST request to https://<hostname>/wirelessmesh/activate-device with the JSON body:

    {"customerLocationId": "my-first-location", "deviceId": "my-first-device"}
  3. Create a POST request to https://<hostname>.com/wirelessmesh/assign-room with the JSON body:

    {"customerLocationId": "my-first-location", "deviceId": "my-first-device", "room": "office"}
  4. Create a POST request to https://<hostname>/wirelessmesh/toggle-nightlight with the JSON body:

    {"customerLocationId": "my-first-location", "deviceId": "my-first-device"}
  5. Rerun the get-customer-location request

    A JSON response with your customer location, and a collection of your single device with the room assigned and the nightlight on, is returned.

Remove the device and location

To remove the device and location:

  1. Create a POST request to https://<hostname>/wirelessmesh/remove-device with the body:

    {"customerLocationId": "my-first-location", "deviceId": "my-first-device"}

    A response that shows no devices is returned.

  2. Create a POST request to `\https://<hostname>/wirelessmesh/remove-customer-location' with the body:

    {"customerLocationId": "my-first-location"}

Rerun the get-customer-location request, and you will see a server error since it no longer exists.

Test Google Cloud Pub/Sub wirelessmesh topic is receiving the message.

To see the messages you need to create a subscription to that topic. This can be done in the subscription menu on your Google Cloud Account.

Once you have a subscription you can POST a new location to your CustomerLocationEntity, as shown in the examples above, and check it out in the details of the subscription by clicking on the view messages button.