narutaro / note

0 stars 0 forks source link

AWS IoT SDK for JavaScript Quick Start #3

Open narutaro opened 1 year ago

narutaro commented 1 year ago

Please note that this code is for aws-iot-device-sdk-js which is v1, previous version. The latest sdk is v2.

const awsIot = require("aws-iot-device-sdk");

// Replace with your AWS IoT endpoint
const THING_ENDPOINT = "<code>-ats.iot.<region>.amazonaws.com";
const CLIENT_ID = "trackThing01";
const IOT_TOPIC = "iot/trackedAssets";

const POINTS_ON_MAP = [
  { lat: 49.282301, long: -123.118408 },
  { lat: 49.282144, long: -123.117574 },
  { lat: 49.282254, long: -123.116522 },
  { lat: 49.282732, long: -123.115799 },
];

const device = awsIot.device({
  host: THING_ENDPOINT,
  keyPath: `${__dirname}/certs/private.pem.key`,
  certPath: `${__dirname}/certs/certificate.pem.crt`,
  caPath: `${__dirname}/certs/root-CA.pem`,
  clientId: CLIENT_ID,
  keepalive: 60000,
});

console.log("Connecting to %s with client ID %s", THING_ENDPOINT, CLIENT_ID);

device.on("connect", async function () {
  console.log("Connected to device %s", CLIENT_ID);

  for (const point of POINTS_ON_MAP) {
    const message = {
      payload: {
        deviceId: "thing123",
        timestamp: new Date().getTime(),
        location: point,
      },
    };
    console.log(
      "Publishing message to topic %s: %s",
      IOT_TOPIC,
      JSON.stringify(message)
    );
    device.publish(IOT_TOPIC, JSON.stringify(message), { qos: 1 });

    // Set timeout to sleep
    await new Promise((resolve) => setTimeout(resolve, 10000));
  }

  device.end();
});
narutaro commented 1 year ago

ランダムに動きまわる(座標が変わる)シミュレーター

ts-node src/index.ts

index.ts

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

import Simulator from "./utils";

const LNG = 36.12309017212961;
const LAT = -115.17077150978058;
const STEP_DISTANCE = 10; // Distance in meters for each step taken by the pet (default 10m / 32 feet)
const STEP_FREQUENCY = 10; // Frequency at which updates will be sent (default 10 seconds)
const IOT_CORE_TOPIC = "iot/pettracker";
const IOT_CERT_SECRET_ID = "pettracker/iot-cert";

const sim = new Simulator(
  `pettracker`,
  IOT_CORE_TOPIC,
  IOT_CERT_SECRET_ID,
  [LAT, LNG],
  STEP_DISTANCE
);

export const handler = async (): Promise<void> => {
  while (true) {
    await sim.makeStep();
    await new Promise((resolve) => setTimeout(resolve, STEP_FREQUENCY * 1000)); // Wait `STEP_FREQUENCY` seconds
  }
};

handler();

utils.ts

// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

import { mqtt, iot } from "aws-iot-device-sdk-v2";
import {
  SecretsManagerClient,
  GetSecretValueCommand,
} from "@aws-sdk/client-secrets-manager";
import { IoTClient, DescribeEndpointCommand } from "@aws-sdk/client-iot";
import { randomPosition } from "@turf/random";
import bbox from "@turf/bbox";
import buffer from "@turf/buffer";
import { point, Position } from "@turf/helpers";
import promiseRetry from "promise-retry";

const retryOptions = {
  retries: 10,
  minTimeout: 5_000,
  maxTimeout: 10_000,
  factor: 1.25,
};

class Simulator {
  private ioTtopic: string;
  private clientId: string;
  private isConnected: boolean = false;
  private secretsManagerClient: SecretsManagerClient;
  private iotCoreClient: IoTClient;
  private secretId: string;
  private currentPosition: Position;
  private stepDistance: number;
  private cert?: string;
  private key?: string;
  private endpoint?: string;
  private ioTConnection?: mqtt.MqttClientConnection;

  constructor(
    clientId: string,
    topic: string,
    secretId: string,
    seed: Position,
    stepDistance: number
  ) {
    this.ioTtopic = topic;
    this.clientId = clientId;
    this.secretId = secretId;
    this.currentPosition = seed;
    this.stepDistance = stepDistance;
    this.secretsManagerClient = new SecretsManagerClient({});
    this.iotCoreClient = new IoTClient({});
  }

