Skip to content

Contributors

Abhishek Sha

Abhishek Sha

Machine Design

Ali Abdul Gafoor

Ali Abdul Gafoor

Production and Documentation

Ardradevi

Ardradevi

Electronics

Ashtami

Ashtami

User Interface

Mishael Sharaf

Mishael Sharaf

Firmware

Group Assignment Documentation


A Voice Controlled Smart Rotary Table

The lazy Susan reimagined

Slide

Slide.png

Video

1. Brainstorming

bs0.jpgbs1.jpgbs2.jpg

Finalizing The Project

Project Brief

This project is a playful reinterpretation of the traditional Lazy Susan, centered around the idea of shared access through controlled motion and automation. The system is built as a motorized rotary platform with indexed compartments that can be precisely positioned using voice or remote commands. Inspired by the original Lazy Susan, the table retains its core idea of shared access, but extends it through programmable control. Users can call specific sections of the table toward them, creating a responsive and interactive experience.

The system is designed as a flexible platform rather than a single-use object, adaptable for organizing tools, holding art supplies, or managing shared resources in a workspace. Its functioning relies on the integration of mechanical design, electronics, and embedded control: a geared rotary mechanism enables smooth motion, a motor driver and sensing system establish positional reference, and firmware logic ensures accurate, repeatable alignment. Together, these layers allow the table to respond reliably to user input while maintaining precision in movement.

Team Members & Roles

1.gp

Project Planning

2.miro

We used a shared Miro board as a live micro collaborative space to bring together ideas, sketches, references, and system breakdowns in one place. It allowed all team members to contribute simultaneously, making the process non-linear and highly interactive. By visually clustering concepts and mapping subsystems, we were able to move efficiently from initial ideation to a clearer system architecture while maintaining visibility of each member’s contribution. This reduced communication gaps and supported faster, more informed decision-making.

3.gantt

We used a Gantt chart to structure and monitor the execution of the project. Tasks were broken down into phases, assigned to team members, and mapped across a timeline with defined start and end points. Regular updates helped track progress and identify delays early. This made dependencies between tasks explicit and ensured that work across different domains remained aligned and coordinated.

Combined Workflow

Together, Miro functioned as the thinking and planning layer, while the Gantt chart acted as the execution and tracking layer. This combination allowed us to transition smoothly from concept to implementation, maintain clarity across the team, and ensure consistent progress throughout the project.

BOM

We got our materials from our Fab inventory at Fab Lab Kochi. fab stash : https://inventory.fablabkerala.in/

4.bom

JIG TESTS

For extended development notes and intermediate versions, refer to Ali's Fab Journal

The purpose of the screw hole test is to determine the appropriate tolerances required for proper screw fitting, heat-threaded insert installation, and bearing press-fit. This ensures accurate assembly, secure fastening, and optimal mechanical performance of all components. 5

Clearance / Tolerance Values

ComponentHole Size (mm)Clearance (mm)
M3 Heat Insert5.20+0.10
M4 Heat Insert5.00+0.05 to +0.10
608ZZ Bearing (Outer)22.10+0.10
608ZZ Bearing (Inner)8.20+0.20
625ZZ Bearing (Inner)5.10+0.10
  • Outcome: Initial errors were identified and fixed through reprinting. Even small tolerance changes (~0.1 mm) significantly affected fit.

The jig ensured accurate alignment, proper fitting, and reliable assembly.

Mechanical Design

For extended development notes and intermediate test versions, refer to Abhishek's Fab Journal

The below is the first sketch that we made for first brainstorming section with the team and with all instructors.

6.image

During the first brainstorming, we identified that we need to make the design slick and slim the motor height and width is the issue so we brainstormed below sketch design.

7.image

8.image

We used Hierarchical Assembly Structure to make the 3D model of entire turn-table to that team can collaborate, we can edit the sub-assembly and then use “get latest version” function on the later update the main 3D file.
image9.image10.image

Electronics

