Companies are starting to reveal their return to work policies for their remote workers. A return to office is bound to increase the number of new COVID-19 cases. However, there may be a way to reduce the transmission of the COVID-19 virus within the office. Generally speaking, offices have cubicles that help isolate direct person-person transmission, but there are still hotspots of transmission, especially in shared spaces. Meeting rooms are one of the worst culprits, as meeting rooms are not only confined and quickly shared among different groups but are also only cleaned at the end of the day. Leaving the responsibility to any shared action, like watering an office plant, to whoever is in the meeting room is rarely if ever upheld. Even markers for whiteboards are rarely replaced. Furthermore, consuming multiple disinfectant wipes/canisters per day/week is extremely wasteful and does not scale with number of meetings. So, I propose the solution of a meeting integrated refillable IoT device that disinfects meeting rooms have meetings have occurred.
How does it work?My solution consists of a full-stack app, as well as an embedded device. There are a couple of working parts so I'll first start by giving a high-level overview of the flow of the system, and then explain in detail. This way, those following the next section titled How did I build it? can understand why I did what I did.
Here is a high-level overview of the system:
My solution begins on a full-stack app. I have a front-end that displays a GUI:
When the parameters are set, and the 'Check Availability' button is toggled, the back-end takes a look at an AWS DynamoDB table of selected meeting intervals and returns a list of available meetings along with the rooms that the meeting intervals are available in. Here is an example of what the table looks like:
To simplify the development process, a couple of caveats were set. Meetings only start every quarter hour lasting multiples of quarter hours, and Room 1 is hardcoded to correspond to my Core2ForAWS device. Once the meeting for Room 1 is selected, the device shadow for the Core2ForAWS element is updated with the meeting interval selected. The device shadow (to be explained later) will look something like this:
Device Logic
The core2ForAWS element will parse the meeting interval information from the device shadow, and populate a meeting interval array of size 96. Each index in the array corresponds to meeting state of that index's corresponding 15 minute interval. Since 1 day is 24 hours, there are 96 intervals of 15 minutes each. The 15 minute interval reference begins at 12:00 AM. 12:00 AM is considered to be equivalent to 0000 minutes, and the 0th 15 minute interval. Therefore, index 0 for the array of 15 minute intervals corresponds to the meeting state for the meeting interval from 12:00 AM to 12:15 AM. If a meeting from 12:00 AM to 12:15 AM is scheduled, then the array's index 0 will be marked with a '1' to denote a meeting interval, and '0' if there is not a scheduled for the 12:00AM to 12:15 AM meeting interval.
Now the embedded device that I coined 'mister' (which is the entirety of the device, including the core2ForAWS element), takes over. Mister checks each 15 minute interval edge, and only activates when the edge occurs between a meeting that had just finished and a 15 minute interval that has no meeting. A meeting interval edge is the boundary between two meeting intervals. An example of this would be the boundary between the 12:00AM->12:15AM meeting interval and the 12:15->12:30 meeting interval. So, the activation condition occurs for example, if mister checks at 12:15 and notices that there was a meeting at from 12:00AM -> 12:15AM and that there is no upcoming meeting from 12:15AM->12:30AM meeting interval. In terms of indexes, we can express this as any transition from any array[j] = 1 to array[j+1] = 0 for all j >= 0 && j < = 94.
Once such an 1->0 edge is found, mister's PIR sensor is activated and checks for movement every 2 minutes in case the meeting that had just occurred is carrying over into another meeting interval. If activity is detected until the next meeting interval, then the current meeting interval is marked as a '1' before checking the upcoming meeting interval edge, because there was a meeting interval's worth of activity. This diagram may clarify this idea:
Let's say currently, it's some time > 8:00am and < 8:15 am, represented by the 1. There's no actual meeting that was scheduled for 8:00am, so mister's internal representation of meeting intervals is that at array index: 32( 8:00am is 8 hours from 12:00am, 8 * 4 = 32 = array index of 8:00am) is a '0'. If the PIR keeps going off until 8:15am, represented by the 2, then the interval at index 32 gets marked as a '1'. Index 32 is marked as a '1' because the 15 minute meeting interval(8:00am) that was not meant to host a meeting, still hosted 15 minutes of activity.
In the case that the PIR sensor doesn't detect activity for 2 minutes, a secondary check activates. First, a chime occurs, as an effort to alert anyone inside the room of the impending spray-down, then a device screen that shows a countdown from 30s with a confirm button is displayed ( the video demo will show this). If the confirmation screen is ignored for the full duration, then mister with start the servo and spray down the room, and wait until the next 15 minute interval. If activity is indeed confirmed, then mister will go back to return back to the PIR logic.
How did I build it ?I started by spending some time learning JS, reactJS: https://reactjs.org/docs/getting-started.html and nodeJS: https://nodejs.org/en/docs/. I'll explain the AWS DynamoDB and AWS IoT Core integrations and link my full-stack app repositories below.
I first started by setting up my Core2ForAWS here: https://edukit.workshop.aws/en/getting-started.html by just walking through the steps. Then, I set up the AWS IoT Core by following the directions here: https://edukit.workshop.aws/en/blinky-hello-world/prerequisites.html . By this stage, you should be able to get your AWS IoT endpoint, and the IAM User you created should have this policy:
If you can't find where to add this policy, sign into AWS Console with your root email, then click on 'IAM'. You should see this on the left:
Click on 'Users', then click on the user you had created. You will see something like this:
Add the AWSIoTFullAccess policy.
Here's my interpretation of what's going on. The AWS account that you created contains all of the resources, like AWS IoT Core, DynamoDB, IAM, etc. The IAM User that you created is an authenticated entity that can access the AWS account's resources based on the IAM Users' policies. When the command 'aws configure' is run on the CLI with the IAM User's keys, a configure file is created and stored on your laptop with the user's keys. When an AWS SDK API is evoked, the configure file is found. Some mediation then occurs such that the API getting evoked has the same permissions as the policies of the IAM User whose keys were configured with the 'aws configure' command.
Now, add the AmazonDynamoDBFullAccess policy to your IAM user as well. It should look like this:
Here are the relevant code snippets that interact with AWS with comments:
AWS.config.dynamodb = {
region: "us-west-2",
//replace endpoint with own endpoint
endpoint: "https://dynamodb.us-west-2.amazonaws.com",
};
AWS.config.iotdata = {
region: "us-west-2",
//replace endpoint with own endpoint
endpoint: "a36ozlrpzvmdr4-ats.iot.us-west-2.amazonaws.com",
};
// setting up the necessary AWS SDK objects
let dynamodb = new AWS.DynamoDB();
let docClient = new AWS.DynamoDB.DocumentClient();
let iotdata = new AWS.IotData();
// parameters for the DynamoDB table to be created
// replace TABLE_NAME with own table name
// replace PRIMARY_KEY with own primary key name
// replace SECONDARY_KEY with own secondary key name
let params = {
TableName: TABLE_NAME,
KeySchema: [
{ AttributeName: PRIMARY_KEY, KeyType: "HASH" },
{ AttributeName: SECONDARY_KEY, KeyType: "RANGE" },
],
AttributeDefinitions: [
{ AttributeName: PRIMARY_KEY, AttributeType: "S" },
{ AttributeName: SECONDARY_KEY, AttributeType: "S" },
],
ProvisionedThroughput: {
ReadCapacityUnits: 10,
WriteCapacityUnits: 10,
},
};
// the API that creates the DynamoDB table
dynamodb.createTable(params, function (err, data) {
if (err) {
console.error(
"Unable to create table. Error JSON:",
JSON.stringify(err, null, 2)
);
} else {
console.log(
"Created table. Table description JSON:",
JSON.stringify(data, null, 2)
);
}
});
// parameters for shadow
// payload is what to update the shadow with
// thingName is the name of the thing that you registered with AWS IoT Core
let shadowParams = {
// core2IoTPayload should be a JSON of your own choice
payload: JSON.stringify(core2IoTPayload),
// replace thingName with the thing name of own device
thingName: "01231bd1cbac971101"
};
// API to update shadow
iotdata.updateThingShadow(shadowParams, function (err, data) {
if (err) {
console.error("Unable to send to core2", JSON.stringify(err, null, 2));
} else {
console.log("send to core2 succeeded:");
}
});
// API to update DynamoDB table
docClient.put(putParams, function (err, data) {
if (err) {
res.send(JSON.stringify("error"));
} else {
res.send(JSON.stringify("success"));
}
});
The next step is creating the mister device. I wanted an electric water pump that I could activate with a transistor, so I bought one of these:
I took it apart, and it looked like this:
As it turns out, it turns on by just shorting two terminals with a switch(you can see this in the schematics section). I glued the electronics to some cardboard, along with a servo and a PIR sensor, and jumping ahead a few steps (I forgot to take intermediate pictures), I ended up with this:
The electric pump mechanism is housed in the middle section, right above the empty clear canister. The curved 'backwards r' segment in the center of the four pillars deflects the mist output. On top, I have the PIR sensor, transistor to control the pump mechanism, and the Core2ForAWS device. Once the mist turns on, the electric pump mechanism shoots disinfectant in a mist form directly upwards, from the canister. The servo kicks into action and the curved 'backwards r' segment deflects the mist in 360 degree fashion. If I had a 3D printer, this device would have been designed drastically differently, and much more efficiently (the mist deflection should swivel the nozzle of the mist output, not the mist itself). For the PIR, I set it to the non-retriggering mode like this, so each movement will cause a edge triggered interrupt :
You can read more about the tradeoffs here: https://learn.adafruit.com/pir-passive-infrared-proximity-motion-sensor/testing-a-pir
For the servo, I had turned a 180 degree servo into a continuous rotation servo, by following the instructions here: https://www.youtube.com/watch?v=mRO0wfSQ6jw
!!!! NOTE !!!! The circuit diagram in the schematics needs some clarification, the 5V and GND lines of the servo should be connected to an external battery or the input cable to the core2ForAWS !! If you connect to an external battery, make sure the ground of the battery is connected to the ground of the core2ForAWS. The wire connected from the the core2ForAWS to the base of the transistor is PIN26 on the core2ForAWS. I ran out of CircuitLab free trial before I could make the changes.
The exact steps on getting the core2ForAWS component up and running as well as the full-stack app is listed below in the code section.
To get started with the core2ForAWS component, follow the readme here: https://github.com/daxlar/mister
Earlier, I had mentioned a device shadow, and I'd like to first invite you to take a look at this link here: https://edukit.workshop.aws/en/smart-thermostat/data-sync.html that explains what a device shadow is. You can think about a device shadow as a communication medium between an AWS IoT 'thing', and external influences, namely a full stack app. In reality, it's more complicated, but it's outside of the scope of this write-up. Essentially, what happens is that the full stack app writes to this device shadow, and the core2ForAWS, which is provisioned and connected to the AWS IoT Core, gets notified with the changes that were just written to the device shadow.
To get started with the full-stack app, follow the READMEs here: https://github.com/daxlar/mister-frontend
and here:
https://github.com/daxlar/mister-backend
The code is very literal and the explanation above likely explains the code better than inserting code blobs throughout this writeup.
The wiring of device will be listed in the 'Schematics' portion, and all the code will be in the 'Code' section. Onto the demo!
Demo
Comments