Author: kim

  • LilL3x on an AIY Voice HAT

    LilL3x on anything other then a reSpeaker is not officially supported, but I know that people will want to use the hardware that they have, and those old AIY kits are just gathering dust!

    A user shared with me the information to get the HAT up and running, and I wanted to pass this knowledge onto you.

    BIG THANKS to mjlill for sharing this with me! See the original post here.

    First, you want to update your config,txt file. Open it in nano:

    sudo cp /boot/firmware/config.txt /boot/firmware/config.txt.bk
    sudo nano /boot/firmware/config.txt

    Make the following changes:

    #dtparam=audio=on                   # comment this line out
    #dtoverlay=vc4-kms-v3d              # comment this line out
    dtoverlay=vc4-fkms-v3d              # add this line right below the one above you commented out
    #dtoverlay=wm8960-soundcard         # comment this line out, if exsists
    dtoverlay=googlevoicehat-soundcard  # add to end

    Next, update asound.conf:

    sudo cp /etc/asound.conf /etc/asound.conf.bk
    sudo nano /etc/asound.conf

    Delete the contents of the file and replace with this:

    options snd_rpi_googlevoicehat_soundcard index=0
    pcm.softvol {
        type softvol
        slave.pcm "hw:sndrpigooglevoi"
        control {
            name Master
            card 0
        }
    }
    pcm.micboost {
        type route
        slave.pcm "hw:sndrpigooglevoi"
        ttable {
            0.0 30.0
            1.1 30.0
        }
    }
    pcm.!default {
        type asym
        playback.pcm "plug:softvol"
        capture.pcm "plug:micboost"
    }
    
    ctl.!default {
        type hw
        card 0
    }

    If you are using the AIY HAT, you will also have to tell LilL3x where the button is. On the reSpeaker, it’s mapped to pin 17, but on the AIY it’s mapped to 23. You might as well set the leds as well while you are there:

    cd ~/LilL3x
    source bin/activate
    python config.py w BUTTON 23  # can also set this on the configuration website
    python config.py w LED_TYPE AIY_LED
    python config.py w LED_PIN 25

    Reboot and your sound card should work! Remember that LilL3x runs at startup, so you won’t be able to do any testing until you quit the process. If LilL3x is running, you can always quit her by doing this:

    cd ~/LilL3x
    touch .quit
    log

    When the log quits, you are free to do any testing you need to do.

  • Introducing LilL3x, the Desktop AI Sidekick

    Who hasn’t wanted their own little AI sidekick? Imagine having a friendly little chatbox sitting on your desk, checking in with you and always up for conversation. This beginner’s Raspberry Pi project will set you up!

    This project assumes:

    • that you are comfortable with Linux, namely installing packages and transferring files.
    • You have experience with 3D printing and printing models off the Internet.
    • You know how to setup and SSH into a Raspberry Pi, and are able to wire the GPIO
    • You are comfortable signing up for various external services (ie ChatGPT) and receiving API keys and secrets/passwords, etc. (Note that these services are not free, but the cost tends to be negligible and often within a free tier.)

    Features

    • Integration with multiple LLMs, including:
      • ChatGPT
      • Ollama
      • Claude
      • Gemini
      • And others!
    • Wake Word functionality (with openWakeWord, PicoVoice Porcupine or Vosk)
    • Integration with multiple text-to-speech engines, including:
      • ChatGPT
      • ElevenLabs
      • Amazon Polly
      • Google
      • TypeCast
    • A mounted Pi Camera will let the AI know when you are there, as well as comment on the surroundings
    • Fully configurable via web interface

    Shopping List

    * Required. This project can be done with just a RPi, sound card and memory card, but you’ll probably want to build your own case.

    Building LilL3x

    Putting the parts together is pretty intuitive. This is the best order to do this:

    • Install the Pi Camera . See this guide for assistance.
    • Attach the 90 degree connector to the GPIO pins on the PI. (This will allow you to install a hat while also taking advantage of the GPIO pins.)
    • Set up the screen (use the pins sticking out to the side, not the ones on top):
      • connect the SDA pin on the screen to pin 3
      • connect the SCL to pin 5
      • Connect the power and the ground to pins 2 and 6 respectively.
    • Use jumper wires to attach pins 1 and 9 to the power (red) and ground (black) of the fan. Note: it might be good to skip this step until you are ready to put the unit in the case.
    • Attach the ReSpeaker 2-Mics Pi HAT to the top of the connector. Take a minute to make sure that the pins are lined up correctly!
    • Attach the speaker to the hat in the rightmost port. It’s tricky to get in, the exposed wires face downward.

    Side view:

    Installation

    Next step is to prepare the Pi. Instructions for this can be found here: Setting up your Raspberry Pi. Follow the “headless” path. I suggest you set the hostname to your AI’s name. Come back once you are SSH’ed into your Pi.

    Note that LilL3x is only supported on trixie at this time, bookworm is not supported.

    First, test the camera. Check that your camera is recognized:

    rpicam-hello --list-cameras

    Note: if you are using a USB camera it won’t appear in this list. See here for information on setting up the USB camera.

    If you camera is not found, unplug your pi, double-check the connections, and reboot. The blue stripe should be facing the USB port.

    Once you’ve gotten to a SSH shell with a working camera, download and run the setup script:

    wget https://raw.githubusercontent.com/followkim/LilL3x/refs/heads/v2/install/install.sh
    bash install.sh

    Note:

    • The website will install first, wait until it is finished and press enter to acknowledge. Then you are free to start configuration.
    • When you are done, you will still need to setup the audio card, see below.

    Configuring LilL3x

    Configure LilL3x while install is finishing. If you go to the Raspberry PI’s IP on a web browser, you should now see a menu and a configuration link. Click “configuration”. (LilL3x will work with the default configuration and you can change it at any time, so feel free to put off this step.)

    Most of the defaults are fine, but you will want to fill out:

    • Your info and your AI’s name
    • The AI engine that you will use (ie ChatGPT), along with an API key and model
    • Wake: PicoVoice is no longer free, so if you don’t have an account openWakeWord is your best bet. You can also choose a wakeword from the defaults if you don’t plan to make your own. Note; the default wakewords won’t show up yet, but they will appear after the install process is complete.
    • Speech Tools: Choose a speech engine, enter your credentials and choose a voice
    • Camera Tools: set the Imgur Client ID (see below)

    Setting up a Wake Word

    openWakeWord

    Go to openWakeWord.com:

    • Follow the instructions to create a new wake word (costs about $4) or select an existing one from the library (free)
    • Download the .onnx file and use ftp to upload it to ~/LilL3x/wake. Change the file name to remove the number, if you want, just make sure it ends with .onnx.
    • Select the new wake word on the configuration webpage. (You may need to refresh.) Note that the default wake words won’t appear until after the install process is complete.

    PicoVoice

    Go to PicoVoice:

    • Sign up for an account, if you are able to. Pico’s free tier seems to be going away.
    • Create a new wake word and download the zip to your computer
    • Unzip the file
    • Use FTP to move the file to ~/LilL3x/wake. Do not change the file name.
    • Select the new wake word on the configuration webpage. (You may need to refresh.) Note that the default wake words won’t appear until after the install process is complete.

    If PicoVoice and openWakeWord is not an option, you can use Vosk which will (attempt) use your AI’s name as a wake word. Word of warning: this does not work well.

    Get an Imgur Key

    An imgur Client ID key is needed to upload files to imgur. Some models, such as ChatGPT, need a web address to an image file and imgur is used as a middleman.

    • Go to https://imgur.com/ and register for a free account
    • Go to account settings (in the profile picture on the upper right)
    • Select “Applications” from the left sidebar
    • Create a new Client ID and insert it into the “Camera” section of the configuration page, in field for the Imgur Client ID.

    Setting the audio device

    If you are using a AIY voice HAT, see these instructions.

    Let’s go back to the install. (It’s probably waiting for you– just a reminder, don’t reboot yet!) When the install script completes, you have to set the default audio device.

    As of trixie, raspi-config can no longer set the sound card. I am working to automate this, but currently you need to find and set the audio sink on your own:

    1. Get the status of Pipewire by calling wpctl status
    2. Look for the sinks listed in Audio: Sinks, and try the one that isn’t selected. Remember the ID.
    3. Inspect the ID with wpctl inspect to make sure it’s the right sound card (seeed-2mic-voicecard)
    4. Set it as default with wpctl set-default
    wpctl statuswpctl inspect <id>
    wpctl set-default <id>

    So in this case above, you’d want to inspect sink 58 to see if it’s the correct card. If it matches, then select it as the default.

    Testing

    Please Read! If you’ve come here after installing and rebooting (ie to fix the camera) you will need to keep in mind that the below tests should only be run when LilL3x is not running. otherwise they will fail. (Of course, if LilL3x is running, the best way to test it is to use it, so testing might not be necessary.)

    After install LilL3x will run on boot, so when you boot up your Pi LilL3x is launched. You will have to end the process before continuing, if you want to test.

    To check if LilL3x is running:

    ps -A -f | grep lillex.py

    If that shows processes (ignore the grep process), then you should stop it before proceeding. Ask it to stop with this:

    cd ~/LilL3x/
    touch .quit  # ONLY if lillex is running!!!

    Wait about 10 seconds and then see if the process is still running. If it is, you might have to kill it. You can also type log to see if the program is shutting down or not.

    Now that we’ve installed, lets test to make sure everything is working. We can do this without a reboot. First get into the virtual environment:

    cd ~/LilL3x
    . bin/activate

    Your prompt should start with “(LilL3x)” now. If it doesn’t the below tests will fail.

    Start with the screen. Test the screen with this command (do this even if you don’t have a screen– to test the install):

    python stats.py

    If you get “ModuleNotFoundError: No module named 'board'“, then that’s because you were naughty and said “Yes” when you were asked to reboot during install. You can either run the entire install script again, or just run everything you missed. Start with adafruit-circuitpython-ssd1306 and run everything after that. The install script is here: ~/LilL3x/install/install.sh.

    Otherwise If nothing is appearing on the screen, or you get errors: check is that it is wired correctly. You might want to perform the rest of the tests first.

    Press the button on the reSpeaker to move on.

    You can test the microphone and speaker this way:

    python speech_tools.py # test the speaker
    python listen_tools.py # test the microphone (say 'quit' to end)

    If you don’t get audio, recheck that you selected the correct sound card.

    Move on to the next test, which will show the camera view on the screen.

    python camera_tools.py

    If after a few seconds, if you see LilL3x winking at you but no video, then there is likely a hardware issue with your camera or ribbon. The errors in the terminal should give you some clues. If you are using a USB camera, then check your device with v4l2-ctl --list-devices and make sure the device ID on the configuration webpage is set to the top number on your device. See here for information on setting up the USB camera.

    Press Cntl-C to exit the camera test. It will take a picture first to test the Imgur connection, then quit.

    Start It Up!

    You have installed all the needed software for LilL3x, and it’s time to sudo reboot. If everything is going well you will see a screen welcoming you to your project and showing your IP. Press the button on the reSpeaker to continue. Be patient, LilL3x takes a bit of time to boot!

    • SSH in and type log while LilL3x is booting. This will tail the log (control-c to exit). If you get a “No such file or directory” error, wait until the screen changes to a logo and try again– the log hasn’t been created yet.
    • The log will also be vital in helping you debug your engine (AI, speech, etc) connections. If you get an error, the log will give you the details.
    • Press the button on the audio HAT to wake LilL3x if the wake word isn’t working.

    If you didn’t change the AI Engine, the one that comes pre-set is “Eliza“, one of the earliest known chatbots built by Joseph Weizenbaum at MIT.

    Note: if you just change an key for an engine (ie, your openAI key) but not the engine itself, the engine itself won’t reload. After saving the key, click the reload button to reload the engine.

    Other things:

    • The button has a few uses:
      • Press the button until you hear a beep to “wake” LilL3x. This is the same as using the wake word.
      • Hold the button until you hear a second beep to restart the application
      • Keep holding until you hear two beeps to reboot the Pi
    • In a terminal window you can control program flow. While in ~/LilL3x:
      • type touch .restart to restart the program
      • type touch .reboot to reboot the pi
      • type touch .quit to quit the program. This is useful for debugging issues.
    • There are a number of programmed phrases that do specific things, including
      • “Show me what you see” (useful to test the camera)
      • “What’s your IP address?”

    Finally, know that LilL3x doesn’t like being run from the command line. If you have stopped the program and want to restart it, reboot the Pi. If you have to start from the command line, press LilL3x’s button until it beeps four times to reboot the unit once you’ve determined LilL3x is working.

    Still having issues? Let me know about it on GitHub! You can also contact me via the contact form on my website. You can also contact me on Discord. In fact, PLEASE let me know if you built the project! Seriously, I am kinda begging you here. Are people building this? Giving up halfway through? Built it and think it’s stupid? I have no idea! My Discord is lonely!

    3D Printing the Case

    The Onshape 3D Model

    Note: the Onshape model is upside down. Long story.

    Print all parts x1 except:

    • Part 8 (peg with a notch removed) – print x4
    • Part 4 (peg with half head) – print x2
    • Part 9 (cylinder) – print x2

    Use the following color scheme:

    • Print the three big pieces (the case, the door and the lid) in the primary color
    • Print the small piece that looks like a hat (piece 6) in clear
    • Print the rest of the pieces in the secondary color

    You might want to consider printing extras of the smaller pieces!

    1. Slip the Raspberry Pi into the large case with the USB facing the top. The holes will line up with the pegs and it should click right in.
    2. Push the speaker into the hole on the side. The wires of the speaker should face downwards (towards the Pi.) Secure it with glue.
    3. insert the transparent piece in the oblong hole on the door. It WILL fit, you might need to tap it lightly with a hammer.
    4. Glue the screen into the top window on the door (the smaller window, it will only fit in one.) The prongs should be towards the top. Tip: take a picture of the wired and working screen first as a reference to where the wires go.
    5. Use the small half-moon pegs to secure the camera holder into the camera window. The notch inside the camera holder should be facing down. You can use the cylinders instead if you prefer.
    6. GENTLY push the camera into the camera holder. It’s very touchy. Don’t try to press it all the way, just enough to clear the pegs. The camera cable will loop upwards, towards the USB ports.
    7. Attach the wires to the screen, stashing the extra under the HAT.
    8. Insert the button into the small square hole in the door. The “flag” should point towards the camera, inside the door.
    9. Press the door into the front of the case.
    10. Wrap the fan wires inside the lid and place the lid on the unit. This will keep it together for testing.
    11. Test!
      • Boot the PI and SSH in. Type log to watch for any errors.
      • If the IP doesn’t appear by the time you SSH in, double check the wiring of the screen. If it still doesn’t work, follow the instructions in “Testing” above, making sure to end the LilL3x process.
      • Wake LilL3x and say “Show me what you see” to test the camera. If the camera doesn’t work when you start up, press on the camera connectors (the black piece of plastic below the lens) to make sure they didn’t come loose, reboot and try again.
    12. Once the unit is working, insert the four pegs into the four holes to secure the door.

    Advanced Configuration

    Setting a device for the USB Camera

    The USB camera gets assigned a device number at startup. If the USB camera is present at boot, this devices seems to be 0.

    You will need to check which device your camera is mapped to by running:

    v4l2-ctl --list-devices

    Look for your camera. There will be a list of /dev/video options, you device number is the number of the first one. Here is my setup:

    You can see the webcam (circled) and it’s list of devices. The first is /dev/video0, so my device number would be 0.

    Downloading Piper Voices

    Piper in an offline text-to-speech engine. It’s a little slower then the other speech engines, but the novel thing about it is that it will generate those voices from a .onnx file on the fly! In addition, one could, in theory, build a voice based on a living sample, but I haven’t tried this yet.

    To download a new voice for Piper:

    • Go to Piper Samples
    • Pick a voice that you like
    • Copy the name that is to the right of the dropdowns. In the below case, it would be “en_GB-semaine-medium“:
    • Type into the terminal:
    python3 -m piper.download_voices --data-dir ~/LilL3x/voices en_GB-semaine-medium
    # replace "en_GB-semaine-medium" with the voice that you want
    • Refresh the configuration webpage and your new voice should be available for selection

  • Creating an AI generated blurb in WordPress using Ollama

    So I have never been much of a writer, so when it came time to write my bio for the website I simply fed my resume into El3ktra (my local Ollama server) and asked her to write it for me. Then I thought, wouldn’t it be fun if the website generated a new bio each page load?

    The first step was to make a text-only version of my resume for my AI to consume while building my bio, which I stored in the root of my directory.

    The next step was adding php code that would talk to my Ollama server and generate a bio as a shortcode function. This can be done by editing the functions.php, found in /var/www/html/wp-content/themes/<your-theme>

    function get_bio() {
        // get the resume text
        $url = "cv.html";
        $resume_txt= file_get_contents($url);
    
        if ($resume_txt!== false) {
    
                $data = [
                    "model" => "llama3.1:8b",
                    "prompt" => "Use Kim's Resume to write a short professional bio in the third person for Kim, less then 200 words, to put on her website.  Here is Kim's resume: " . $resume_txt]
                    "stream" => false
                ];
    
                $ch = curl_init("http://localhost:11434/api/generate");
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
    
                $response = curl_exec($ch);
                curl_close($ch);
                $r = json_decode($response)->response;
                if (str_contains($r, ':')) {
                        return substr(strstr($r, ':'), 2);
                } else {
                        return $r;
                }
    
        } else {
            return "Failed to retrieve the resume.";
        }
    }
    add_shortcode('bio', 'get_bio');

    After that, it was a matter of adding shortcode bio and viola, lovely bios generated every load.

    Gotchas

    The issue that I ran into was that I couldn’t get my AI to not add an introduction for the bio, ie “Here is a bio for Kim: Kim is a programmer etc..." no matter how much I begged her not to in the prompt.

    The following code fixes that in a dirty way by checking to see if there is a colon in the answer, and returning everything after that colon. Yes, dirty but effective.

    if (str_contains($r, ':')) {
                        return substr(strstr($r, ':'), 2);
                } else {
                        return $r;
                }

    Works, but too slow!

    I was excited to see generated bios, but these took several seconds to generate the text and meanwhile my page was just sitting there. My next step was to load the shortcode after the page was done loading. For this we needed JQuery and AJAX.

    The first step was to write the function that would call the shortcode (in the function.php file):

    function handle_delayed_shortcode() {
         echo do_shortcode('[bio]');
         wp_die(); // Always close AJAX connections in WordPress
    }

    In the same file, I registered AJAX actions for both logged-in and anon users:

    add_action('wp_ajax_load_bio', 'handle_delayed_shortcode');
    add_action('wp_ajax_nopriv_load_bio', 'handle_delayed_shortcode');

    Finally, I replaced the shortcode on my site with the following custom HTML:

    <pre class="wp-block-code"><code><div id="delayed-shortcode-container">Loading...</div>
    <script>
    jQuery(document).ready(function($) {
    
        $.ajax({
            url: 'https://el3ktra.net/wp-admin/admin-ajax.php', 
            type: 'POST',
            data: {
                action: 'load_bio'
            },
            success: function(response) {
                $('#delayed-shortcode-container').html(response);
            }
        });
    });
    </script></code></pre>

    Basically it waits for the page to finish loading, then calls the shortcode and writes the result to the delayed-shortcode-container div. Pretty simple.

    Gotchas

    Now one of the first things that I ran into, using the TwentyTwentyFive theme, is that JQuery is not loaded to this theme. I determined that JQuery wasn’t running by adding an alert to the JQuery code. I had to add this to the end of my functions.php file:

    add_action('wp_enqueue_scripts', function() {
    wp_enqueue_script('jquery');
    });

    This allowed JQuery to run on my page.

  • Managed to Secure my Ollama/Whisper Ubuntu Server

    So I am a novice web administrator running my own server, which hosts apache2, ollama, and whisper. I have programs that need to access these outside my local net, and I was as shocked as many are to find that there isn’t a built in way to authenticate Ollama,

    I was able to get this working using Caddy. I am running Ubuntu 24.04.1 LTS, x86_64. Thanks to coolaj86 (link to comment) who got me down the right path, although this solution didn’t work for me (as I am already running an apache2 server and didn’t want to use Caddy as my webserver.)

    First, I installed Caddy:

    curl https://webi.sh/caddy | sh

    Then I created a few API keys (I used a website) and got thier hashes using

    caddy hash-password

    Finally, I created Caddyfile (named exactly that):

    http://myserver.net:2800 {
    handle /* {
    basic_auth {
    email1@gmail.com <hash_1>
    email2@gmail.com <hash_2>
    email3@gmail.com <hash_3>
    }
    reverse_proxy :5000
    }
    }
    http://myserver.net:2900 {
    handle /* {
    basic_auth {
    email1@gmail.com <hash_1>
    email2@gmail.com <hash_2>
    email3@gmail.com <hash_3>
    }
    reverse_proxy :11434
    }
    }

    Started up Caddy:

    caddy run --config ./Caddyfile &

    And ports 2900 and 2800 were no longer accessible without a password. Ports 11343 and 5000 are closed both on my router and ufw and are not publically accessible at all. To access Ollama, I had to go through port 2900 and supply a username (my email) and the api key I generated.

    The next step was to update my code to authenticate, which I haven’t seen spelled out anywhere although it’s pretty obvious. I am using Python.

    Here is what my python Whisper request looks like:
    resp = requests.post(url, files=files, data=data, auth=(email, api))

    And here is what my python Ollama Client call looks like (using Ollama Python):

    self.client=ollama.Client(host=url, auth=(email, api))

    I hope this helps! the next step is obviously to send the requests via https, if anyone has thoughts I’d love to hear them.