For extended development notes and intermediate test versions, refer to Ardradevi's Fab Journal

Main pcb board

The pcb board has through hole pins and these components are connected on the backside.

11.pcb12.pcb13.pcb14.pcb15.pcb PCB editor - KiCAD 16.pcb schematic drawing _KiCAD

Microcontroller: XIAO ESP32-S3 – for its complete 2.4 GHz Wi Fi subsystem

Motor: NEMA 17 17HS19 2004S1 is a high torque bipolar stepper motor (42 × 42 × 48 mm)

Motor driver: DRV8825 features adjustable current limiting, over current and over temperature protection, and six microstep resolutions

Polarised Electrolytic Capacitor: The capacitor used is a 63 V 100 µF polarised electrolytic capacitor – Keltron

JST pins: used to connect the NeoLights and Hall effect sensor

Screw terminal: used to connect the power wires.

Hall effect sensor

Hall Effect Sensor: AH49E Linear Hall Effect Sensor – a digital switch sensor that detects a magnetic field, specifically the south pole of a magnet.

In this machine, it is used to mark the home position of the rotating table. A magnet is attached to the inner ring gear, and the sensor is fixed on the plywood below along its path. When the magnet passes the sensor, it detects the field and sends a signal to the microcontroller, allowing the system to identify and reset to the home position.

17.pcb18.pcb19.pcb

Fabrication & Assembly

For extended development notes and intermediate versions, refer to Ali's Fab Journalassembly

Firmware

Code Logic (Step-by-Step)

For extended development notes and intermediate test versions, refer to Mishael's Fab Journal.

1. System Initialization (What loads first)

We begin by importing required libraries that handle motion, networking, and communication.

#include<AccelStepper.h>
#include<WiFi.h>
#include<PubSubClient.h>
  • AccelStepper → motion control with acceleration
  • WiFi → network connection
  • PubSubClient → MQTT messaging This defines the system capabilities before execution begins.

2. Hardware + Motion Configuration

We define motor resolution and mechanical scaling.

#defineMOTOR_STEPS   200
#defineMICROSTEPS    16
#defineGEAR_RATIO    5
#defineSTEPS_PER_REV (MOTOR_STEPS * MICROSTEPS * GEAR_RATIO)

Interpretation:

  • Microstepping: 16 → smoother motion
  • Gear ratio: 5 → increases precision

Final resolution:

  • Base motor: 200 steps/rev
  • 16000 steps per full table rotation

This becomes the fundamental unit for all positioning.

3. Lookup Table (Core Position Logic)

Instead of calculating positions every time, we predefine them.

constlongLUT[7][6] = {
  {0,0,0,0,0,0},
  {0,0,3200,6400,9600,12800},
  {0,2667,5867,9067,12267,15467},
  ...
};

What this does:

  • Maps → (Compartment, User Position) → Step Position
  • Uses modular arithmetic internally:

Position=(C$offset$+H$offset$) mod 16000

Why this matters:

  • No runtime math
  • Faster execution
  • No accumulated error

4. Hall Sensor Processing (Noise Handling)

Raw sensor data is unstable, so we filter it.

filtered =0.8f*filtered+0.2f*raw;

We also use hysteresis:

if (!hallState&&filtered>ON_THRESHOLD)hallState =true;
if (hallState&&filtered<OFF_THRESHOLD)hallState =false;

Result:

  • Clean digital signal from noisy analog input
  • Prevents flickering detection

5. Finite State Machines (Control Backbone)

We structure behaviour using two FSMs:

enumHomingState {SEARCH,BACKOFF,APPROACH,HOMING_DONE };
enumSystemState {HOMING,POSITIONING,IDLE };

Why FSM:

  • Eliminates blocking code
  • Makes behavior predictable
  • Separates responsibilities

6. Homing Algorithm (First Action After Start)

Homing establishes the zero reference.

stepper.setSpeed(SEARCH_SPEED);
stepper.runSpeed();
  • Rotate until magnet is detected

