Scroll to Top
OD

Mechanical Design, Machine Design


Group Assignment

  • Design a machine that includes mechanism+actuation+automation+application

  • Build the mechanical parts and operate it manually

  • Actuate and automate your machine

  • Document the group project and your individual contribution

Group Assignment


Coming soon

Click here:

Cut-Urumbu

Coming soon

Team

Coming soon

Individual Contribution

Coming soon

For this week, 5 of my group members were assigned different tasks for the making of Cut-Urumbu. Having a background in electronics and programming was assigned with the same for the machine. Therefore my documentation will be covering the electronics, programming, firmware, kinematics and UI of Cut-Urumbu.


Urumbu

In the development of Cut-Urumbu, our cake cutting machine, we took inspiration from Neil Gershenfeld's Urumbu project . Urumbu simplifies the process of building machines by employing advanced technology to manage complex hardware setups efficiently. This innovative approach, pioneered in the Urumbu project, greatly simplifies the control of motors and other components. Urumbu acts as a central system that coordinates communication between different parts of a machine, allowing them to work together seamlessly. It utilizes a sophisticated USB hub to facilitate this communication, making it easier to control motors and other devices. By adopting principles from the Urumbu project, Cut-Urumbu is capable of cutting cakes with motors connected to their seperate boards and communicate via different ports.


Cut-Urumbu Board

Our instructor Saheen had already designed a board based on Urumbu concept, refer this documentation. Once the board was tested, my group member Nihal milled and soldered the board. Both of the boards needed for our stepper motors were ready and it was my turn to program it.


Test Programming

When the Cut-Urumbu board was getting tested, I thought of using the quentorres boards and making them communicate with each other. So my idea was to get familiarised with python and arduino. I have previous experience using the same for using joystick to play video games on pc and more details are available in my documentation .


So to start off I wanted to control the led of one quentorres board using the button of another quentorres board, using arduino and python.


Sender Code



    int buttonPin = 27; // Define the pin connected to the button

    void setup() {
        Serial.begin(9600); // Initialize serial communication
        pinMode(buttonPin, INPUT_PULLUP); // Set the button pin as an input with internal pull-up resistor
    }
    
    void loop() {
        if (digitalRead(buttonPin) == HIGH) {
        Serial.println("Button pressed");
        delay(1000); // debounce delay
        }
    }
                                                        
 

Receiver Code



    int ledPin = 26; // Define the pin connected to the LED

    void setup() {
    Serial.begin(9600); // Initialize serial communication
    pinMode(ledPin, OUTPUT); // Set the LED pin as an output
    }

    void loop() {
    if (Serial.available() > 0) {
        char receivedChar = Serial.read(); // Read the incoming data from Python
        if (receivedChar == '1') {
        digitalWrite(ledPin, HIGH); // Turn on the LED if '1' is received
        Serial.println("LED turned on");
        } else if (receivedChar == '0') {
        digitalWrite(ledPin, LOW); // Turn off the LED if '0' is received
        Serial.println("LED turned off");
        }
    }
    }



Python Code



    import serial
    import time

    arduino_led = serial.Serial(port='COM7', timeout=0)
    arduino_button = serial.Serial(port='COM5', timeout=0)
    time.sleep(2)

    while True:
        if arduino_button.in_waiting:
            button_state = arduino_button.readline().strip()
            if button_state:
                print("Button pressed")
                arduino_led.write(b'1')  # Turn on the LED
                time.sleep(1)  # Delay to debounce the button
        else:
            arduino_led.write(b'0')  # Turn off the LED when the button is released




Programming

For UI, I thought of using streamlit. I have previous experience using streamlit for making a few AI apps for hackathons. Please refer the github repos given in About Me to see those apps. For more details about streamlit, refer this documentation.

Port Control

