I decided to house my voice assistant in this cool antique bakelite radio that I found at an estate sale. (The radio was in pieces and non-working, so it was a perfect candidate for my project.) I also upgraded the Raspberry Pi Zero W to a Pi 3 B+ which runs my code a bit better without getting too hot.
After removing the radio’s internals and giving the case a thorough cleaning, I mounted the components to an aluminum board using standoffs and then mounted the board in the radio with more standoffs. I think the end result looks great on my desk, and you can still see the LEDs through the original cloth speaker cover.
I have made some progress with my Python voice assistant. I have it running on a Raspberry Pi Zero W with an Adafruit Voice Bonnet. The code doesn’t run as fast on the Pi Zero as it does on my pc, and it runs a little hot, but it does work. I’ll experiment with a Pi 3B+ later to see how the performance differs. I’ve added the code to GitHub for anyone interested.
I still need to create the JavaScript side of things to give the assistant more utility. And I need to build an enclosure for it, but sadly my ancient 3D printer has just shuffled off its mortal coil, so until I can afford a new printer the enclosure will have to wait.
I use my Alexa Echo devices daily for various purposes. While I find it incredibly useful, Alexa does have limitations, and it would be great if it were more customizable. I decided to try making my own voice assistant that I can fully customize to my needs. I attempted this many years ago when I was first learning how to code, and mostly failed. At the time, the only method available to me for speech synthesis was to have a database of pre-recorded audio files that I could call when appropriate, and that was very limiting. Now however, there is a nifty text-to-speech conversion Python library called “pyttsx3“. Using this library alongside Google’s cloud speech recognition API (which can be accessed using the SpeechRecognition 3.10.0 library) allowed me to put together a very capable voice assistant. So far, this is what I have:
# -*- coding: utf-8 -*-
"""
Created on Fri Oct 13 13:31:34 2023
@author: austin dixon
"""
import os
import time
import playsound
import speech_recognition as sr
from gtts import gTTS
import pyttsx3
from datetime import datetime
import wikipedia
import time
from datetime import date
def voiceChange():
eng = pyttsx3.init() #initialize an instance
voice = eng.getProperty('voices') #get the available voices
# eng.setProperty('voice', voice[0].id) #set the voice to index 0 for male voice
eng.setProperty('voice', voice[1].id) #changing voice to index 1 for female voice
if __name__ == "__main__":
voiceChange()
def get_audio(): #listen to input from microphone
r = sr.Recognizer()
with sr.Microphone() as source:
audio = r.listen(source)
said = ""
try:
said = r.recognize_google(audio) #use Google Cloud to process language
print(said)
except Exception as e:
print("Exception: " + str(e))
return said
wake_word = "hey Darla"
break_word = "stop"
engine = pyttsx3.init() #start the voice engine
while True:
text = get_audio() #get microphone input as string
if break_word in text: #stop running loop
break
elif wake_word in text or "darling" in text: #listen for wake word
text = text.replace(wake_word, '')
#########################################################################################################################
#smalltalk
if "what is your name" in text or "who are you" in text or "what are you" in text:
engine.say("My name is Darla. I am a virtual assistiant created by the, brilliant, Austin Dixon.")
engine.runAndWait()
elif "hello" in text:
engine.say("Hello, how are you today?")
engine.runAndWait()
elif "how are you" in text or "how are you doing" in text or "how are you feeling" in text:
engine.say("I'm doing ok, but I do feel a little artificial... Ha Ha.")
engine.runAndWait()
#########################################################################################################################
#search wikipedia
elif "search" in text:
text = text.replace('search', '')
text = text.replace('for', '')
text = text.replace('the', '')
text = text.replace('internet', '')
text = text.replace('wikipedia', '')
result = ""
try:
result = wikipedia.summary(text)
except Exception:
pass
if result == "":
engine.say("Sorry, I didn't find any results for " + text)
engine.runAndWait()
else:
engine.say(result)
engine.runAndWait()
#########################################################################################################################
# timer
elif "timer" in text:
text = text.replace('set', '')
text = text.replace('a', '')
text = text.replace('timer', '')
text = text.replace('for', '')
if "coffee" in text:
engine.say("Setting coffee timer for 5 minutes.")
engine.runAndWait()
time.sleep(300) #set timer for five mins for coffee
engine.say("Your coffee is ready! .... Your coffee is ready! .... Your coffee is ready!")
engine.runAndWait()
elif "hour" in text:
if "hours" in text:
text = text.replace('hours', '')
engine.say("Setting timer for " + text + " hours")
engine.runAndWait()
else:
text = text.replace('hour', '')
engine.say("Setting timer for one hour")
engine.runAndWait()
hours = int(text) #convert str to int to get hours
secs = hours * 3600 #convert hours to secs
time.sleep(secs)
engine.say("Your time is up! .... Your time is up! .... Your time is up!")
engine.runAndWait()
elif "minute" in text:
if "minutes" in text:
text = text.replace('minutes', '')
engine.say("Setting timer for " + text + " minutes")
engine.runAndWait()
else:
text = text.replace('minute', '')
engine.say("Setting timer for one minute")
engine.runAndWait()
mins = int(text) #convert str to int to get mins
secs = mins * 60 #convert mins to secs
time.sleep(secs)
engine.say("Your time is up! .... Your time is up! .... Your time is up!")
engine.runAndWait()
elif "seconds" in text:
secs = int(text.replace('seconds', '')) #get the number from text and convert str to int
engine.say("Setting timer for " + str(secs) + " seconds")
engine.runAndWait()
time.sleep(secs)
engine.say("Your time is up! .... Your time is up! .... Your time is up!")
engine.runAndWait()
#########################################################################################################################
#time/date
elif "time" in text:
time_now = datetime.now()
current_time = time_now.strftime("%I:%M %p")
engine.say("The current time is " + current_time)
engine.runAndWait()
elif "date" in text:
today = date.today()
engine.say("Today's date is " + str(today))
engine.runAndWait()
#########################################################################################################################
#if input doesn't match any trigger words
else:
engine.say("You just asked me " + text + "... sorry, but I do not know anything about that yet.")
engine.runAndWait()
At the moment, the voice assistant only has a few capabilities. It listens for a wake word to activate, and then can give you the current time or date, it can set a timer, it can search Wikipedia to answer questions, and it has some small-talk options. I intend to build in many more features, including fetching news headlines, giving weather forecasts, web scrapping various sites, setting reminders, sending emails, and controlling some of my smart home gadgets.
Some of these features are going to require a web server running some custom JavaScript that can gather data from online sources and send that data to my voice assistant via JSON requests. So building the web app that will serve as an API to my voice assistant is one of my next steps.
I also want the voice assistant to run on hardware as an IoT device, so that is going to take some tinkering. I have ordered the components I need to build this, and I am waiting for them to arrive. The final step will be deciding what to use to house my project. Maybe 3D printing an animatronic, talking head? Or recreating the Hal 9000? I haven’t decided yet.
Developers tend to take their keyboards seriously. I have been using classic buckling spring IBM Model M computer keyboards since I first began programming. These are great to type on, and I still love them (kind of feels like typing on a typewriter), but I decided recently that I should upgrade to a compact keyboard that uses modern mechanical switches. This would give me more space on my desk, and allow for some customization. There seems to be an endless sea of options to choose from, though; the first step in my consumer journey is to narrow my options down to a few top brands, so what is a developer to do? I thought a good way to cut through the clutter would be to scrape the r/MechanicalKeyboards subreddit to see what brands are the most talked about currently. So I wrote this Python script that uses Reddit’s API to scrape the subreddit.
import praw
from praw.models import MoreComments
import datetime
import pandas as pd
# Lets use PRAW (a Python wrapper for the Reddit API)
reddit = praw.Reddit(client_id='', client_secret='', user_agent='')
# Scraping the posts
posts = reddit.subreddit('MechanicalKeyboards').hot(limit=None) # Sorted by hottest
posts_dict = {"Title": [], "Post Text": [], "Date":[],
"Score": [], "ID": [],
"Total Comments": [], "Post URL": []
}
comments_dict = {"Title": [], "Comment": [], "Date":[],
"Score": [], "ID": [], "Post URL": []
}
for post in posts:
# Title of each post
posts_dict["Title"].append(post.title)
# Text inside a post
posts_dict["Post Text"].append(post.selftext)
# Date of each post
dt = datetime.date.fromtimestamp(post.created_utc) # Convert UTC to DateTime
posts_dict["Date"].append(dt)
# The score of a post
posts_dict["Score"].append(post.score)
# Unique ID of each post
posts_dict["ID"].append(post.id)
# Total number of comments inside the post
posts_dict["Total Comments"].append(post.num_comments)
# URL of each post
posts_dict["Post URL"].append(post.url)
# Now we need to scrape the comments on the posts
id = post.id
submission = reddit.submission(id)
submission.comments.replace_more(limit=0) # Use replace_more to remove all MoreComments
# Use .list() method to also get the comments of the comments
for comment in submission.comments.list():
# Title of each post
comments_dict["Title"].append(post.title)
# The comment
comments_dict["Comment"].append(comment.body)
# Date of each comment
dt = datetime.date.fromtimestamp(comment.created_utc) # Convert UTC to DateTime
comments_dict["Date"].append(dt)
# The score of a comment
comments_dict["Score"].append(comment.score)
# Unique ID of each post
comments_dict["ID"].append(post.id)
# URL of each post
comments_dict["Post URL"].append(post.url)
# Saving the data in pandas dataframes
allPosts = pd.DataFrame(posts_dict)
allPosts
allComments = pd.DataFrame(comments_dict)
allComments
# Time to output everything to csv files
allPosts.to_csv("MechanicalKeyboards_Posts.csv", index=True)
allComments.to_csv("MechanicalKeyboards_Comments.csv", index=True)
Reddit limits API requests to 1000 posts, so the most current 1000 posts is my sample size. My code outputs two files: the last 1000 posts, and more importantly the comments on those 1000 posts, which ended up being 9042 rows of data. (I posted the files to Kaggle if anyone would like to play with them.) Then I imported my comments dataset into OpenRefine so I could run text filters to find brand names, and I recorded the number of mentions for each brand. Finally, using Tableau, I created a couple of Data Visualization charts to express my findings. Here are the most talked about keyboard brands on r/MechanicalKeyboards currently:
×
×
Update:
I decided to go with the Keychron keyboard that my research found to be the most discussed (and I also added Glorious Panda Switches and HK Gaming PBT Keycaps). Couldn’t be happier; it’s a pleasure to type on.
For a class project, I decided to reimagine Katsushika Hokusai’s seminal “Red Fuji” as ASCII art. Essentially, I wrote a Python script that converted Hokusai’s “Red Fuji” into text art using only the letters in Hokusai’s name. Since different letters have different visual weights, you can use them for gradations of shading. The script converts the image into black and white, then assigns a numerical value to each pixel depending on how dark the shading is. Then it replaces each pixel with the letter assigned to that shade range. I like how it turned out.