BACKOFF

stepper.setSpeed(-BACKOFF_SPEED);
  • Move away from magnet

APPROACH

stepper.setSpeed(APPROACH_SPEED);
  • Re-approach slowly for precision

Finalization

stepper.setCurrentPosition(0);
turntableStepPos =0;

Outcome:

  • Defines Home = C1 at H1
  • All future movement is relative to this

7. Command Interpretation Layer

Commands are converted into motion instructions.

if (cmd.length()==4&&cmd[0]=='C'&&cmd[2]=='H') {
intc =cmd[1]-'0';
inth =cmd[3]-'0';
}

Example:

  • C2H3 → Move compartment 2 to position 3 Also supports:
HOME
POS

8. Movement Planning (Shortest Path Logic)

We compute required motion:

longdelta =target-turntableStepPos;

Then optimise:

if (delta>STEPS_PER_REV/2)delta-=STEPS_PER_REV;
if (delta<-STEPS_PER_REV/2)delta+=STEPS_PER_REV;

Meaning:

  • Avoid long rotation
  • Always take minimum angular path

Effect:

  • Faster movement
  • Less mechanical strain

9. Motion Execution Strategy

We execute motion in two phases:

High-speed travel

stepper.setMaxSpeed(MOVE_MAX_SPEED);
stepper.moveTo(current+delta);

Precision (Crawl Mode)

if (abs(distanceToGo)<CRAWL_THRESHOLD) {
stepper.setMaxSpeed(CRAWL_SPEED);
}

Why:

  • Prevent overshoot
  • Improve final alignment

10. MQTT Communication (External Control)

Incoming messages are handled here:

client.setCallback(callback);

Inside callback:

msg.replace(":","");
parseCommand(msg);

Function:

  • Receives wireless commands
  • Cleans formatting
  • Passes to parser

11. Setup Function (Boot Sequence)

This runs once at startup:

setup_wifi();
client.setServer(mqtt_server,mqtt_port);

Also:

  • Enables motor driver
  • Configures microstepping
  • Starts homing

12. Main Loop (Continuous Execution)

Core runtime behaviour:

switch (sysState)

HOMING

  • Runs homing FSM

POSITIONING

stepper.run();
  • Executes movement

IDLE

if (Serial.available())
  • Waits for commands

13. Full Algorithm Flow

Complete system behaviour:

  1. Power ON
  2. Initialize system
  3. Start homing
  4. Set reference position (0)
  5. Wait for command
  6. Parse command (C, H)
  7. Lookup target (LUT)
  8. Compute shortest path
  9. Execute motion
  10. Return to IDLE

14. Final Code

cpp
#include <AccelStepper.h>
#include <WiFi.h>
#include <PubSubClient.h>

// ================== PIN DEFINITIONS ==================
#define STEP_PIN D1
#define DIR_PIN  D0
#define EN_PIN   D3

#define M0_PIN   D2
#define M1_PIN   D4
#define M2_PIN   D5

#define HALL_PIN D8

// ================= WIFI =================
const char* ssid = "Ali";
const char* password = "00000000";

// ================= MQTT =================
const char* mqtt_server = "mqtt.fabcloud.org";
const int mqtt_port = 1883;
const char* mqtt_user = "fabacademy";
const char* mqtt_pass = "fabacademy";

const char* sub_topic = "fabacademy/ashtami/tool";

WiFiClient espClient;
PubSubClient client(espClient);

// ================== HALL THRESHOLDS ==================
#define ON_THRESHOLD  3800
#define OFF_THRESHOLD 3400

// ================== TURNTABLE CONSTANTS ==================
#define MOTOR_STEPS   200
#define MICROSTEPS    16
#define GEAR_RATIO    5
#define STEPS_PER_REV (MOTOR_STEPS * MICROSTEPS * GEAR_RATIO)  // 16000

// ================== SPEED SETTINGS ==================
#define SEARCH_SPEED    800
#define BACKOFF_SPEED   400
#define APPROACH_SPEED  200