To start off with UI, I thought of making a port control page to open and close the ports.



    import serial
    import streamlit as st

    # Function to open serial port
    def open_port(port):
        try:
            ser = serial.Serial(port, timeout=1)
            st.success(f"Port {port} opened successfully!")
            return ser
        except serial.SerialException:
            st.error(f"Failed to open port {port}. Make sure the port is available.")

    # Function to close serial port
    def close_port(ser):
        if ser:
            ser.close()
            st.success(f"Port {ser.port} closed successfully!")

    # Streamlit app
    def main():
        st.title("Port Control")

        # Dropdown to select action for port 5
        action_port_5 = st.selectbox("Port 5:", ["Select action", "Open", "Close"])
        ser5 = None
        if action_port_5 == "Open":
            ser5 = open_port('COM5')
        elif action_port_5 == "Close":
            close_port(ser5)

        # Dropdown to select action for port 7
        action_port_7 = st.selectbox("Port 7:", ["Select action", "Open", "Close"])
        ser7 = None
        if action_port_7 == "Open":
            ser7 = open_port('COM7')
        elif action_port_7 == "Close":
            close_port(ser7)

        # Button to open both ports together
        if st.button("Open Both Ports"):
            ser5 = open_port('COM5')
            ser7 = open_port('COM7')

    if __name__ == "__main__":
        main()

Coming soon
Coming soon
Coming soon

Motor Control

One Urumbu board was ready, so I tried to program the motor using arduino and python and streamlit as the UI. Referring to Urumbu documentation, I used the same arduino code for the motor with a few changes.


