Click here:
• Refer Nihal's documentation for details on Project Management.
• Refer Midhun's documentation for details on Turn Table Design.
• Refer Kalyani's documentation for details on Concept sketch and Poster.
• Refer Laxmi's documentation for details on Design for cutting mechanism.
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.
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.
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.
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
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.
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()
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.
// 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");
}
}
}
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()
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)
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)
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 :(
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]
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.
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()
Enjoy equal slices!