#define MOVE_MAX_SPEED  1200
#define MOVE_ACCEL      800
#define CRAWL_SPEED     300
#define CRAWL_THRESHOLD 200

// ================== LOOKUP TABLE ==================
// Formula: LUT[C][H] = (C_offset + H_offset) % 16000
//
// C offsets — 6 compartments equally spaced:
//   Nachos=0, Juice=2667, Lays=5333, Snickers=8000, Popcorn=10667, Cookies=13333
//
// H offsets — 5 positions equally spaced:
//   Abhishek=0, Ardra=3200, Ali=6400, Ashtami=9600, Mishael=12800
//
//                 [0]  [H1=Ab]  [H2=Ar]  [H3=Al]  [H4=As]  [H5=Mi]
const long LUT[7][6] = {
  {0,      0,      0,      0,      0,      0 }, // row 0 unused
  {0,      0,   3200,   6400,   9600,  12800 }, // C1 Nachos
  {0,   2667,   5867,   9067,  12267,  15467 }, // C2 Juice
  {0,   5333,   8533,  11733,  14933,   2133 }, // C3 Lays
  {0,   8000,  11200,  14400,   1600,   4800 }, // C4 Snickers
  {0,  10667,  13867,   1067,   4267,   7467 }, // C5 Popcorn
  {0,  13333,    533,   3733,   6933,  10133 }, // C6 Cookies
};

const char* COMP_LABEL[7] = {"", "Nachos","Juice","Lays","Snickers","Popcorn","Cookies"};
const char* HOME_LABEL[6] = {"", "Abhishek","Ardra","Ali","Ashtami","Mishael"};

// ================== STEPPER ==================
AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);

// ================== HALL FILTER ==================
float filtered  = 0.0f;
bool  hallState = false;

// ================== STATE MACHINES ==================
enum HomingState { SEARCH, BACKOFF, APPROACH, HOMING_DONE };
enum SystemState { HOMING, POSITIONING, IDLE };

HomingState homingState = SEARCH;
SystemState sysState    = HOMING;

// ================== POSITION TRACKING ==================
long turntableStepPos = 0;
int  lastComp = 0;
int  lastHome = 0;

// ================== HALL SENSOR ==================
bool readHallDigital() {
  int raw = analogRead(HALL_PIN);
  filtered = 0.8f * filtered + 0.2f * raw;
  if (!hallState && filtered > ON_THRESHOLD) hallState = true;
  if ( hallState && filtered < OFF_THRESHOLD) hallState = false;
  return hallState;
}

// ================== HOMING ==================
void runHoming() {
  static bool lastHall = false;
  bool currentHall = readHallDigital();

  switch (homingState) {

    case SEARCH:
      stepper.setSpeed(SEARCH_SPEED);
      stepper.runSpeed();
      if (currentHall && !lastHall) {
        Serial.println("[HOMING] Magnet detected → BACKOFF");
        homingState = BACKOFF;
      }
      break;

    case BACKOFF:
      stepper.setSpeed(-BACKOFF_SPEED);
      stepper.runSpeed();
      if (!currentHall && lastHall) {
        Serial.println("[HOMING] Magnet cleared → APPROACH");
        homingState = APPROACH;
      }
      break;

    case APPROACH:
      stepper.setSpeed(APPROACH_SPEED);
      stepper.runSpeed();
      if (currentHall && !lastHall) {
        stepper.setSpeed(0);
        stepper.setCurrentPosition(0);
        turntableStepPos = 0;
        lastComp = 1;
        lastHome = 1;

        stepper.setMaxSpeed(MOVE_MAX_SPEED);
        stepper.setAcceleration(MOVE_ACCEL);

        homingState = HOMING_DONE;
        sysState    = IDLE;

        Serial.println("HOMING COMPLETE → Nachos at Abhishek");
      }
      break;

    case HOMING_DONE:
      break;
  }

  lastHall = currentHall;
}

