10. Machine Week - Richard Shan, Evan Park, and David Vaughn¶
This week we were tasked to create a machine that includes a mechanism, actuation, automation, and application.
Work Distribution¶
People | Description |
Richard | All programming and software development |
Richard | Raspberry Pi work |
Richard | Microphone, keyboard, touchscreen setup and config |
Evan | CNC |
Richard, David | Motors setup |
Richard, David | Motors programming, debugging |
Evan, David | CAD |
Evan, David | Assembly |
Evan, David | Test laser cut |
All | Ideation |
All | Documentation |
Individual Documentation Pages¶
Research¶
While researching for what type of machine we wanted to do, Mr. Dubick told us about a Automated ChatGPT Ouija Board by Jack Hollingsworth. That machine uses magnets and ChatGPT to create a self-moving ouija board.
We decided that we wanted to create this machine and add some modifications. The first thing we wanted to add was a Speech-to-Text so that you didn’t have to type your question into ChatGPT to get a response. We planned to utilize Whisper API to accomplish our Speech-to-text feature.
Designing¶
Fusion 360¶
To start out creating the box, we designed a file in Fusion360 that will assemble the box that will hold our gantry and the rest of our electronic components. The top of the box will also have the Ouija Board engraved on top. We designed it in Fusion360 so that we could use parameters to change the size of the box if needed. We added a small panel for a button that we would use to activate the microphone that would pick up our speech and turn it into text for ChatGPT. We also added a hole for a USB for debugging and a hole to put your finger in and push the top off.
This is the file we created and a photo of the parameters
Laser Cut Tests¶
We decided that we wanted to laser cut our design on cardboard first to make sure everything fit as we wanted.
Aspire¶
We then took our DXF file into Aspire. We took a tape measurer and found that the length and width were 96’’ and 48’’ respectivly and with calipers we measured that the width was .46’‘. This was our Job Setup in Aspire
Then we added dogbones by using the filet tool and specified our tool radius and went along each finger joint and added dog bones so that they would fit when we milled them. We then used a profile toolpath. We started the profile toolpath by adding all the tabs we needed for our mill.
ShopBot¶
We were milling a wood that had already been milled before so we had to organize our file so it went around the already milled out parts on the wood.
MILLING VIDEO
Post Mill¶
There were a few issues that we found out after we milled. The first issue was that we had milled the magnet holder on the box the wrong way.
We were working around an already milled plywood so we had to rearrange our file so that it would fit on the wood. I used the same process as before to mill out the file but, while the file was milling, I realized that the tabs weren’t present. This was most likely due to my cut depth being too deep for the wood. This made the wood shift a little when the pocket toolpaths started. I stopped the ShopBot and put brads in each of the cut out pieces so that they wouldn’t move for the pocket toolpaths. Unfortunately, I stopped the cut a little late so one of the sides were a little bit scuffed but it didn’t cause any issues so we kept the piece.
Assembly¶
Assembly of Box¶
Now it was time to assemble our pieces to create our box.
The first thing I did was screw the magnet holders onto the sides of the boxes. I did this by using a measuring tape to figure out the center of the magnet holder on the other side, marking it with a pen, and then screwing a screw into that mark. I did two screws for each magnet holder.
Gantry¶
For this project, we wanted a 2D axis gantry system which could be programmed using G-Code.
Mechanics and Wiring¶
First, we set up the CNC Shield and Arduino to connect to the stepper motors using this video and a few others as reference. To sum up the process, we attached the CNC Shield to an Arduino Uno, attached stepper drivers to the CNC Shield, used ribbon cables to connect stepper motors to the CNC Shield, and connected power to the Arduino through some stripped wires. Eventually, we exchanged these stripped wires for jumper cables as these are more reliable.
3D Printing Parts for the Installation¶
Many of the 3D printed parts we used in this build we got from Sand Table video. Below is a table of which designs we got and how many we used.
Part | Image | Quantity |
---|---|---|
X-Carriage Mounts | 2 | |
Idle Mounts | 1 | |
Belt Grip | 1 | |
Magnet Mount | 1 | |
Motor Bases | 2 | |
Wire Loops | 2 |
*Note that one of the motor bases must be mirrored horizantally in Prusa or other slicing software before print.
Although we got downloaded many of the 3D files from the Jack Hollingsworth Instructables site (which got these files from the Sand Table video by DIY Machines), we wanted to make at least some of these ourselves. To hold the CNC Shield and the Arduino Uno, I created a simple box with holes in the sides for various cables and wires. I also created the part which would hold the magnet. This part originally fit directly onto the top of the mount and went straight up, before we realized that it would not fit there as the screws used to hold the belt grip would intrude on its space. After this, I recreated this with an offset, however I forgot to turn the slot which the part sticking out went into and we neglected to think about how it went right through the path of the belt, but these things turned out fine. Additionally, either my measurements were off or I was too cautious, but the holder was about an inch short.
We eventually just printed a small cylindar to fit inside the magnet holder which we hot glued the magnet on the top of. One of the box’s holes was slightly off, but this could be easily fixed by using a soldering iron to melt away some filament, thereby extending the hole.
Assembling the Gantry¶
The gantry for this machine is based on one for a sand table by DIY Machines. Their video is really good for following the assembly of the gantry machine but we will still outline what we did.
We started by attaching the X carrages onto the X rails by using M38 screws for the carrage. We then attached the smooth idlers using M520 screws.
We then attacked the motors to our motor cases using M3*12 screws.
I then attached the X rails to the idler supports and motor mounts using M312 bolts. I was suggested to start using heat inserts for my screws and I used a certain tool to push the heat inserts vertically into my 3D printed motor mount. We would later attach a teethed idler onto it using M520 screws.
It ended up making the hole clogged with PLA and making a bolt impossible to go through. I had to take a hot air blower and heat the hole until I could push the heat insert out and then take an exactoknife and take material out of the hole until I could push the bolt through and put the nut at the end of it to hold it in. I decided to give up on the heat inserts after this.
We then put everything in the box at this point.
I then attached the Y axis by using M3*12 bolts.
We then attaches the magnet carriawge mount using M3*8 bolts onto the Y axis.
We then ran our belt around the mount going around each motor connecting everything using one belt
We then wood screwed our Arduino and motor mounts into the box. To make sure the motor mounts were squared, we used a triangular ruler to measure each side and make sure everything was at a 90 degree angle. We then hot glued the Rasberry Pi to the side of the box near the outlet hole. We also attached the magnet holder to the magnet holder carriage mount using M3*8 screws.
We then installed some wire loops with wood screws for wiring organization.
Our box was ready to be programmed and run.
Installing the Gantry¶
To install the gantry system into the box (which we only did after we got all the programming working with the motors properly), we screwed some what kind of screws into the holes in the 3D printed parts at the ends of the x and y axes. We used a triangular ruler to make sure these were fairly straight and symmetrical. Additionally, we screwed down the wire holders and the box for the CNC Shield and Arduino.
Programming¶
Planning¶
The final program must be able to process a user’s question through voice and display an answer on the actual ouija board by moving the magnet to corresponding letter locations.
The whole program for the board can be broken down into a few key tasks: receiving audio input from the microphone, converting audio to text using Whisper API, querying ChatGPT (OpenAI API) for a response to that same text, sending the generated text from the chip to the Arduino, and moving the magnet to a specified set of coordinates that correspond to each letter within the text string.
With these goals established, it directly follows that our chosen chip must be able to:
- Connect to the internet to query APIs
- Receive audio input from a USB microphone and/or keyboard
- Use serial communication with an Arduino
As a personal goal, I wanted the entire machine to be self-contained and run without the need of any laptop. This self-imposed goal definitely made the entire process a lot more difficult, necessitating the following major changes (and many more minor ones!).
With laptop | Without laptop | |
Inputting coordinates into Arduino IDE's Serial Monitor | Serial communication between Raspberry Pi and Arduino | |
Using computer capabilities for API calls | Chip connection to wireless network and querying APIs | |
Builtin keyboard for text input | Support generic USB keyboards for text input (not PS/2 specific) | |
Builtin computer microphone for audio input | Script to record sound through USB microphone and save file to a static local path for processing |
However, I’m glad that I decided to run the whole machine without a laptop. First off, it simply is cooler. Secondly, even with this extra challenge, I still spent a lot of time waiting for the mechanical part of the machine to be built and without this modification, I likely would have finished the code on the first or second day. Lastly, running the code without a laptop (albeit with a Raspberry Pi) taught me a lot of important skills on networking, serial communication, and managing inputs that will be of massive help in later weeks.
Finding a Chip¶
ESP¶
I initially tried both an ESP32 and ESP8266 chip, but abandoned them due to issues connecting to a WiFi network. Although I know that these chips are capable of connecting to a wireless network, I wasn’t able to do it after about an hour of work.
Pico¶
My next (and more promising) chip was the Raspberry Pi Pico. I found a Pico with a built-in WiFi chip, which allowed me to connect to WiFi to query OpenAI. Using the Pico’s TX/RX pins, I was also able to confirm serial communication with this chip. Additionally, using a Pi-family chip allowed me to easily integrate Python/MicroPython, which made querying OpenAI and Whisper APIs exponentially easier. The following code connects the Pico to WiFi, queries OpenAI, and sends the response to an Arduino having been connected through TX/RX pins.
import network
import urequests
import ujson
import machine
import time
ssid = "NETWORKNAME"
password = "NETWORKPASSWORD"
api_key = 'USER API KEY'
prompt = 'PROMPT'
url = 'https://api.openai.com/v1/chat/completions'
def connect_to_wifi():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
while not wlan.isconnected():
pass
print('Connected to Wi-Fi')
def send_prompt_to_openai():
headers = {
'Authorization': 'Bearer ' + api_key,
'Content-Type': 'application/json'
}
data = {
'model': 'gpt-3.5-turbo',
"messages": [{"role": "user", "content": "Answer the following query in an ominous manner, and keep your response to under 20 characters:" + prompt}],
'max_tokens': 50
}
response = urequests.post(url, data=ujson.dumps(data), headers=headers)
return response.json()
def main():
connect_to_wifi()
response = send_prompt_to_openai()
response_text = response['choices'][0]['message']['content']
print(response_text + "\n")
arduinoify(response_text)
def arduinoify(response):
uart1 = machine.UART(1, baudrate=9600, tx=4, rx=5)
uart1.write(response)
if __name__ == '__main__':
main()
However, the Pico had a critical flaw in that I couldn’t easily integrate any USB-based input device - namely, in this case, a USB microphone or USB keyboard. After doing some research online, I discovered that in order to use a keyboard, I either needed to switch from using MicroPython to CircuitPython (which still only may or may not work) or to buy an entirely new keyboard with PS/2 compatibility, neither of which were realistic solutions.
Raspberry Pi 4B¶
I was a little hesitant at first to use a Raspberry Pi, both because using it would require an entire redesign of my current code (connecting to OpenAI via completion model URL changed to querying via a completion prompt), and because the Raspberry Pi itself could qualify as a computer. However the Raspberry Pi just seemed to check all of the boxes, given that it has builtin WiFi connectivity, runs Python, has 4 USB ports for inputs (keyboard, microphone), and can plug directly into the Arduino, effectively eliminating the need for TX/RX pin wiring for serial communication.
However, changing to a Raspberry Pi required a few modifications:
- Since the Raspberry Pi is more a computer than a chip (runs Linux), a code redesign is necessary. As mentioned earlier, I no longer used a URL endpoint to query OpenAI but rather assembled the entire prompt locally and sent it out to a model (either GPT3.5 or GPT4 - for the purposes of keeping my API costs low, I use GPT3.5 but there should be no significant difference given that all generated responses are soft capped at 20 characters.)
- A screen. Once more, because the Pi resembles an actual computer, I have to actually execute and call a script on the Pi as opposed to loading a script onto it. I actually always wanted to add a screen to this project, but with a Pi, adding a screen allows me to run and view the status of the program from the actual machine. The screen also functions as the HDMI output of the Pi - for many hours, I was coding on a small 5 inch screen.
Software Development¶
Now that I have decided on a chip and have a more clear idea of the entire system, it’s time to start programming. Again, envision the final program as a conglomeration of several key tasks:
- Receiving audio input from the Microphone
- Saving the audio file and converting to text (Whisper API)
- Generating the oujia board’s response to the user query (OpenAI API + some minor prompt engineering)
- Processing the response text into movements for the gantries
- Moving the gantries and therefore the magnet
Audio Input¶
As I had little prior experience in both Linux and working with microphone input devices, I developed the following handy program that allowed me to view all connected audio devices along with some of their specs.
import pyaudio
audio = pyaudio.PyAudio()
def print_device_info(device_index):
device_info = audio.get_device_info_by_index(device_index)
print(f"Device {device_index}: {device_info.get('name')}")
print(f" Input Channels: {device_info.get('maxInputChannels')}")
print(f" Output Channels: {device_info.get('maxOutputChannels')}")
print(f" Default Sample Rate: {device_info.get('defaultSampleRate')}")
num_devices = audio.get_device_count()
print(f"Found {num_devices} device(s)\n")
for i in range(0, num_devices):
print_device_info(i)
audio.terminate()
As a demonstration, when ran on my computer, the above code yields the following output:
Device 0: Microsoft Sound Mapper - Input
Input Channels: 2
Output Channels: 0
Default Sample Rate: 44100.0
Device 1: Microphone Array (Intel® Smart
Input Channels: 4
Output Channels: 0
Default Sample Rate: 44100.0
Device 2: Microsoft Sound Mapper - Output
Input Channels: 0
Output Channels: 2
Default Sample Rate: 44100.0
[...]
Device 19: PC Speaker (Realtek HD Audio 2nd output with SST)
Input Channels: 2
Output Channels: 0
Default Sample Rate: 48000.0
Device 20: Stereo Mix (Realtek HD Audio Stereo input)
Input Channels: 2
Output Channels: 0
Default Sample Rate: 48000.0
As is shown in the printout, this script finds the input channels, output channels, and default sample rate of any connected audio device, along with its device number.
When running the diagnostic script on the Raspberry Pi, I learn that the USB microphone is recognized as Device #2, with 2 audio input channels and a default sample rate of 44100.0. These specifications are important for recording audio.
Now that the device specifications are cleared up, the actual coding can begin. The following script records 10 seconds of audio and saves it to “output.wav”.
import pyaudio
import wave
# Audio recording parameters
FORMAT = pyaudio.paInt16 # Audio format (16-bit PCM)
CHANNELS = 2 # The microphone has 2 audio input channels
RATE = 22050 # Reduced sampling rate from default of 44100 to minimize overflow errors (they are bad)
CHUNK = 2048 # Increased buffer size to minimize overflow errors (they are bad)
RECORD_SECONDS = 10 # Duration of recording
WAVE_OUTPUT_FILENAME = "output.wav" # Output filename
def record_audio():
audio = pyaudio.PyAudio()
# Open stream
stream = audio.open(format=FORMAT, channels=CHANNELS,
rate=RATE, input=True, input_device_index=2, # Device index of 2 because the microphone is recognized as Device #2
frames_per_buffer=CHUNK)
print("Recording...")
frames = []
# Record for specified number of seconds
try:
for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
try:
data = stream.read(CHUNK)
frames.append(data)
except IOError as e:
if e.errno == pyaudio.paInputOverflowed:
print(f"Overflow at iteration {i}. Continuing...")
time.sleep(0.1)
except Exception as e:
print(f"Error during recording: {e}")
print("Recording finished.")
stream.stop_stream()
stream.close()
audio.terminate()
# Save the recorded data as a WAV file
try:
with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf:
wf.setnchannels(CHANNELS)
wf.setsampwidth(audio.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
except Exception as e:
print(f"Error saving WAV file: {e}")
Audio to Text¶
Converting audio to text is surprisingly easy, although the process can take upwards of 20 seconds for a short clip.
import whisper
def transcribe_audio(file_path):
model = whisper.load_model("base")
result = model.transcribe(file_path)
return result["text"]
Side note: the code’s implementation is set up so that every time the recording occurs, it overwrites the previous recording in the output.wav file. This means that there is no wasted storage on old audio clips, however, this could easily be tweaked if you are trying to implement this with saving.
Generative Text Response¶
Now that we have extracted text from the short microphone audio clip, we can send that text to OpenAI’s API to generate a response. The following function queries GPT3.5 with a parsed text prompt and returns the output of the query. This process is surprisingly fast.
from openai import OpenAI
def call_openai_api(prompt):
client = OpenAI(api_key='REDACTED')
completion = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a helpful assistant trapped in a ouija board and are a ghost."},
{"role": "user", "content": "Answer the following query in an ominous tone and under 25 characters: " + prompt}
]
)
return completion.choices[0].message.content
Serial Connection¶
This code snippet, in isolation, initializes a new serial connection. Since the Pi is wired straight to the Arduino’s USB port, writing to the serial here is equivalent to typing something into the Arduino IDE’s Serial Monitor. Obviously, the code is not used in isolation in the actual program.
from serial import Serial
ser = Serial('/dev/ttyUSB0', 115200, timeout = 1)
ser.write(("Whatever you want to write").encode())
The lsusb
builtin command on linux displays all connected USB devices. When unplugging the Arduino and rerunning lsusb
, I can see that the ttyUSB0 device has disappeared. Therefore, the Arduino is the tty0USB device.
The .encode()
function is important because it turns the string into bytes before sending it through serial. Otherwise, Arduino cannot interpret the string.
Moving the Magnet¶
To move the magnet based on each character in the text string, I first need to find physical positions of each character and map them to that character. This process took about 20 minutes to do, as it was just some manual labor.
alphabet_dict = {chr(65 + i): (0, 0) for i in range(26)}
alphabet_dict['A'] = (1, 20)
alphabet_dict['B'] = (5, 25)
alphabet_dict['C'] = (10, 27)
alphabet_dict['D'] = (16, 28)
alphabet_dict['E'] = (20, 30)
alphabet_dict['F'] = (27, 30)
alphabet_dict['G'] = (34, 30)
alphabet_dict['H'] = (37, 31)
alphabet_dict['I'] = (41, 31)
alphabet_dict['J'] = (44, 28)
alphabet_dict['K'] = (51, 27)
alphabet_dict['L'] = (57, 25)
alphabet_dict['M'] = (64, 22)
alphabet_dict['N'] = (0, 10)
alphabet_dict['O'] = (3, 12)
alphabet_dict['P'] = (9, 17)
alphabet_dict['Q'] = (14, 20)
alphabet_dict['R'] = (19, 22)
alphabet_dict['S'] = (24, 23)
alphabet_dict['T'] = (30, 23)
alphabet_dict['U'] = (36, 23)
alphabet_dict['V'] = (41, 21)
alphabet_dict['W'] = (47, 20)
alphabet_dict['X'] = (55, 17)
alphabet_dict['Y'] = (59, 14)
alphabet_dict['Z'] = (64, 10)
alphabet_dict['1'] = (9, 5)
alphabet_dict['2'] = (15, 5)
alphabet_dict['3'] = (19, 5)
alphabet_dict['4'] = (24, 5)
alphabet_dict['5'] = (29, 5)
alphabet_dict['6'] = (34, 5)
alphabet_dict['7'] = (39, 5)
alphabet_dict['8'] = (43, 5)
alphabet_dict['9'] = (48, 5)
alphabet_dict['0'] = (53, 5)
alphabet_dict['.'] = (24, 0)
alphabet_dict[','] = (40, 0)
alphabet_dict[' '] = (33, 13)
The dictionary maps the physical coordinate values of each letter on the actual engraved ouija board to the character in the text string. Now to actually move the magnet to the right location:
def move(letter):
time.sleep(4)
x, y = alphabet_dict[letter.upper()]
ser.write(("g1x" + str(x) + "y" + str(y) + "f2000" + "\n").encode());
time.sleep(4)
The command format for moving the gantries is shown in the ser.write command. For example, writing g1x40y25f500\n
would move the magnet to (40, 25) at a frequency (speed) of 500.
The newline character is important in actually sending the command across. Without it, this is analogous to typing something into the Arduino IDE’s Serial Monitor and not clicking the enter button.
With the move function defined, a simple for each loop that iterates through the string that we will display on the ouija board is all I need.
The delays are important so that the gantries actually have time to move. These values can be further optimized - increasing or decreasing them depending on how much time you want the magnet to stay on each letter for and how much leeway you want to give the machine.
for char in response:
move(char)
Aggregation¶
To tie up all the individual parts:
import pyaudio
import wave
import time
import whisper
import os
import curses
from openai import OpenAI
from serial import Serial
alphabet_dict = {chr(65 + i): (0, 0) for i in range(26)}
alphabet_dict['A'] = (1, 20)
alphabet_dict['B'] = (5, 25)
alphabet_dict['C'] = (10, 27)
alphabet_dict['D'] = (16, 28)
alphabet_dict['E'] = (20, 30)
alphabet_dict['F'] = (27, 30)
alphabet_dict['G'] = (34, 30)
alphabet_dict['H'] = (37, 31)
alphabet_dict['I'] = (41, 31)
alphabet_dict['J'] = (44, 28)
alphabet_dict['K'] = (51, 27)
alphabet_dict['L'] = (57, 25)
alphabet_dict['M'] = (64, 22)
alphabet_dict['N'] = (0, 10)
alphabet_dict['O'] = (3, 12)
alphabet_dict['P'] = (9, 17)
alphabet_dict['Q'] = (14, 20)
alphabet_dict['R'] = (19, 22)
alphabet_dict['S'] = (24, 23)
alphabet_dict['T'] = (30, 23)
alphabet_dict['U'] = (36, 23)
alphabet_dict['V'] = (41, 21)
alphabet_dict['W'] = (47, 20)
alphabet_dict['X'] = (55, 17)
alphabet_dict['Y'] = (59, 14)
alphabet_dict['Z'] = (64, 10)
alphabet_dict['1'] = (9, 5)
alphabet_dict['2'] = (15, 5)
alphabet_dict['3'] = (19, 5)
alphabet_dict['4'] = (24, 5)
alphabet_dict['5'] = (29, 5)
alphabet_dict['6'] = (34, 5)
alphabet_dict['7'] = (39, 5)
alphabet_dict['8'] = (43, 5)
alphabet_dict['9'] = (48, 5)
alphabet_dict['0'] = (53, 5)
alphabet_dict['.'] = (24, 0)
alphabet_dict[','] = (40, 0)
alphabet_dict[' '] = (33, 13)
ser = Serial('/dev/ttyUSB0', 115200, timeout = 1)
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 22050
CHUNK = 2048
RECORD_SECONDS = 10
WAVE_OUTPUT_FILENAME = "output.wav"
def record_audio():
audio = pyaudio.PyAudio()
stream = audio.open(format=FORMAT, channels=CHANNELS,
rate=RATE, input=True, input_device_index=2,
frames_per_buffer=CHUNK)
print("Recording...")
frames = []
try:
for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):
try:
data = stream.read(CHUNK)
frames.append(data)
except IOError as e:
if e.errno == pyaudio.paInputOverflowed:
print(f"Overflow at iteration {i}. Continuing...")
time.sleep(0.1)
except Exception as e:
print(f"Error during recording: {e}")
print("Recording finished.")
stream.stop_stream()
stream.close()
audio.terminate()
try:
with wave.open(WAVE_OUTPUT_FILENAME, 'wb') as wf:
wf.setnchannels(CHANNELS)
wf.setsampwidth(audio.get_sample_size(FORMAT))
wf.setframerate(RATE)
wf.writeframes(b''.join(frames))
except Exception as e:
print(f"Error saving WAV file: {e}")
def transcribe_audio(file_path):
model = whisper.load_model("base")
result = model.transcribe(file_path)
return result["text"]
def move(letter):
time.sleep(1.5)
x, y = alphabet_dict[letter.upper()]
ser.write(("g1x" + str(x) + "y" + str(y) + "f2000" + "\n").encode());
time.sleep(1.5)
def call_openai_api(prompt):
client = OpenAI(api_key='REDACTED')
completion = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a helpful assistant trapped in a ouija board and are a ghost."},
{"role": "user", "content": "Answer the following query in an ominous tone and under 20 characters: " + prompt}
]
)
return completion.choices[0].message.content
def main():
record_audio()
transcription = transcribe_audio(WAVE_OUTPUT_FILENAME)
print("Transcribed: ", transcription)
response = call_openai_api(transcription)
print("Response from OpenAI:\n", response)
for char in response:
move(char)
t_end = time.time() + 10
while time.time() < t_end:
ser.write(("g1x0y0f500\n").encode());
if __name__ == '__main__':
main()
The only discrepancy between this code and the sum of each individual task is the g1x0y0f500
command at the end, which brings the machine back to (0, 0). An interesting phenomenon occurs - that when the program ends, the machine will set its current point as (0, 0). As such, after the program finishes, it is hard coded to return to the true home point so it is ready for the next query.
Hero Shot¶
Next Steps¶
We had a couple ideas on how this project could be improved in the future:
- Incorporate a magnet pointer instead of a pure magnet on the top of the board, like regular ouija boards often do
- Use a longer belt, as the current belt we used caused a lot of problems as it was just barely long enough to fit the entire track length when stretched out
- Add more multimodal input methods
File Downloads¶
Our group’s files for this week can be downloaded here.