Blog

Making a Voice Assistant – part 3

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.

Making a Voice Assistant – part 2

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.

Making a Voice Assistant – part 1

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.

Built a LAMP server

I just finished turning a Raspberry Pi into my own personal LAMP server. This is going to be a test environment for running any web apps that I’m developing. I just used hardware I had laying around, including a vintage amber monitor (from the Apple II era) and a mechanical keyboard I built a while back. I think the setup looks pretty cool.

Ran into a few problems, including having to edit the config file to create overscans for fitting the display better on the curved monitor, and it seems a recent OS change makes most tutorials on setting up phpMyAdmin user privileges on a pi wrong now, but I was able to figure it out. Now that I have a working server I can SSH to, I need to put some code together…

Unity Game Dev Update

I have been teaching myself game development using Unity for a few weeks now. I’ve put together a small project for my nephews that I’m calling “Robot Slayer”: a FPS shooter, with robots. Unfortunately, Unity decided to pull the rug out from under all developers this week with their predatory pricing changes, and it has pretty much stalled my interest in learning Unity further. Still, it was a fun project, and I am putting my mini game on my site here if anyone is interested: RobotSlayer. It’s just one small level and a bit janky, but it’s playable and you can run it in your browser. I’ve also put the code for it up on GitHub.

Game Dev in Unity

My nephews wanted me to make a videogame for them, so I’m teaching myself some C# game dev within Unity. After watching several tutorials, I started by creating an arena where I could test attaching scripts to game objects. I experimented with textures and skyboxes a bit. A script for player movement was easy enough to get working. Then I wrote a basic script to have objects move through the environment while avoiding obstacles via Raycasting. That looked like this:

Next, I created a proper enemy, with a bit of basic AI. Using the code for avoiding obstacles, I added a function to cause the enemy to stalk the player through the environment and to attack when the player is spotted. Then I created a simple weapons system and armed the enemy and the player. (This also gave me a chance to play with Bloom post-processing to get the projectiles “glowy”.) This is where I’m at now:

Next on the agenda:

  • create a damage system so the enemy and player will take damage when hit
  • create an effect to communicate the destruction of an enemy
  • create an effect that will communicate when the player takes damage
  • write an enemy spawn script that will generate new enemies whenever an enemy is destroyed
  • build a weapon change system

Adventures in Commodore 64

I consider myself something of a videogame historian: I like to collect and restore vintage computers, and then run original games on the original hardware. One of my recent adventures has been exploring the world of Commodore 64’s. I purchased my first C64 from a local Frameshop that used it as their primary computer for about 32 years, until the store owner recently passed away. The new owner wasn’t interested in using a Commodore as her Point-Of-Sale system, so she sold me the computer and even threw in the POS software (on a 5.25″ floppy) that the previous owner had written himself.

Since I now own the only copy of this software, I felt that I should try preserving it. To that end, I began experimenting, and I was eventually able to use a SD2IEC in combination with CBM Command to save a d64 image of the floppy disk onto a SD card. Then, I uploaded the Frameshop POS to Internet Archive.

While I was at it, I also uploaded some other obscure C64 files I happen to have in my collection: COSMI Top 20 Tools for Commodore 64/128 (has a neat spinning menu), and MFJ MultiCom64 (software for controlling Ham Radio).

Advertising in Fiction

I find fictional advertising really interesting. I’ve noticed that several modern videogames include fictional advertising for fictional products as a type of world building. In the real world, thriving economies are underscored by a background of ads, so seeing ads in a fictional world makes it seem a little more real.

As an advertising nerd though, I like to stop and admire these ads and wonder about the thought process behind them. Are they simply mimicking a real world advertisement, or has some zealous designer invented an entire fictional ad campaign, complete with an original brand strategy? Usually these ads take the form of posters plastered throughout the environment, though I have also come across tv commercials and radio ads. Next time you are playing a game that creates a believable virtual world, look around and see if you can spot the advertising.

Lessons I Learned Running an E-Commerce Store

For many years, my wife and I have enjoyed spending our weekends at estate sales, yard sales, auctions, and thrift stores. It’s fun treasure hunting for the rare and unusual, and antiques give you a physical connection to the past that I find worthwhile. I collect old books, vintage technology, art supplies, pencils, and whatever else I happen to find interesting. At some point, I started routinely culling my collections so they wouldn’t get out of hand. Then I began looking for things I could sell for a profit to fund other purchases. I sold smaller items in my eBay store, and larger items locally on Facebook Marketplace. At a certain point, I realized that my side hustle was generating enough revenue that I could quit my day job as a Web App Dev and become a full-time e-commerce business owner. So I did. This is what I learned from that experience:

Become an Industry Expert

I thought that I was in the business of flipping rare antiques for profit. What I came to realize is, I was really in the business of leveraging knowledge. In order to acquire valuable inventory, I needed to be able to identify value where most people saw trash. Moving items from a market where they are under-valued to a market where they are highly sought. This is difficult to do, and requires a great deal of knowledge.

I studied certain areas in great depth. I read books and blogs, I watched videos, I joined online groups, I pulled sales data from various sources… I made myself an expert on the sorts of things I could likely find in the places I most often looked. Not just the monetary value, but also the market trends for those categories.

For example, some items sell for a high rate, but don’t sell very quickly, so you have to account for how long your funds will be tied up in that piece of merchandise. Every dollar trapped in inventory is a dollar that you can’t spend on acquiring new inventory. Some items you might be able to buy for $50 and sell for $500, a healthy ROI, but the customers for that item are few and difficult to reach. Some items are so rare that it’s impossible to find data on previous sales, which creates an element of risk that must be accounted for. Data is king.