// ================== MOVE (SHORTEST PATH) ==================
void moveCompartmentToHome(int c, int h) {

  if (c < 1 || c > 6) { Serial.println("[ERR] C must be 1-6"); return; }
  if (h < 1 || h > 5) { Serial.println("[ERR] H must be 1-5"); return; }

  long target = LUT[c][h];
  long delta  = target - turntableStepPos;

  // Shortest path
  if (delta >  STEPS_PER_REV / 2) delta -= STEPS_PER_REV;
  if (delta < -STEPS_PER_REV / 2) delta += STEPS_PER_REV;

  if (delta == 0) {
    Serial.println("[INFO] Already at target");
    return;
  }

  stepper.setMaxSpeed(MOVE_MAX_SPEED);
  stepper.moveTo(stepper.currentPosition() + delta);
  sysState = POSITIONING;

  turntableStepPos = target;
  lastComp = c;
  lastHome = h;

  Serial.print("Moving ");
  Serial.print(COMP_LABEL[c]);
  Serial.print(" → ");
  Serial.println(HOME_LABEL[h]);
  Serial.print("Steps: ");
  Serial.println(delta);
  Serial.print("Direction: ");
  Serial.println(delta > 0 ? "CW" : "CCW");
}

// ================== COMMAND PARSER ==================
void parseCommand(String cmd) {

  cmd.trim();
  cmd.toUpperCase();

  if (cmd.length() == 4 && cmd[0]=='C' && cmd[2]=='H') {
    int c = cmd[1] - '0';
    int h = cmd[3] - '0';
    moveCompartmentToHome(c, h);
    return;
  }

  if (cmd == "HOME") {
    homingState = SEARCH;
    sysState    = HOMING;
    stepper.setMaxSpeed(SEARCH_SPEED);
    Serial.println("[HOMING] Restarting...");
    return;
  }

  if (cmd == "POS") {
    Serial.print("Steps: ");
    Serial.println(turntableStepPos);
    return;
  }

  Serial.println("[ERR] Unknown command");
}

// ================== MQTT CALLBACK ==================
void callback(char* topic, byte* payload, unsigned int length) {

  Serial.println("\n MQTT MESSAGE RECEIVED");

  String msg = "";
  for (int i = 0; i < length; i++) {
    msg += (char)payload[i];
  }

  Serial.print("MQTT RAW: ");
  Serial.println(msg);

  msg.replace(":", "");

  Serial.print("Cleaned: ");
  Serial.println(msg);

  parseCommand(msg);
}

// ================== WIFI ==================
void setup_wifi() {
  Serial.print("Connecting WiFi");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\n WiFi Connected");
}

// ================== MQTT RECONNECT ==================
void reconnect() {
  while (!client.connected()) {
    Serial.println("Connecting MQTT...");
    if (client.connect("ESP32S3", mqtt_user, mqtt_pass)) {
      Serial.println("MQTT Connected");
      client.subscribe(sub_topic);
      Serial.print("Subscribed: ");
      Serial.println(sub_topic);
    } else {
      Serial.print("Failed rc=");
      Serial.println(client.state());
      delay(2000);
    }
  }
}

// ================== SETUP ==================
void setup() {

  Serial.begin(115200);

  pinMode(EN_PIN, OUTPUT);
  digitalWrite(EN_PIN, LOW);

  pinMode(M0_PIN, OUTPUT); digitalWrite(M0_PIN, LOW);
  pinMode(M1_PIN, OUTPUT); digitalWrite(M1_PIN, LOW);
  pinMode(M2_PIN, OUTPUT); digitalWrite(M2_PIN, HIGH);

  filtered = analogRead(HALL_PIN);
  stepper.setMaxSpeed(SEARCH_SPEED);

  Serial.println("Starting homing...");

  setup_wifi();

  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);

  Serial.println("MQTT Ready");
}