  private async getEndpoint(): Promise<string> {
    return promiseRetry(async (retry: (err?: Error) => never, _: number) => {
      try {
        const endpoint = await this.iotCoreClient.send(
          new DescribeEndpointCommand({
            endpointType: "iot:Data-ATS",
          })
        );

        if (!endpoint.endpointAddress)
          throw new Error("Unable to get IoT Core Endpoint");

        console.info(`Got IoT Core Endpoint: ${endpoint.endpointAddress}`);
        return endpoint.endpointAddress;
      } catch (err) {
        retry(err as Error);
      }
    }, retryOptions);
  }

  private async getCertAndKey(): Promise<{
    cert: string;
    key: string;
  }> {
    if (!this.cert || !this.key) {
      const secret = await this.secretsManagerClient.send(
        new GetSecretValueCommand({
          SecretId: this.secretId,
        })
      );
      const { SecretString } = secret;
      if (!SecretString) {
        throw new Error("Could not find secret");
      }
      const { cert, keyPair } = JSON.parse(SecretString);

      this.cert = cert;
      this.key = keyPair;

      if (!this.cert || !this.key) {
        throw new Error("Could not find cert or key");
      }

      console.info("Got cert and key from Secrets Manager");
    }

    return {
      cert: this.cert,
      key: this.key,
    };
  }

  private buildConnection = async (
    clientId: string
  ): Promise<mqtt.MqttClientConnection> => {
    if (!this.endpoint) {
      this.endpoint = await this.getEndpoint();
    }
    const { cert, key } = await this.getCertAndKey();
    let configBuilder = iot.AwsIotMqttConnectionConfigBuilder.new_mtls_builder(
      cert,
      key
    );
    configBuilder.with_clean_session(false);
    configBuilder.with_client_id(clientId);
    configBuilder.with_endpoint(this.endpoint);
    const config = configBuilder.build();
    const client = new mqtt.MqttClient();

    return client.new_connection(config);
  };

  private connect = async () => {
    try {
      this.ioTConnection = await this.buildConnection(this.clientId);
    } catch (err) {
      console.error(err);
      console.error("Failed to build connection object");
      throw err;
    }

    try {
      console.info("Connecting to IoT Core");
      await promiseRetry(async (retry: (err?: Error) => never, _: number) => {
        try {
          await this.ioTConnection?.connect();
        } catch (err) {
          console.error(err);
          retry(new Error("Connection failed, retrying."));
        }
      }, retryOptions);
      console.info("Successfully connected to IoT Core");
      this.isConnected = true;
    } catch (err) {
      console.error("Error connecting to IoT Core", { err });
      throw err;
    }
  };

  private publishUpdate = async (location: Position) => {
    if (!this.isConnected) {
      await this.connect();
    }

    const payload = {
      id: this.clientId,
      timestamp: new Date().toISOString(),
      lng: location[0],
      lat: location[1],
    };

    // Log update before publishing
    console.debug(JSON.stringify(payload, null, 2));

    await this.ioTConnection?.publish(
      this.ioTtopic,
      JSON.stringify({
        ...payload,
      }),
      mqtt.QoS.AtMostOnce
    );
  };

  /**
   * Generates a random point within a given radius from another point.
   *
   * It takes the initial position and the map bounds as input. It then creates a buffer
   * around the point which represents the area that the device might end up in.
   *
   * It then generates a random point within the area and publishes it to the IoT Core endpoint/topic.
   */
  public makeStep = async () => {
    const currentPosition = point(this.currentPosition);
    // Create a buffer around the current position (i.e. a polygon 10feet around the point)
    const bufferAroundPoint = buffer(currentPosition, this.stepDistance, {
      units: "feet",
    });
    // Create a bounding box around the buffer
    const bboxAroundPoint = bbox(bufferAroundPoint);
    // Generate a random point within the intersection bounding box
    const nextPosition = randomPosition(bboxAroundPoint);
    // Publish
    await this.publishUpdate(nextPosition);
  };
}

export default Simulator;