I’ll give you a real example. There was a certain model of Polaroid camera that I would occasionally come across, usually for about $5. Most people saw Polaroids as virtually worthless since they require a special film that hasn’t been produced in decades, making the camera essentially a paper weight. However, I knew that a new company had recently sprung up to cater to the vintage camera enthusiast market, and this company was now producing film for old Polaroid cameras. Suddenly, these “worthless” cameras were in high demand in certain circles. They would sell almost instantly for around $80, meaning every time I came across one for $5 I knew that I would make a quick return of 16 times my investment. I did this many times. Knowledge equals profit.

Find Passionate Niche Markets

My specialty was finding hard-to-find, high-value antiques, and selling those antiques to passionate collectors. I sold fountain pens, movie props, cameras, books, art, industrial decor, and anything else I knew a collector would highly prize. Truly obscure items don’t pop up on eBay as often as common items, and when they do, they are often not optimized in such a way that collectors can easily find the listing. This can make it difficult for collectors to find the items they are the most motivated to acquire, and with the economics of supply-and-demand, the more difficult it is to acquire an item the more a collector is willing to pay for it. Connecting these motivated collectors with their holy grail equals profit. Thankfully, groups of like-minded collectors tend to gather on social media platforms, which made my job easier. To reach them:

  • I joined niche collector groups on Facebook, Reddit, and certain forums.
  • I posted quality pictures of my inventory on Instagram, with hashtags that would attract the attention of collectors, and descriptions that indicated that the items were for sale.
  • I marketed my social media accounts to grow followers, who would eagerly watch to see if I posted the items they collected.
  • I kept lists of good customers so I could send them special offers.

When you sell common items, there is a larger market, but also a greater level of competition. Greater supply, less demand. On the other hand, passionate niche markets have less supply and greater demand, but you have to work harder to reach those customers. You have to go where the customers are.

Know when to Pivot

I enjoyed running my own company. I learned a great deal, and I earned enough money to live pretty comfortably. But I also came to the realization that what I was doing was not scalable. There is only one of me and only so many hours in a day. Unfortunately, that means there is a limit to how much I could conceivably earn (since the nature of the business wasn’t conducive to expansion), and I also knew that my personal income potential was higher than this business could provide. Besides this, I found myself ready to take on a new challenge. After running my company for a few years, I decided to pivot and channel the passion I had devolved for business and marketing into a new career. (However, I still sell as a side hustle, and likely always will.)

Data Analysis of Tuscaloosa Tweets

I wanted to play around with Sentiment Analysis of Tweets; specifically, I wanted to try the Python TextBlob library, which has a built-in function that performs text analysis to determine if a string has a positive or negative sentiment. After pondering a bit, I decided it would be fun to search for tweets that were created specifically within the city limits of Tuscaloosa, where I am currently attending school. I wrote a script that scrapes Twitter and returns tweets by geolocation, and then uses TextBlob on the results.

# -*- coding: utf-8 -*-
"""
Created on Wed Jul  6 15:58:58 2022

@author: austin
"""

import snscrape.modules.twitter as sntwitter #Social Network Scraping Library
import pandas as pd #so I can make a dataframe of results
from textblob import TextBlob
import csv
import time

#Tuscaloosa = geocode:33.23726448661455,-87.58279011262114,20km
query = "geocode:33.23726448661455,-87.58279011262114,20km"
tweets = []
combinedtweets = []
limit = 10000000 #set a limit on how many results I want to pull

for tweet in sntwitter.TwitterSearchScraper(query).get_items():
    
    if len(tweets) == limit:
        break
    else:
        # set sentiment 
        text = tweet.content
        analysis = TextBlob(text)
        if analysis.sentiment.polarity >= 0:
            sentiment = 'positive'
        else: 
            sentiment = 'negative'
        tweets.append([tweet.date, tweet.user.username, tweet.content, sentiment])

df = pd.DataFrame(tweets, columns=['Date', 'User','Tweet', 'Sentiment'])
df.to_csv('twitter_scrape_results.csv') #save dataframe as csv

print("\014") #clear console
time.sleep(10) 
print("CSV Successfully Created")

The results were pretty interesting (I uploaded the dataset to Kaggle if anyone is interested). It seems sentiment stays roughly the same each year, hovering around 85% positive and 15% negative. I really would have thought negative sentiment would be much higher based on my personal observations of Twitter content: makes me wonder if Tuscaloosa is an unusually happy place, or if my Twitter observations are influenced by negative bias…

In any case, perhaps a more interesting bit of data is that the total amount of Tweets seems to decline quite a bit each year. This raises the question, why are Tuscaloosians tweeting less often? I put the results into this Tableau dashboard, which displays just how steady and steep a decline there has been.

Update:

I decided to test a hypothesis: perhaps the high level of positive tweet sentiment is due to the fact that this is a college town, and numerous tweets were posted by official University of Alabama departments? I used OpenRefine to filter out official UA accounts, which was easy enough to do since their usernames seem to either begin with “UA_” or end with “_UA”. Surprisingly though, that didn’t change the sentiment percentages at all. I now suspect that even if you factor in all official UA Twitter accounts, you would also have to factor for the fact that a large number of Tuscaloosians work for UA (45,000 employees). I know many of my professors post UA related content using their personal Twitter accounts, and by design this content will logically slant positive.

RSS
Twitter
LinkedIn