// ================== LOOP ==================
void loop() {

  if (!client.connected()) reconnect();
  client.loop();

  switch (sysState) {

    case HOMING:
      runHoming();
      break;

    case POSITIONING:
      if (stepper.distanceToGo() != 0) {

        if (abs(stepper.distanceToGo()) < CRAWL_THRESHOLD) {
          stepper.setMaxSpeed(CRAWL_SPEED);
        } else {
          stepper.setMaxSpeed(MOVE_MAX_SPEED);
        }

        stepper.run();

      } else {
        stepper.setMaxSpeed(MOVE_MAX_SPEED);
        Serial.println("Move complete");
        sysState = IDLE;
      }
      break;

    case IDLE:
      if (Serial.available()) {
        String cmd = Serial.readStringUntil('\n');
        parseCommand(cmd);
      }
      break;
  }
}

User Interface - Alexa Integration

For extended development notes and intermediate test versions, refer to Ashtami's Fab Journal.

Alexa developer console

It's a web-based portal where we can create, test, manage, and publish Alexa skills. 17 An Alexa Skill is like an app for Amazon Alexa.

It allows you to add new features or commands to Alexa so it can do specific tasks beyond its default abilities. Like a new ability you give to Alexa. Like I'm learning how to play sitar, it adds up to my skillset. ask4.jpg

Building an Alexa skill requires two main parts: the Voice Interaction Model and the Programming Logic.

The Voice Interaction Model is created in the Amazon Developer Console and defines how the user speaks to Alexa, including the invocation name, intents, utterances, and slots. It helps Alexa understand what the user is saying.

The Programming Logic is written in code (using Python or Node.js) and runs on AWS. This part decides what action should happen after Alexa understands the command.

ask5.jpg

Creating an Alexa Skill

After logging into the Alexa Developer Console with Amazon account, we created a skill by following these steps:


SKILL1.png

1. Skill Name

First, give a name to your skill.

Next, select the primary language (locale) for your skill (e.g., English - India).

  • Make sure the Alexa app language and skill locale match.

SKILL2.jpg

2. Choose Type of Experience

Since this project is a custom system (rotary table), select: “Other”

3. Choose a Model - selected custom model

SKILL3.jpg

4. Hosting Service

Choose: Alexa-hosted (Python): provides a free cloud environment to run your code and don’t need to set up your own server.

SKILL4.jpg

5. Template

Choose: Start from scratch - build everything from the beginning (intents, logic, responses)

SKILL5.jpg

  • Review all selected options
  • Click Create Skill

Build Skill

ask2.jpg

Invocation Name:

The name you use to open your skill.

How it works: You must say it to Alexa to start your skill.


The name must follow specific rules — it usually needs at least two words, and you cannot use launch words like “open”, “start”, or “ask” as the name.

We named it lazy susan

Intent

An action Alexa should perform based on what the user says.

Utterance


The different ways a user can say the same command(phrases). Alexa uses these to understand and match the user’s speech to the correct intent.

Intent – MoveToolIntent

Utterance
move {snacks} to
give me {snacks} to
show me
go to
give me

ask3.png

Slot

A variable value inside an intent (dynamic input from user).

Slots – MoveToolIntent

Slot NameSlot Type
snackscompartmenttype
personpositiontype

Slot type


Defines what kind of values a slot can take.

There are in build slot types like AMAZON.SearchQuery -A built-in slot type used to capture any free-form text or phrase the user says.

Slot Type: compartmenttype

ValueSynonyms
cookiesbiscuit, biscuits, cookie
popcornpop corn
snickerschocolate
layschips, lace, laze, orange lays, red lays, green lays, blue lays
juicedrink, mango juice
nachosnacho

Slot Type: positiontype

ValueSynonyms
mishaelmishail, mitchel, mishell, mi shael, mishel, michael
ashtamiash tommy, ash tumy, ash tamy, ashthami, ashtomy, ashtamy
alially, alee
ardraar dra, ar da ra, rthra, andra, adra, ardhra
abhishekabhisek, abishek

