I am making an interface to change the colours of RGB LED of the T_CUBINO board I had designed during week eight of electronics design.
I am using Streamlit for making the interface. Streamlit turns data scripts into shareable web apps in minutes. All in pure Python. No front‑end experience required. For more details about streamlit, refer this documentation.
So in the interface, I will be able to choose the colour and the LED turns on with that respective colour.
Here's the python code for streamlit interface.
import streamlit as st import serial import time # Set up the serial connection (make sure the port matches your Arduino's port) ser = serial.Serial('COM12', 9600) # Adjust to your serial port st.title("RGB LED Controller") # List of colors colors = { 'Red': 'r', 'Green': 'g', 'Blue': 'b', 'Yellow': 'y', 'Cyan': 'c', 'Magenta': 'm', 'White': 'w' } # Create radio button for color selection color = st.radio('Select a color', list(colors.keys())) # Send command to Arduino if st.button('Set Color'): ser.write(colors[color].encode()) time.sleep(1) st.write(f'The LED color is set to {color}')
Here's the arduino code for the board.
const int redPin = 5; // Red pin of the RGB LED const int greenPin = 8; // Green pin of the RGB LED const int bluePin = 9; // Blue pin of the RGB LED void setup() { pinMode(redPin, OUTPUT); // Initialize Red pin of the RGB LED as output pinMode(greenPin, OUTPUT); // Initialize Green pin of the RGB LED as output pinMode(bluePin, OUTPUT); // Initialize Blue pin of the RGB LED as output SerialUSB.begin(9600); // Initialize SerialUSB communication } void loop() { digitalWrite(redPin, HIGH); digitalWrite(greenPin, HIGH); digitalWrite(bluePin, HIGH); if (SerialUSB.available()) { char c = SerialUSB.read(); switch (c) { case 'r': // Turn on red digitalWrite(redPin, LOW); digitalWrite(greenPin, HIGH); digitalWrite(bluePin, HIGH); delay(100); break; case 'g': // Turn on green digitalWrite(redPin, HIGH); digitalWrite(greenPin, LOW); digitalWrite(bluePin, HIGH); delay(100); break; case 'b': // Turn on blue digitalWrite(redPin, HIGH); digitalWrite(greenPin, HIGH); digitalWrite(bluePin, LOW); delay(100); break; case 'y': // Turn on yellow (red + green) digitalWrite(redPin, LOW); digitalWrite(greenPin, LOW); digitalWrite(bluePin, HIGH); delay(100); break; case 'c': // Turn on cyan (green + blue) digitalWrite(redPin, HIGH); digitalWrite(greenPin, LOW); digitalWrite(bluePin, LOW); delay(100); break; case 'm': // Turn on magenta (red + blue) digitalWrite(redPin, LOW); digitalWrite(greenPin, HIGH); digitalWrite(bluePin, LOW); delay(100); break; case 'w': // Turn on white (red + green + blue) digitalWrite(redPin, LOW); digitalWrite(greenPin, LOW); digitalWrite(bluePin, LOW); delay(500); break; } } }
During machine week, 5 of my group members were assigned different tasks for the making of Cut-Urumbu. Here's the documentation to how I designed the interface 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!
Click here for Machine week documentation:
Click here: