Steve Kasuya
Published © GPL3+

Automatic Ventilation System with CO2 Monitors

At Akihabara Hackerspace, we have automated the ventilation process with CO2 monitors, a fan, AWS IoT Edukit, and IoT-related AWS Services.

IntermediateFull instructions provided5 hours2,568

Things used in this project

Hardware components

AWS IoT EduKit
Amazon Web Services AWS IoT EduKit
×5
MH-Z19C NDIR CO2 Sensor
×4
Mini mini breadboard (orange)
×10
Seeed Studio Grove - Universal 4 Pin 20cm Unbuckled Cable
×3
USB Cable, USB Type C Plug
USB Cable, USB Type C Plug
×5
Kitchen Hood (generic)
×1
SSR Kit (Akizuki Electric)
×1
Mate-N-Lok 6 Pin Connector Kit
×1
AC100-240V to DC5V Supply Module
×1
Dupont Connector Kit
×1

Software apps and online services

Amazon Web Services AWS IoT Core
Amazon Web Services AWS IoT Events
Amazon Web Services AWS IoT Sitewise
AWS DynamoDB
Amazon Web Services AWS DynamoDB

Hand tools and fabrication machines

Microsoft VIsual Studio Code
3D Printer (generic)
3D Printer (generic)
Wire stripper/crimper (generic)

Story

Read more

Custom parts and enclosures

CO2 Monitor HAT

Schematics

Schematic (CO2 Sensor)

Schematic (Fan Controller)

Code

main.cpp (CO2 Monitors)