Code

To communicate between Alexa and the XIAO ESP32-S3, we used MQTT with FabAcademy’s broker "mqtt.fabcloud.org"

Alexa (via AWS Lambda) converts natural language commands into messages and publishes them to a topic, and the ESP32 subscribes to that topic to receive and act on them.

MQTT is a lightweight communication protocol for IoT based on a publish–subscribe system.

It works like a messaging system:

  • Publisher → sends message
  • Broker → middle server (FabAcademy broker)
  • Subscriber → receives message

Any device can be both a publisher and a subscriber.

mqtt.jpg

SKILL11.JPG

Inside requirements.txt

paho-mqtt is a Python library used to send and receive MQTT messages.

jsx
boto3==1.28.78
ask-sdk-core==1.19.0
paho-mqtt //Python library to communicate with a MQTT broker.

Lambda_function.py

jsx
import json
import paho.mqtt.client as mqtt
import random

# ================= MQTT CONFIG =================
MQTT_BROKER = "mqtt.fabcloud.org"
MQTT_PORT = 1883
MQTT_TOPIC = "fabacademy/ashtami/tool"

MQTT_USER = "fabacademy"
MQTT_PASS = "fabacademy"

def send_mqtt(message):
    try:
        client = mqtt.Client()
        client.username_pw_set(MQTT_USER, MQTT_PASS)
        client.connect(MQTT_BROKER, MQTT_PORT, 60)
        client.publish(MQTT_TOPIC, message)
        client.disconnect()
    except Exception as e:
        print("❌ MQTT Error:", str(e))

def lambda_handler(event, context):

    print("📥 EVENT:", json.dumps(event))

    request_type = event["request"]["type"]

    # ================= LAUNCH =================
    if request_type == "LaunchRequest":
        speech = "Hey! What do you want, and who should I give it to?"
        return build_response(speech, False)

    elif request_type == "IntentRequest":

        intent = event["request"]["intent"]
        intent_name = intent["name"]
        slots = intent.get("slots", {})

        if intent_name == "MoveToolIntent":

            # ✅ FINAL SLOT NAMES
            tool = slots.get("snacks", {}).get("value")
            position = slots.get("person", {}).get("value")

            # CLEAN INPUT
            if tool:
                tool = tool.lower().strip()
            if position:
                position = position.lower().strip()

            # ================= SYNONYMS =================
            tool_alias = {
                "nacho": "nachos",
                "juices": "juice", "drink": "juice", "drinks": "juice",
                "chips": "lays", "lace": "lays", "laze": "lays",
                "chocolate": "snickers", "snicker": "snickers",
                "pop corn": "popcorn",
                "cookie": "cookies", "biscuit": "cookies", "biscuits": "cookies",
            }

            name_alias = {
                "adra": "ardra", "andra": "ardra", "andre": "ardra", "r dra": "ardra",
                "ashthami": "ashtami", "ashtamy": "ashtami", "ash": "ashtami", "ash tommy": "ashtami",
                "michael": "mishael", "mishel": "mishael", "mishail": "mishael",
                "mis sail": "mishael", "missile": "mishael",
                "alee": "ali", "ally": "ali",
                "abhisek": "abhishek", "abhi": "abhishek",
            }

            if tool in tool_alias:
                tool = tool_alias[tool]

            if position in name_alias:
                position = name_alias[position]

            # ================= MAPS =================
            snack_map = {
                "nachos": "C1",
                "juice": "C2",
                "lays": "C3",
                "snickers": "C4",
                "popcorn": "C5",
                "cookies": "C6"
            }

            name_map = {
                "ali": "H3",
                "abhishek": "H1",
                "ardra": "H2",
                "ashtami": "H4",
                "mishael": "H5"
            }

            reverse_map = {v: k for k, v in name_map.items()}

            # ================= VALIDATION =================

            if not tool:
                return build_response("What do you want?", False)

            if tool not in snack_map:
                return build_response("Hmm, I don’t have that. Want something else?", False)

            if not position:
                return build_response(f"Got it, {tool}. Who should I give it to?", False)

            if position not in name_map:
                return build_response("I didn’t catch the name. Who should I give it to?", False)

            # ================= ACTION =================

            tool_code = snack_map[tool]
            position_code = name_map[position]

            message = f"{tool_code}:{position_code}"
            send_mqtt(message)

            spoken_name = reverse_map.get(position_code, position)

            responses = [
                f"Alright, bringing {tool} to {spoken_name}.",
                f"Got it, sending {tool} to {spoken_name}.",
                f"Okay, {spoken_name} gets the {tool}."
            ]

            speech = random.choice(responses)

            return build_response(speech, True)

    return build_response("Hmm, I didn’t catch that. Try again?", True)

