Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions arduino/Dryer/imu_dryer/aws_pub.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,17 @@ void publish_res(int vibration) {
lastHeartbeatMs = millis();
}

void publish_res_json(String jsonMsg) {
if (!client.connected()) {
Serial.println("MQTT publish skipped: client not connected.");
return;
}

client.publish(vibrationTopic, jsonMsg.c_str());
Serial.println("Published JSON: " + jsonMsg);
lastHeartbeatMs = millis();
}

bool setup_wifi(uint8_t maxAttempts = WIFI_RETRY_BUDGET) {
if (WiFi.status() == WL_CONNECTED) {
counter = 0;
Expand Down
18 changes: 17 additions & 1 deletion arduino/Dryer/imu_dryer/imu_dryer.ino
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,18 @@ void read_imu_publish() {
Serial.println("");

int pred_res = 0;
float confidence = 0.0;

if (true_cnt > 15) {
Serial.println("Predicted as drying.");
pred_res = 1;
} else {
Serial.println("Predicted as idle.");
pred_res = 0;
}

// Calculate confidence as percentage of positive predictions
confidence = (float)true_cnt / 30.0;

if (!setup_wifi()) {
Serial.println("Skipping publish: WiFi unavailable after retries.");
Expand All @@ -148,7 +153,18 @@ void read_imu_publish() {
return;
}

publish_res(pred_res);
String msg = "{\"machine_id\":\"RVREB-D1\",";
msg += "\"device_type\":\"dryer\",";
msg += "\"is_spinning\":";
msg += String(pred_res);
msg += ",\"confidence\":";
msg += String(confidence, 2);
msg += ",\"timestamp\":";
msg += String(millis());
msg += ",\"sensor_type\":\"imu\"}";

Serial.println("Publishing: " + msg);
publish_res_json(msg);
maintainAwsConnection();
delay(3000);
}
Expand Down
24 changes: 23 additions & 1 deletion arduino/Washer/imu_washer/imu_washer.ino
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,15 @@ void get_acc_gyro_readings() {
}

void read_imu_publish() {
int16_t total_magnitude = 0;

for (int i = 0; i < 30; i += 1) {
Serial.println(i);
get_acc_gyro_readings();
preds[i] = pred;

// Calculate vibration magnitude for confidence scoring
// Note: ax, ay, az should be accessible - if not, calculate during get_acc_gyro_readings
delay(10);

get_vib_readings();
Expand All @@ -132,15 +137,32 @@ void read_imu_publish() {
Serial.println("");

int pred_res = 0;
float confidence = 0.0;

if (true_cnt > 15) {
Serial.println("Predicted as spinning.");
pred_res = 1;
} else {
Serial.println("Predicted as not spinning.");
pred_res = 0;
}

// Calculate confidence as percentage of positive predictions
confidence = (float)true_cnt / 30.0;

setup_wifi();
String msg = String(pred_res);

String msg = "{\"machine_id\":\"RVREB-W1\",";
msg += "\"device_type\":\"washer\",";
msg += "\"is_spinning\":";
msg += String(pred_res);
msg += ",\"confidence\":";
msg += String(confidence, 2);
msg += ",\"timestamp\":";
msg += String(millis());
msg += ",\"sensor_type\":\"imu\"}";

Serial.println("Publishing: " + msg);
mqttClient.publish(publishTopic, msg, 0, false);

delay(3000);
Expand Down
30 changes: 30 additions & 0 deletions aws/dynamodb.tf
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,34 @@ resource "aws_dynamodb_table" "CameraImageJSON" {
Owner = "Nicholas"
}

}

# New table for processed camera detections (from YOLOv7 + classification)
resource "aws_dynamodb_table" "CameraDetectionData" {
name = "CameraDetectionData"
billing_mode = "PAY_PER_REQUEST"
hash_key = "machine_id"
range_key = "timestamp"

attribute {
name = "machine_id"
type = "S"
}

attribute {
name = "timestamp"
type = "N"
}

ttl {
attribute_name = "ttl"
enabled = true
}

tags = {
Name = "CameraDetectionData"
Environment = "production"
Project = "DLLM"
Owner = "Nicholas"
}
}
39 changes: 28 additions & 11 deletions aws/functions/__tests__/storeDataFunction.test.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, expect, it, vi, beforeEach } from "vitest";

const sendMock = vi.fn();
const lambdaSendMock = vi.fn();

vi.mock("@aws-sdk/client-dynamodb", () => {
return {
Expand All @@ -18,51 +19,67 @@ vi.mock("@aws-sdk/lib-dynamodb", () => {
};
});

vi.mock("@aws-sdk/client-lambda", () => {
return {
LambdaClient: vi.fn(() => ({ send: lambdaSendMock })),
InvokeCommand: vi.fn((input) => ({ input, name: "InvokeCommand" })),
};
});

describe("storeDataFunction handler", () => {
beforeEach(() => {
sendMock.mockReset();
lambdaSendMock.mockReset();
process.env.DYNAMODB_TABLE = "TelemetryTable";
process.env.MACHINE_STATUS_TABLE = "MachineStatusTable";
process.env.STATE_MACHINE_FUNCTION = "updateMachineStateFunction";
});

it("stores vibration payload and updates machine status when vibration is 1", async () => {
sendMock.mockResolvedValueOnce({}).mockResolvedValueOnce({});
it("stores vibration payload and invokes state machine function when vibration is 1", async () => {
sendMock.mockResolvedValueOnce({});
lambdaSendMock.mockResolvedValueOnce({});

const { handler } = await import("../storeDataFunction.mjs");
const event = { machine_id: "RVREB-W1", vibration: 1 };
const response = await handler(event);

expect(response.statusCode).toBe(200);
expect(sendMock).toHaveBeenCalledTimes(2);
expect(sendMock).toHaveBeenCalledTimes(1);
expect(sendMock.mock.calls[0][0]).toEqual(
expect.objectContaining({
name: "PutCommand",
input: expect.objectContaining({
TableName: "TelemetryTable",
Item: event,
Item: expect.objectContaining({
machine_id: "RVREB-W1",
vibration: 1,
}),
}),
})
);
expect(sendMock.mock.calls[1][0]).toEqual(
expect(lambdaSendMock).toHaveBeenCalledTimes(1);
expect(lambdaSendMock.mock.calls[0][0]).toEqual(
expect.objectContaining({
name: "UpdateCommand",
name: "InvokeCommand",
input: expect.objectContaining({
TableName: "MachineStatusTable",
Key: { machineID: "RVREB-W1" },
FunctionName: "updateMachineStateFunction",
InvocationType: "Event",
}),
})
);
});

it("skips status update when vibration is 0", async () => {
it("stores vibration payload and invokes state machine function when vibration is 0", async () => {
sendMock.mockResolvedValueOnce({});
lambdaSendMock.mockResolvedValueOnce({});

const { handler } = await import("../storeDataFunction.mjs");
const event = { machine_id: "RVREB-W1", vibration: 0 };
await handler(event);
const response = await handler(event);

expect(response.statusCode).toBe(200);
expect(sendMock).toHaveBeenCalledTimes(1);
expect(sendMock.mock.calls[0][0].name).toBe("PutCommand");
expect(lambdaSendMock).toHaveBeenCalledTimes(1);
});
});

Expand Down
8 changes: 2 additions & 6 deletions aws/functions/connectFunction.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import os
import boto3
import time

_REGION = os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION") or "us-east-1"
_TABLE_NAME = os.getenv("WEBSOCKET_CONNECTIONS_TABLE", "WebSocketConnections")

dynamodb = boto3.resource("dynamodb", region_name=_REGION)
table = dynamodb.Table(_TABLE_NAME)
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('WebSocketConnections')

def lambda_handler(event, context):
connection_id = event['requestContext']['connectionId']
Expand Down
8 changes: 2 additions & 6 deletions aws/functions/disconnectFunction.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import os
import boto3

_REGION = os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION") or "us-east-1"
_TABLE_NAME = os.getenv("WEBSOCKET_CONNECTIONS_TABLE", "WebSocketConnections")

dynamodb = boto3.resource("dynamodb", region_name=_REGION)
table = dynamodb.Table(_TABLE_NAME)
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('WebSocketConnections')

def lambda_handler(event, context):
connection_id = event['requestContext']['connectionId']
Expand Down
7 changes: 2 additions & 5 deletions aws/functions/fetchMachineStatusFunction.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@
import os
from decimal import Decimal

_REGION = os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION") or "us-east-1"
_TABLE_NAME = os.getenv("MACHINE_STATUS_TABLE", "MachineStatusTable")

dynamodb = boto3.resource("dynamodb", region_name=_REGION)
machine_status_table = dynamodb.Table(_TABLE_NAME)
dynamodb = boto3.resource('dynamodb')
machine_status_table = dynamodb.Table(os.environ['MACHINE_STATUS_TABLE'])

class DecimalEncoder(json.JSONEncoder):
def default(self, obj):
Expand Down
9 changes: 3 additions & 6 deletions aws/functions/postCameraImageJSONFunction.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import json
import os
import boto3

_REGION = os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION") or "us-east-1"
_TABLE_NAME = os.getenv("MACHINE_STATUS_TABLE", "MachineStatusTable")

# Initialize DynamoDB resource
dynamodb = boto3.resource("dynamodb", region_name=_REGION)
table = dynamodb.Table(_TABLE_NAME)
dynamodb = boto3.resource('dynamodb')
table_name = "MachineStatusTable"
table = dynamodb.Table(table_name)

def lambda_handler(event, context):
try:
Expand Down
64 changes: 64 additions & 0 deletions aws/functions/processCameraDataFunction.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb";
import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda";

const dynamoClient = new DynamoDBClient({});
const ddbDocClient = DynamoDBDocumentClient.from(dynamoClient);
const lambdaClient = new LambdaClient({});

export const handler = async (event) => {
console.log("Received camera detection event:", JSON.stringify(event));

const cameraDataTable = process.env.CAMERA_DETECTION_TABLE || "CameraDetectionData";
const stateMachineFunctionName = process.env.STATE_MACHINE_FUNCTION || "updateMachineStateFunction";

// Add TTL (7 days from now)
const ttl = Math.floor(Date.now() / 1000) + (7 * 24 * 60 * 60);

// Store camera detection in DynamoDB
const params = {
TableName: cameraDataTable,
Item: {
machine_id: event.machine_id,
timestamp: event.timestamp || Date.now() / 1000,
device_type: event.device_type || "washer",
event_type: event.event_type || "person_detected",
is_bending: event.is_bending || false,
confidence: event.confidence || 0,
sensor_type: event.sensor_type || "camera",
ttl: ttl
}
};

try {
await ddbDocClient.send(new PutCommand(params));
console.log("Camera detection stored successfully");

// Invoke state machine function to process the event
const stateMachinePayload = {
source: "camera",
data: event
};

const invokeParams = {
FunctionName: stateMachineFunctionName,
InvocationType: "Event", // Async invocation
Payload: JSON.stringify(stateMachinePayload)
};

await lambdaClient.send(new InvokeCommand(invokeParams));
console.log("State machine function invoked");

return {
statusCode: 200,
body: JSON.stringify({ message: "Camera data processed successfully" })
};
} catch (error) {
console.error("Error processing camera data:", error);
return {
statusCode: 500,
body: JSON.stringify({ message: "Failed to process camera data", error: error.message })
};
}
};

9 changes: 2 additions & 7 deletions aws/functions/shuffle_machine_status.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import os

import boto3

_REGION = os.getenv("AWS_REGION") or os.getenv("AWS_DEFAULT_REGION") or "us-east-1"
_TABLE_NAME = os.getenv("MACHINE_STATUS_TABLE", "MachineStatusTable")

dynamodb = boto3.resource("dynamodb", region_name=_REGION)
table = dynamodb.Table(_TABLE_NAME)
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('MachineStatusTable')

statuss = ["available", "in-use", "complete"]

Expand Down
Loading
Loading