Arduino
Publishes a CO2 value to AWS IoT every 10 sec.
Modified from https://github.com/aws-samples/aws-iot-edukit-examples/blob/main/Basic_Arduino/src/main.cpp
/*
 * Core2 for AWS IoT EduKit
 * Arduino Basic Connectivity Example v1.0.1
 * main.cpp
 * 
 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <Arduino.h>
#include <M5Core2.h>
#include <driver/i2s.h>
#include <Wire.h>
#include <WiFi.h>
#include <FastLED.h>
#include <time.h>
#include <ArduinoBearSSL.h>
#include <ArduinoECCX08.h>
#include <ArduinoMqttClient.h>
#include "arduino_secrets.h"

// NTP server details.
//
// NOTE: GMT offset is in seconds, so multiply hours by
// 3600 (e.g. Pacific Time would be -8 * 3600)
const char* ntp_server = "pool.ntp.org";
const long  gmt_offset_sec = 0 * 3600;
const int   daylight_offset_sec = 3600;

// 
const char wifi_ssid[]      = WIFI_SSID;
const char wifi_password[]  = WIFI_PASS;
const char endpoint_address[]      = AWS_IOT_ENDPOINT_ADDRESS;
const char* certificate  = THING_CERTIFICATE;

const int publish_interval = 10000; // Interval between publising, 10 sec
int co2;  // CO2 concentration in ppm
byte cmd[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79}; // Command to CO2 sensor
byte sensorVal[9];
int threshold = 600; // CO2 concentration threshold
int yPosition = 95; // Display cursor y position

// Clients for Wi-Fi, SSL, and MQTT libraries
WiFiClient    wifi_client;            
BearSSLClient ssl_client(wifi_client);
MqttClient    mqtt_client(ssl_client);

// The MQTT client Id used in the connection to 
// AWS IoT Core. AWS IoT Core expects a unique client Id
// and the policy restricts which client Id's can connect
// to your broker endpoint address.
//
// NOTE: client_Id is set after the ATECC608 is initialized 
// to the value of the unique chip serial number.
String client_Id = "";

// Used to track how much time has elapsed since last MQTT 
// message publish.
unsigned long last_publish_millis = 0;

// Retrieves stored time_t object and returns seconds since
// Unix Epoch time.
unsigned long get_stored_time() {
  time_t seconds_since_epoch;
  struct tm time_info;
  
  if (!getLocalTime(&time_info)) {
    Serial.println("Failed to retrieve stored time.");
    return(0);
  }

  time(&seconds_since_epoch);
  return seconds_since_epoch;
}

// Retrieves the current time from the defined NTP server.
//
// NOTE: Time is stored in the ESP32, not the RTC using configTime.
void get_NTP_time() {
  configTime(gmt_offset_sec, daylight_offset_sec, ntp_server);
}

// Connects to the specified Wi-Fi network using the defined
// SSID and password. A failed connection retries every 5 seconds.
void connect_wifi() {
  Serial.print("Attempting to connect to SSID: ");
  Serial.println(wifi_ssid);

  while (WiFi.begin(wifi_ssid, wifi_password) != WL_CONNECTED) {
    Serial.println("Failed to connect. Retrying...");
    delay(5000);
  }
  Serial.println("Successfully connected to the Wi-Fi network!");
}

// Connects to the MQTT message broker, AWS IoT Core using
// the defined endpoint address at the default port 8883.
// A failed connection retries every 5 seconds.
// On a successful connection, it will then subscribe to a
// default MQTT topic, that is listening to everything
// on starting with a topic filter of the device name/.
// Changing the topic filter can cause the broker to disconnect
// the client session after successfully connecting if the thing policy 
// doesn't have sufficient authorization.
//
// NOTE: You must use the ATS endpoint address.
void connect_AWS_IoT() {
#define PORT 8883
  Serial.print("Attempting to AWS IoT Core message broker at mqtt:\\\\");
  Serial.print(endpoint_address);
  Serial.print(":");
  Serial.println(PORT);

  while (!mqtt_client.connect(endpoint_address, PORT)) {
    Serial.println("Failed to connect to AWS IoT Core. Retrying...");
    delay(5000);
  }

  Serial.println("Connected to AWS IoT Core!\n");

  // Subscribe to an MQTT topic.

  // NOTE: "#" is a wildcard, meaning that it will subscribe to all 
  // messages that start with the topic 'client_Id/' with the client_Id
  // being your device serial number.
  Serial.println(client_Id + "/#");
  //mqtt_client.subscribe(client_Id + "/#");
}

// Publishes the MQTT message string to the MQTT broker. The thing must
// have authorization to publish to the topic, otherwise the connection
// to AWS IoT Core will disconnect.
// 
// NOTE: Use the "print" interface to send messages.
void publish_MQTT_message() {
  Serial.println("Publishing:");

  mqtt_client.beginMessage(client_Id + "/1"); // AWS IoT publish topic
  //mqtt_client.print(millis());
  mqtt_client.print("{\"id\": 1, \"co2\": ");
  mqtt_client.print(co2);
  mqtt_client.print("}");
  mqtt_client.endMessage();
}

// Callback for messages received on the subscribed MQTT
// topics. Use the Stream interface to loop until all contents
// are read.
void message_received_callback(int messageSize) {
  // we received a message, print out the topic and contents
  Serial.print("Received a message with topic '");
  Serial.print(mqtt_client.messageTopic());
  Serial.print("', length ");
  Serial.print(messageSize);
  Serial.println(" bytes:");

  while (mqtt_client.available()) {
    Serial.print((char)mqtt_client.read());
  }
  Serial.println("\n");
}

// Initialize the ATECC608 secure element to use the stored private
// key in establishing TLS and securing network messages.
//
// NOTE: The definitions for I2C are in the platformio.ini file and
// not meant to be changed for the M5Stack Core2 for AWS IoT EduKit.
void atecc608_init(){
  Wire.begin(ACTA_I2C_SDA_PIN, ACTA_I2C_SCL_PIN, ACTA_I2C_BAUD);

  if (!ECCX08.begin(0x35)) {
    Serial.println("ATECC608 Secure Element initialization error!");
    while (1);
  }
  Serial.print("Device serial number: ");
  Serial.println(ECCX08.serialNumber());
}

void setup() {
  // Initialize the M5Stack Core2 for AWS IoT EduKit
  bool LCDEnable = true;
  bool SDEnable = false; 
  bool SerialEnable = true;
  bool I2CEnable = false;
  mbus_mode_t MBUSmode = kMBusModeOutput;
  M5.begin(LCDEnable, SDEnable, SerialEnable, I2CEnable, MBUSmode);
  
  // Initialize the secure element, connect to Wi-Fi, sync time
  atecc608_init();
  connect_wifi();
  get_NTP_time();
  
  // Set a callback to get the current time
  // used to validate the servers certificate
  ArduinoBearSSL.onGetTime(get_stored_time);

  // Uses the private key slot from the secure element and the
  // certificate you pasted into arduino_secrets.h,
  ssl_client.setEccSlot(ACTA_SLOT_PRIVATE_KEY, certificate);

  // The client Id for the MQTT client. Uses the ATECC608 serial number
  // as the unique client Id, as registered in AWS IoT, and set in the
  // thing policy.
  client_Id = ECCX08.serialNumber();
  client_Id.toLowerCase();
  mqtt_client.setId(client_Id);

  // The MQTT message callback, this function is called when 
  // the MQTT client receives a message on the subscribed topic
  mqtt_client.onMessage(message_received_callback);

  M5.Lcd.fillScreen(GREEN);
  M5.Lcd.setCursor(40, yPosition); //Move the cursor position to (x,y). 
  M5.Lcd.setTextColor(WHITE); //Set the font color to white. 
  M5.Lcd.setTextSize(5);  //Set the font size. 
  M5.Lcd.printf("Starting");  //Serial output format string. 
  Serial1.begin(9600, SERIAL_8N1, 13, 14); 
}

void loop() {
  // Attempt to reconnect to Wi-Fi if disconnected.
  if (WiFi.status() != WL_CONNECTED) {
    connect_wifi();
  }

  // Attempt to reconnect to AWS IoT Core if disconnected.
  if (!mqtt_client.connected()) {
    connect_AWS_IoT();
  }

  // Poll for new MQTT messages and send keep alive pings.
  mqtt_client.poll();

  // Publish a message every 3 seconds or more.
  if (millis() - last_publish_millis > publish_interval) {
    // Send command to CO2 sensor
    Serial1.write(cmd,sizeof cmd);

    // Receive measured CO2 concentration in ppm
    Serial1.readBytes((char *)sensorVal, sizeof sensorVal);
    co2 = sensorVal[2] * 256 + sensorVal[3];

    // Ignore abnormal CO2 concentrations
    if (co2 > 5000){
      co2 = 5000;
    } else if (co2 < 400){
      co2 = 400;
    }
    Serial.println(co2);
    
    // Fill screen
    if (co2 > threshold){
      M5.Lcd.clear(RED); 
      M5.Lcd.setTextColor(WHITE);  
    } else {
      M5.Lcd.clear(GREEN); 
      M5.Lcd.setTextColor(BLACK); 
    }

    // Show CO2 concentration on the display
    int xPosition = 55; // Display cursor x position
    if (co2 > 1000){
      xPosition = 40;
    }
    M5.Lcd.setCursor(xPosition, yPosition);
    char str[10];
    sprintf(str, "%d", co2);
    M5.Lcd.printf(str);
    M5.Lcd.printf(" ppm");
    last_publish_millis = millis();
    publish_MQTT_message();
  }
}

main.cpp (Fan Controller)

Arduino
Receives MQTT messages from AWS IoT Core and turns on/off GPIO 26.
Modified from https://github.com/aws-samples/aws-iot-edukit-examples/blob/main/Basic_Arduino/src/main.cpp
/*
 * Core2 for AWS IoT EduKit
 * Arduino Basic Connectivity Example v1.0.1
 * main.cpp
 * 
 * Copyright (C) 2021 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <Arduino.h>
#include <M5Core2.h>
#include <driver/i2s.h>
#include <Wire.h>
#include <WiFi.h>
#include <FastLED.h>
#include <time.h>
#include <ArduinoBearSSL.h>
#include <ArduinoECCX08.h>
#include <ArduinoMqttClient.h>
#include "arduino_secrets.h"

// NTP server details.
//
// NOTE: GMT offset is in seconds, so multiply hours by
// 3600 (e.g. Pacific Time would be -8 * 3600)
const char* ntp_server = "pool.ntp.org";
const long  gmt_offset_sec = 0 * 3600;
const int   daylight_offset_sec = 3600;

// 
const char wifi_ssid[]      = WIFI_SSID;
const char wifi_password[]  = WIFI_PASS;
const char endpoint_address[]      = AWS_IOT_ENDPOINT_ADDRESS;
const char* certificate  = THING_CERTIFICATE;

// Fan controller
uint8_t fanPin = 14;
const int co2Monitors = 4; // Number of CO2 Monitors
boolean state[co2Monitors + 1]; // CO2 Monitor vent states

// Clients for Wi-Fi, SSL, and MQTT libraries
WiFiClient    wifi_client;            
BearSSLClient ssl_client(wifi_client);
MqttClient    mqtt_client(ssl_client);

// The MQTT client Id used in the connection to 
// AWS IoT Core. AWS IoT Core expects a unique client Id
// and the policy restricts which client Id's can connect
// to your broker endpoint address.
//
// NOTE: client_Id is set after the ATECC608 is initialized 
// to the value of the unique chip serial number.
String client_Id = "";

// Used to track how much time has elapsed since last MQTT 
// message publish.
unsigned long last_publish_millis = 0;

// Retrieves stored time_t object and returns seconds since
// Unix Epoch time.
unsigned long get_stored_time() {
  time_t seconds_since_epoch;
  struct tm time_info;
  
  if (!getLocalTime(&time_info)) {
    Serial.println("Failed to retrieve stored time.");
    return(0);
  }

  time(&seconds_since_epoch);
  return seconds_since_epoch;
}

// Retrieves the current time from the defined NTP server.
//
// NOTE: Time is stored in the ESP32, not the RTC using configTime.
void get_NTP_time() {
  configTime(gmt_offset_sec, daylight_offset_sec, ntp_server);
}

// Connects to the specified Wi-Fi network using the defined
// SSID and password. A failed connection retries every 5 seconds.
void connect_wifi() {
  Serial.print("Attempting to connect to SSID: ");
  Serial.println(wifi_ssid);

  while (WiFi.begin(wifi_ssid, wifi_password) != WL_CONNECTED) {
    Serial.println("Failed to connect. Retrying...");
    delay(5000);
  }
  Serial.println("Successfully connected to the Wi-Fi network!");
}

// Connects to the MQTT message broker, AWS IoT Core using
// the defined endpoint address at the default port 8883.
// A failed connection retries every 5 seconds.
// On a successful connection, it will then subscribe to a
// default MQTT topic, that is listening to everything
// on starting with a topic filter of the device name/.
// Changing the topic filter can cause the broker to disconnect
// the client session after successfully connecting if the thing policy 
// doesn't have sufficient authorization.
//
// NOTE: You must use the ATS endpoint address.
void connect_AWS_IoT() {
#define PORT 8883
  Serial.print("Attempting to AWS IoT Core message broker at mqtt:\\\\");
  Serial.print(endpoint_address);
  Serial.print(":");
  Serial.println(PORT);

  while (!mqtt_client.connect(endpoint_address, PORT)) {
    Serial.println("Failed to connect to AWS IoT Core. Retrying...");
    delay(5000);
  }

  Serial.println("Connected to AWS IoT Core!\n");

  // Subscribe to an MQTT topic.

  // NOTE: "#" is a wildcard, meaning that it will subscribe to all 
  // messages that start with the topic 'client_Id/' with the client_Id
  // being your device serial number.
  Serial.println(client_Id + "/#");
  mqtt_client.subscribe(client_Id + "/#");
}

// Publishes the MQTT message string to the MQTT broker. The thing must
// have authorization to publish to the topic, otherwise the connection
// to AWS IoT Core will disconnect.
// 
// NOTE: Use the "print" interface to send messages.
void publish_MQTT_message() {
  Serial.println("Publishing:");
  mqtt_client.beginMessage(client_Id + "/1"); // AWS IoT publish topic
  mqtt_client.print(millis());
  mqtt_client.endMessage();
}

// Callback for messages received on the subscribed MQTT
// topics. Use the Stream interface to loop until all contents
// are read.
void message_received_callback(int messageSize) {
  // we received a message, print out the topic and contents
  Serial.print("Received a message with topic '");
  Serial.print(mqtt_client.messageTopic());
  Serial.print("', length ");
  Serial.print(messageSize);
  Serial.println(" bytes:");
  
  // Store message into char array
  int i=0;
  char str[messageSize+1];
  while (mqtt_client.available()) {
    str[i]=(char)mqtt_client.read();
    Serial.print(i);
    Serial.print(": ");
    Serial.println(str[i]);
    i++;
  }

  // Extract fan and id from array
  char vent = str[9];
  Serial.print("vent: ");
  Serial.println(vent);
  char id = str[18];
  Serial.print("id: ");
  Serial.println(id);
  
  // Assign CO2 monitor a vent state
  int vent_ = vent - '0';
  int id_ = id - '0';
  if (vent_ == 1){
    state[id_] = true;
  } else if  (vent_ == 0){
    state[id_] = false;
  }
  
  // If any of the CO2 monitor vent state is true, set fan true
  boolean fan = false;
  for (int i =0; i < co2Monitors + 1; i++){
    if (state[i]){
      fan = true;
    }
  }
  
  // Control screen and GPIO
  if (fan){
    Serial.println("fan on");
    M5.Lcd.fillScreen(RED);
    digitalWrite(fanPin, HIGH);
   } else {
    Serial.println("fan off");
    M5.Lcd.fillScreen(GREEN);
    digitalWrite(fanPin, LOW);
  }

  Serial.println("\n");
}

// Initialize the ATECC608 secure element to use the stored private
// key in establishing TLS and securing network messages.
//
// NOTE: The definitions for I2C are in the platformio.ini file and
// not meant to be changed for the M5Stack Core2 for AWS IoT EduKit.
void atecc608_init(){
  Wire.begin(ACTA_I2C_SDA_PIN, ACTA_I2C_SCL_PIN, ACTA_I2C_BAUD);

  if (!ECCX08.begin(0x35)) {
    Serial.println("ATECC608 Secure Element initialization error!");
    while (1);
  }
  Serial.print("Device serial number: ");
  Serial.println(ECCX08.serialNumber());
}

void setup() {
  // Initialize the M5Stack Core2 for AWS IoT EduKit
  bool LCDEnable = true;
  bool SDEnable = false; 
  bool SerialEnable = true;
  bool I2CEnable = false;
  mbus_mode_t MBUSmode = kMBusModeOutput;
  M5.begin(LCDEnable, SDEnable, SerialEnable, I2CEnable, MBUSmode);

  pinMode(fanPin, OUTPUT);

  // Initialize the secure element, connect to Wi-Fi, sync time
  atecc608_init();
  connect_wifi();
  get_NTP_time();
  
  // Set a callback to get the current time
  // used to validate the servers certificate
  ArduinoBearSSL.onGetTime(get_stored_time);

  // Uses the private key slot from the secure element and the
  // certificate you pasted into arduino_secrets.h,
  ssl_client.setEccSlot(ACTA_SLOT_PRIVATE_KEY, certificate);

  // The client Id for the MQTT client. Uses the ATECC608 serial number
  // as the unique client Id, as registered in AWS IoT, and set in the
  // thing policy.
  client_Id = ECCX08.serialNumber();
  client_Id.toLowerCase();
  mqtt_client.setId(client_Id);

  // The MQTT message callback, this function is called when 
  // the MQTT client receives a message on the subscribed topic
  mqtt_client.onMessage(message_received_callback);

  M5.Lcd.fillScreen(GREEN);
}

void loop() {
  // Attempt to reconnect to Wi-Fi if disconnected.
  if (WiFi.status() != WL_CONNECTED) {
    connect_wifi();
  }

  // Attempt to reconnect to AWS IoT Core if disconnected.
  if (!mqtt_client.connected()) {
    connect_AWS_IoT();
  }

  // Poll for new MQTT messages and send keep alive pings.
  mqtt_client.poll();

}

rule.json

JSON
AWS IoT Core Rule definition with 3 actions.
You need 4 of these for each CO2 Monitor.
{
    "ruleArn": "arn:aws:iot:us-east-1:123456789012:rule/AHS_CO2_1",
    "rule": {
        "ruleName": "AHS_CO2_1",
        "sql": "SELECT * FROM '0123253988a0614401/1'",
        "createdAt": "2021-09-17T02:31:02+09:00",
        "actions": [
            {
                "iotSiteWise": {
                    "putAssetPropertyValueEntries": [
                        {
                            "propertyAlias": "/AHS/MainArea",
                            "propertyValues": [
                                {
                                    "value": {
                                        "integerValue": "${co2}"
                                    },
                                    "timestamp": {
                                        "timeInSeconds": "${floor(timestamp() / 1E3)}",
                                        "offsetInNanos": "${(timestamp() % 1E3) * 1E6}"
                                    }
                                }
                            ]
                        }
                    ],
                    "roleArn": "arn:aws:iam::123456789012:role/service-role/AWSIoTEduKit"
                }
            },
            {
                "iotEvents": {
                    "inputName": "AHSCO2",
                    "roleArn": "arn:aws:iam::123456789012:role/service-role/AWSIoTEduKit"
                }
            },
            {
                "dynamoDB": {
                    "tableName": "CO2",
                    "roleArn": "arn:aws:iam::123456789012:role/service-role/AWSIoTEduKit",
                    "hashKeyField": "Id",
                    "hashKeyValue": "${clientid()}",
                    "hashKeyType": "STRING",
                    "rangeKeyField": "Timestamp",
                    "rangeKeyValue": "${timestamp()}",
                    "rangeKeyType": "STRING"
                }
            }
        ],
        "ruleDisabled": false,
        "awsIotSqlVersion": "2016-03-23"
    }
}

input.json

JSON
AWS IoT Events input definition
{
  "co2": 1234,
  "id": 1
}

detector-model.json

JSON
AWS IoT Events detector model definition
{
  "detectorModelDefinition": {
    "states": [{
        "stateName": "VENT",
        "onInput": {
          "events": [],
          "transitionEvents": [{
            "eventName": "belowThreshold",
            "condition": "$input.AHSCO2.co2 < 600",
            "actions": [],
            "nextState": "NORMAL"
          }]
        },
        "onEnter": {
          "events": [{
            "eventName": "activateFan",
            "condition": "true",
            "actions": [{
              "iotTopicPublish": {
                "mqttTopic": "0123fb1d2c5404c201/1",
                "payload": {
                    "contentExpression": "'{\\\"vent\\\": 1, \\\"id\\\": ${$input.AHSCO2.id}}'",
                    "type": "JSON"
                }
              }
            }]
          }]
        },
        "onExit": {
          "events": [{
            "eventName": "deactivateFan",
            "condition": "true",
            "actions": [{
              "iotTopicPublish": {
                "mqttTopic": "0123fb1d2c5404c201/1",
                "payload": {
                    "contentExpression": "'{\\\"vent\\\": 0, \\\"id\\\": ${$input.AHSCO2.id}}'",
                    "type": "JSON"
                }
              }
            }]
          }]
        }
      },
      {
        "stateName": "NORMAL",
        "onInput": {
          "events": [{
            "eventName": "inputEvent",
            "condition": "$input.AHSCO2.co2 < 600",
            "actions": [{
              "resetTimer": {
                "timerName": "timer"
              }
            }]
          }],
          "transitionEvents": [{
            "eventName": "aboveThresholdFor1min",
            "condition": "timeout(\"timer\")",
            "actions": [{
              "clearTimer": {
                "timerName": "timer"
              }
            }],
            "nextState": "VENT"
          }]
        },
        "onEnter": {
          "events": [{
            "eventName": "createTimer",
            "condition": "true",
            "actions": [{
              "setTimer": {
                "timerName": "timer",
                "seconds": 60,
                "durationExpression": null
              }
            }]
          }]
        },
        "onExit": {
          "events": []
        }
      }
    ],
    "initialStateName": "NORMAL"
  },
  "detectorModelDescription": null,
  "detectorModelName": "AHSCO2Model",
  "evaluationMethod": "BATCH",
  "key": "Id",
  "roleArn": "arn:aws:iam::xxxxxxxxxxx:role/service-role/AHSCO2ModelRole"
}

asset.json

JSON
AWS IoT SiteWise asset definition
{
    "assetSummaries": [
        {
            "id": "ffef3df5-3c69-4aff-b9a2-3d8e72fabb0e",
            "arn": "arn:aws:iotsitewise:us-east-1:723939311032:asset/ffef3df5-3c69-4aff-b9a2-3d8e72fabb0e",
            "name": "AHS",
            "assetModelId": "ed1dae64-2ef3-433a-9031-31c87a987fc3",
            "creationDate": "2021-09-09T04:58:57+09:00",
            "lastUpdateDate": "2021-09-09T05:05:40+09:00",
            "status": {
                "state": "ACTIVE"
            },
            "hierarchies": []
        }
    ]
}

model.json

JSON
AWS IoT SiteWise model definition
{
    "assetModelId": "ed1dae64-2ef3-433a-9031-31c87a987fc3",
    "assetModelArn": "arn:aws:iotsitewise:us-east-1:723939311032:asset-model/ed1dae64-2ef3-433a-9031-31c87a987fc3",
    "assetModelName": "AHSCO2",
    "assetModelProperties": [
        {
            "id": "8ff3e37c-2ec1-48b3-bcac-bc2860897a96",
            "name": "Main Area CO2",
            "dataType": "INTEGER",
            "unit": "ppm",
            "type": {
                "measurement": {}
            }
        },
        {
            "id": "797e970b-47cd-4c86-a6e5-b723006d0e98",
            "name": "Video Studio CO2",
            "dataType": "INTEGER",
            "unit": "ppm",
            "type": {
                "measurement": {}
            }
        },
        {
            "id": "35d6c91c-e7af-41ec-a4e5-201ef4c3e755",
            "name": "Hallway CO2",
            "dataType": "INTEGER",
            "unit": "ppm",
            "type": {
                "measurement": {}
            }
        },
        {
            "id": "b2471290-7e57-43ba-9623-c483960d2ae4",
            "name": "Restroom CO2",
            "dataType": "INTEGER",
            "unit": "ppm",
            "type": {
                "measurement": {}
            }
        }
    ],
    "assetModelHierarchies": [],
    "assetModelCompositeModels": [],
    "assetModelCreationDate": "2021-09-09T04:58:31+09:00",
    "assetModelLastUpdateDate": "2021-09-09T05:05:30+09:00",
    "assetModelStatus": {
        "state": "ACTIVE"
    }
}

Credits

Steve Kasuya

Steve Kasuya

2 projects • 4 followers
I am an IoT enthusiast.

Comments