Arduino Code

    // serialstep.ino
    //
    // serial step-and-direction
    //
    // Neil Gershenfeld 4/11/21
    // Quentin Bolsee 12/7/21 : add button
    //
    // This work may be reproduced, modified, distributed,
    // performed, and displayed for any purpose, but must
    // acknowledge this project. Copyright is retained and
    // must be preserved. The work is provided as is; no
    // warranty is provided, and users accept all liability.
    //

    #define LEDA 30
    #define LEDC 14
    #define EN 5
    #define DIR 2
    #define STEP 4


    #define BUTTON 31

    void setup() {
    SerialUSB.begin(0);
    digitalWrite(LEDA,HIGH);
    pinMode(LEDA,OUTPUT);
    digitalWrite(LEDC,LOW);
    pinMode(LEDC,OUTPUT);
    digitalWrite(EN,LOW);
    pinMode(EN,OUTPUT);
    digitalWrite(STEP,LOW);
    pinMode(STEP,OUTPUT);
    digitalWrite(DIR,LOW);
    pinMode(DIR,OUTPUT);
    pinMode(BUTTON, INPUT_PULLUP);

    void loop() {
    if (SerialUSB.available()) {
        char c = SerialUSB.read();
        if (c == 'f') {
            digitalWrite(DIR,HIGH);
            digitalWrite(STEP,HIGH);
            delayMicroseconds(4);
            digitalWrite(STEP,LOW);
            }
        else if (c == 'r') {
            digitalWrite(DIR,LOW);
            digitalWrite(STEP,HIGH);
            delayMicroseconds(4);
            digitalWrite(STEP,LOW);
            }
        else if (c == '?') {
            // reply with button value
            int btn = digitalRead(BUTTON);
            SerialUSB.write(btn ? '1' : '0');
            }
        else if (c == '@') {
            SerialUSB.write("0000");
            }
        }
    }


Python Code

    import streamlit as st
    import serial

    # Function to open serial port
    def open_port(port):
        try:
            ser = serial.Serial(port, timeout=1)
            st.success(f"Port {port} opened successfully!")
            return ser
        except serial.SerialException:
            st.error(f"Failed to open port {port}. Make sure the port is available.")

    # Streamlit app
    def main():
        st.title("Motor Control")

        # Open port selection
        port = st.sidebar.selectbox("Select Port", ["COM6", "COM7"])

        # Open selected port
        ser = open_port(port)

        # Button to run the motor
        if st.button("Run Motor"):
            # Send control commands to Arduino
            ser.write(b'f')  # Signal to Arduino to run the motor

    if __name__ == "__main__":
        main()



Steps Calculation

Since the cake has to be cut into equal slices, the motor which should be rotating the turn table should turn in such a way that the blade will be cutting the cake into equal slices. Here's the formula adopted intially:



  steps_per_slice = int((1 * 6400) / num_slices) 


Slice distribution

For an intercative experience, I tried to include a pie chart which shows the number of slices depending upon the input in streamlit.



    def generate_pie_chart(num_slices):
        labels = [f'Slice {i+1}' for i in range(num_slices)]
        sizes = np.ones(num_slices)
        explode = [0.1] * num_slices  # Explode each slice slightly for better visualization
        fig, ax = plt.subplots()
        ax.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%', shadow=True, startangle=140)
        ax.axis('equal')  # Equal aspect ratio ensures that pie is drawn as a circle
        ax.set_title('Distribution of Slices')
        st.pyplot(fig)



Web Socket

I wanted to try web socket to control the machine from my phone. With the help of ChatGPT I've got just the code for it.

However I have to run two codes seperately for it. This should be run by python websocket_server.py in one terminal and the other streamlit run phone.py in another terminal.



    import asyncio
    import websockets

    # Function to send message to Streamlit app
    async def send_message(message):
        async with websockets.connect("ws://localhost:8501/ws") as websocket:
            await websocket.send(message)

    # WebSocket server coroutine
    async def handle_message(websocket, path):
        async for message in websocket:
            if message.startswith("speed"):
                # Extract speed value from the message
                speed = float(message.split(":")[1])
                print(f"Received speed: {speed}")
                # Send speed value to the Streamlit app
                await send_message(f"speed:{speed}")
            elif message.startswith("slices"):
                # Extract number of slices value from the message
                num_slices = int(message.split(":")[1])
                print(f"Received number of slices: {num_slices}")
                # Send number of slices to the Streamlit app
                await send_message(f"slices:{num_slices}")
            elif message == "run_motor":
                # Send "run_motor" command to the Streamlit app
                print("Received 'run_motor' command")
                await send_message("run_motor")
            else:
                print(f"Unsupported message: {message}")

    # Start WebSocket server
    start_server = websockets.serve(handle_message, "localhost", 8765)

    # Run the event loop
    asyncio.get_event_loop().run_until_complete(start_server)
    asyncio.get_event_loop().run_forever()


Unfortunately, I've lost the version of phone.py code used as shown in the video. Apologies :(



Port Detection

In the previous code of port control, I had to hard code the port number but I found a function in which I could automatically get all the available ports.



    # Function to get available serial ports
    def get_available_ports():
        ports = serial.tools.list_ports.comports()
        return [port.device for port in ports]


Trial Implementation

When the second board was also ready, I tried to implement all of these functions into the code. I had help from ChatGPT as well. So I'll be inputting the number of slices and speed and the motor for the blade will act forward and reverse mimicking down and up motion and motor for the table will be rotating as per slice count based on the formula given before.



    import streamlit as st
    import serial.tools.list_ports
    import time
    import matplotlib.pyplot as plt
    import numpy as np
    import websocket
    from PIL import Image

    image=Image.open('cuturumbu.png')

    st.set_page_config(page_title='Cut-Urumbu', page_icon='cuturumbu.png')

    st.sidebar.image(image, width=275)

    # Function to open serial port
    def open_ports(ports):
        ser_ports = []
        for port in ports:
            try:
                ser = serial.Serial(port, timeout=1)
                st.sidebar.success(f"Port {port} opened successfully!")
                ser_ports.append(ser)
            except serial.SerialException:
                st.sidebar.error(f"Failed to open port {port}. Make sure the port is available.")
        return ser_ports

    # Function to generate the pie chart
    def generate_pie_chart(num_slices):
        labels = [f'Slice {i+1}' for i in range(num_slices)]
        sizes = np.ones(num_slices)
        explode = [0.05] * num_slices  # Explode each slice slightly for better visualization
        fig, ax = plt.subplots()
        ax.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%', startangle=140)
        ax.axis('equal')  # Equal aspect ratio ensures that pie is drawn as a circle
        ax.set_title('See your Cake Slices')
        st.pyplot(fig)

    # Function to send message to WebSocket server
    def send_message(message):
        ws = websocket.create_connection("ws://192.168.33.86:8765")
        ws.send(message)
        ws.close()

    # Streamlit app
    def main():
        st.title("Cut-Urumbu🐜🍰")

        # Get available serial ports
        available_ports = serial.tools.list_ports.comports()
        port_names = [port.device for port in available_ports]

        # Open port selection
        selected_ports = st.sidebar.multiselect("Select Ports", port_names)

        # Open selected ports
        ser_ports = open_ports(selected_ports)

        # Input for number of slices
        num_slices = st.number_input("Enter number of slices", min_value=2, value=2)

        # Input for speed
        speed = st.number_input("Enter speed", min_value=0.01, value=1.00, step=0.01)

        # Display the pie chart
        generate_pie_chart(num_slices)

        # Button to run the motors
        if st.sidebar.button("Cut Cake"):
            
            steps_per_slice = int((1 * 6400) / num_slices)  # Calculate steps per slice
            try:
                # Alternate between motor 1 and motor 2%
                for i in range(num_slices):
                    # Motor 1
                    for ser in ser_ports[:1]:  # Iterate over the first selected port
                        # Send control commands to Arduino for motor 1 ("f" motion)
                        for _ in range(steps_per_slice):
                            ser.write(b'f')  # Signal to Arduino to move motor 1 forward
                            time.sleep(speed / 1000000)
                        time.sleep(2)  # Delay before changing motor
                        # Send control commands to Arduino for motor 1 ("r" motion)
                        for _ in range(steps_per_slice):
                            ser.write(b'r')  # Signal to Arduino to move motor 1 backward
                            time.sleep(speed / 1000000)
                        time.sleep(2)  # Delay before changing motor

                    # Motor 2
                    for ser in ser_ports[1:]:  # Iterate over the second selected port onwards
                        # Send control commands to Arduino for motor 2 ("f" motion)
                        for _ in range(steps_per_slice):
                            ser.write(b'f')  # Signal to Arduino to move motor 2 forward
                            time.sleep(speed / 1000000)
                        time.sleep(2)  # Delay before changing motor
                st.balloons()
            except serial.SerialException:
                st.error("Error occurred while sending commands to Arduino.")

        # Close serial ports
        for ser in ser_ports:
            if ser.isOpen():
                ser.close()

    # Footer
        # Footer
        st.sidebar.markdown("---")
        st.sidebar.text("Made at Super FabLab Kerala")
        st.markdown("---")
        st.text("Made by Laxmi, Kalyani, Thej, Nihal & Midhun")

    if __name__ == "__main__":
        main()


To control this from phone, run websocket_server.py before running this code.



Coming soon
Coming soon
Coming soon

Testing on Machine

While testing on the machine there were a few things to be considered.



    steps_per_mm_blade = 800       
    steps_per_deg_table =  (7.067 * 6400)/360
    cut_bottom_limit=254 # full length in mm
    speed_delay=1 / (1000000*speed) #speed will be inputed


To start the machine, the blade has to be homed first i.e. at the highest position so for that we have a limiting switch connected to pin 31 and in the arduino code used, sending '?' will be send the state of the button as '1' or '0'. Here '0' denotes that the button is pressed. Implementation of this in a function is this code.



    def home():
    try:
            ser = ser_ports[:1][0]
            global speed_delay
            response = ''
            while response != '0':
                ser.write(b'?')  # Send command to check if motor is in home position
                response = ser.read().decode().strip()
                if response == '0':  # Motor is in home position
                    st.info("Motor is in home position.")
                    break
                else:  # Motor is not in home position, move towards home
                    ser.write(b'r')  # Move motor towards home
                    time.sleep(speed_delay)

    except serial.SerialException:
        st.error("Error occurred while sending commands to Arduino.")


Since cake height will also be inputed, the blade to cut will come down to the cake height position to start with the cutting process.



    def setCutHeight():
    try:
        steps_to_set_height=int((cut_bottom_limit-cake_height)*steps_per_mm_blade)
        global speed_delay
        ser = ser_ports[:1][0]
        for _ in range(steps_to_set_height):
                ser.write(b'f')  # Signal to Arduino to move motor 1 backward
                time.sleep(speed_delay)
          
    except serial.SerialException:
            st.error("Error occurred while sending commands to Arduino.")


For cutting 'f' will move the blade down and 'r' will move the blade upto the steps calculated based on cake height



    def cut():
    try:
        steps_per_slice_mm = int(steps_per_mm_blade*cake_height)
        global speed_delay
        # Motor 1
        ser = ser_ports[:1][0]
        print(ser)
            # Move Motor 1 backward (r) in steps_per_slice3
        for _ in range(steps_per_slice_mm):
                ser.write(b'f')  # Signal to Arduino to move motor 1 backward
                time.sleep(speed_delay)
        time.sleep(0.1)  # Delay before changing motor direction

            # Move Motor 1 backward (r) in steps_per_slice3
        for _ in range(steps_per_slice_mm):
                ser.write(b'r')  # Signal to Arduino to move motor 1 backward
                time.sleep(speed_delay)
               
    except serial.SerialException:
        st.error("Error occurred while sending commands to Arduino.")


For the turn table sending 'f' will rotate the table depending upon the slices.


 
    def rotate():
        ser = ser_ports[1:][0]
        steps_per_slice = int(steps_per_deg_table*(360/num_slices))
        global speed_delay
        for _ in range(steps_per_slice):
            ser.write(b'f')  # Signal to Arduino to move motor 2 forward
            time.sleep(speed_delay)


Here's the full code. Please note this doesn't support web socket.



    import streamlit as st
    import serial.tools.list_ports
    import time
    import matplotlib.pyplot as plt
    import numpy as np
    from PIL import Image
    
    steps_per_mm_blade = 800       
    steps_per_deg_table =  (7.067 * 6400)/360
    cake_height=170 
    cut_bottom_limit=254 
    num_slices=2
    
    speed= 1
    speed_delay=1 / (1000000*speed)
    
    ser_ports = []
    
    # Importing the image
    image = Image.open('cuturumbu.png')
    
    # Setting up the Streamlit page configuration
    st.set_page_config(page_title='Cut-Urumbu', page_icon='cuturumbu.png')
    
    # Displaying the image in the sidebar
    st.sidebar.image(image, width=275)
    
    # Function to open serial ports
    def open_ports(ports):
        for port in ports:
            try:
                ser = serial.Serial(port, timeout=1)
                st.sidebar.success(f"Port {port} opened successfully!")
                ser_ports.append(ser)
            except serial.SerialException:
                st.sidebar.error(f"Failed to open port {port}. Make sure the port is available.")
    
    # Function to generate the pie chart
    def generate_pie_chart(num_slices):
        labels = [f'Slice {i+1}' for i in range(num_slices)]
        sizes = np.ones(num_slices)
        explode = [0.05] * num_slices  # Explode each slice slightly for better visualization
        fig, ax = plt.subplots()
        ax.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%', startangle=140)
        ax.axis('equal')  # Equal aspect ratio ensures that pie is drawn as a circle
        ax.set_title('See your Cake Slices')
        st.pyplot(fig)
    
    # Function to return the motor to home position
    def home():
            try:
                    ser = ser_ports[:1][0]
                    global speed_delay
                    response = ''
                    while response != '0':
                        ser.write(b'?')  # Send command to check if motor is in home position
                        response = ser.read().decode().strip()
                        if response == '0':  # Motor is in home position
                            st.info("Motor is in home position.")
                            break
                        else:  # Motor is not in home position, move towards home
                            ser.write(b'r')  # Move motor towards home
                            time.sleep(speed_delay)
    
            except serial.SerialException:
                st.error("Error occurred while sending commands to Arduino.")
    
    def setCutHeight():
        try:
            steps_to_set_height=int((cut_bottom_limit-cake_height)*steps_per_mm_blade)
            global speed_delay
            ser = ser_ports[:1][0]
            for _ in range(steps_to_set_height):
                    ser.write(b'f')  # Signal to Arduino to move motor 1 backward
                    time.sleep(speed_delay)
              
        except serial.SerialException:
                st.error("Error occurred while sending commands to Arduino.")
    
    
    def cut():
        try:
            steps_per_slice_mm = int(steps_per_mm_blade*cake_height)
            global speed_delay
            # Motor 1
            ser = ser_ports[:1][0]
            print(ser)
                # Move Motor 1 backward (r) in steps_per_slice3
            for _ in range(steps_per_slice_mm):
                    ser.write(b'f')  # Signal to Arduino to move motor 1 backward
                    time.sleep(speed_delay)
            time.sleep(0.1)  # Delay before changing motor direction
    
                # Move Motor 1 backward (r) in steps_per_slice3
            for _ in range(steps_per_slice_mm):
                    ser.write(b'r')  # Signal to Arduino to move motor 1 backward
                    time.sleep(speed_delay)
                   
        except serial.SerialException:
            st.error("Error occurred while sending commands to Arduino.")
    
    
    def rotate():
        ser = ser_ports[1:][0]
        steps_per_slice = int(steps_per_deg_table*(360/num_slices))
        global speed_delay
        for _ in range(steps_per_slice):
            ser.write(b'f')  # Signal to Arduino to move motor 2 forward
            time.sleep(speed_delay)
   
    # Streamlit app
    def main():
    
        st.title("Cut-Urumbu🐜🍰")
    
        # Get available serial ports
        available_ports = serial.tools.list_ports.comports()
        port_names = [port.device for port in available_ports]
    
        # Open port selection
        selected_ports = st.sidebar.multiselect("Select Ports", port_names)
    
        # Open selected ports
        open_ports(selected_ports)
        print(ser_ports)
        # Input for number of slices
        global num_slices
        num_slices = st.number_input("Enter number of slices", min_value=2, value=2)
    
        # Input for speed
        global speed
        speed = st.number_input("Enter speed", min_value=0.01, value=1.00, step=0.01)
        global cake_height
        cake_height = st.number_input("Enter height of the cake (in mm)",min_value=0, value=0, step=1)
        global speed_delay
        speed_delay=1 / (100000000000*speed)
    
        # Display the pie chart
        generate_pie_chart(num_slices)
  
        if st.sidebar.button("Home"):# Return the motor to home position
            home()
  
        # Button to run the motors
        if st.sidebar.button("Cut Cake"):
            if cake_height == 0:
                 st.error("Warning: Cake height is 0. Set a non-zero height value.")
            else:
                home()
                setCutHeight()
    
                for _ in range(num_slices):
                    cut()
                    rotate()
                st.balloons()
                home()
    
        # Close serial ports
        for ser in ser_ports:
            if ser.isOpen():
                ser.close()
    
        # Footer
        st.sidebar.markdown("---")
        st.sidebar.text("Made at Super FabLab Kerala")
        st.markdown("---")
        st.text("Made by Laxmi, Kalyani, Thej, Nihal & Midhun")
    
    if __name__ == "__main__":
        main()
    
    
Coming soon

Enjoy equal slices!




Files



Program Files

Full codes