def build_response(speech, end_session):
    return {
        "version": "1.0",
        "response": {
            "outputSpeech": {
                "type": "PlainText",
                "text": speech
            },
            "shouldEndSession": end_session
        }
    }

Testing

Learnings & Insights

We concluded on a simple idea to rotate the table using a voice command. Although the idea seemed simple, the execution came with its own challenges, as all of us were new to building a machine. We referred to existing Lazy Susan mechanisms for guidance, but the design evolved through both ideation and practical constraints.

The design process required careful attention to even the smallest details, such as heat insert screw holes, bearings, and their distribution. While space for wires was considered, we did not fully account for the thickness and number of wires that would be used. The gears were successfully fabricated without errors, and we used two different materials for them. We had also included holders to manage the wires, but the actual number of wires was underestimated.

The enclosure was designed towards the end. It is currently screwed on from the top, but a press fit solution would have been better. Especially since we had to open it repeatedly for testing and code updates.

Overall, the project was a strong learning experience in translating a simple idea into a functional system. It highlighted the importance of thinking through both design and assembly at the same time, and of anticipating real world constraints early in the process. These insights will help us approach our final projects with better planning, integration, and adaptability.

Future Outlook

In the future, the system can be improved by integrating the ReSpeaker 4-Mic Array for Raspberry Pi, which can detect the direction of arrival (DOA) of the user’s voice. This allows the system to understand which person is speaking based on their position, making the interaction more natural and accurate.

Since the system knows where the command is coming from, it can rotate the compartment toward the detected direction instead of requiring the user to specify a person’s name.

reSpeaker 4-Mic Array for Raspberry Pi | Seeed Studio Wiki

reSpeaker 4-Mic Array for Raspberry Pi | Seeed Studio Wiki

Specifications

  • Microphones: 4 microphones placed around the board for spatial audio capture
  • Audio Chip: AC108 quad-channel ADC for high-quality audio input
  • Voice Features: Supports direction detection (DOA), noise reduction, and voice focusing
  • LEDs: 12 APA102 RGB LEDs for visual feedback (like Google Home / Alexa style indicators)
  • Audio Output: No built-in speaker (uses Raspberry Pi headphone jack or HDMI)
  • Software Support: Demo code, libraries, and hotword detection support

features.jpg

Improvement in Voice Interaction

Instead of relying on cloud-based assistants like Alexa, we can use offline tools such as Picovoice and Porcupine to create custom wake words and commands.

So instead of saying:

“Alexa, ask Lazy Susan to move nachos to Abhishek”

we can directly say:

“Move nachos ”

Custom wake words like “lazy susan”, “turntable”, “working susan”….

Benefits

  • Faster response (low latency)
  • Works offline (no internet required)
  • Better privacy (no cloud processing)
  • More natural and user-friendly interaction

Contributors

Abhishek Sha

Abhishek Sha

Machine Design

Ali Abdul Gafoor

Ali Abdul Gafoor

Production and Documentation

Ardradevi

Ardradevi

Electronics

Ashtami

Ashtami

User Interface

Mishael Sharaf

Mishael Sharaf

Firmware