ROKR 3D PINBALL
ROKR 3D PINBALL
So, I thought this wouldn’t really be classed as a project, but in the end, I think it was.
First off, you really need to have somewhere specific to mount this. That was the first thing that sprang to mind. Instead of opting for a tabletop as the concept suggests, I instead made a custom shelf out of plywood and mounted it higher than normal to emulate the height it would be at if it were a full table. Second, I had to get power access to allow the game to be powered by a 2A USB-PSU. With those considerations completed, I went on to the build
This is where the kit shines. It has very detailed instructions, and everything is well thought out—including little linked videos to cover the trickier steps. You get custom cutters and a screwdriver, and there are even spares for parts that are likely to break. Chef’s kiss at that part; I absolutely loved the attention to detail there.
In terms of time, I spent around a week and a half on it, working about 2 hours at a time. Any more than that and I lost concentration, and even then, I did sometimes make some mistakes. The main thing to know is that it's quite delicate. It felt like the point at which things would break was if you forced parts out too vigorously from the templates.
Moving onto my main snags: when it came to testing the electronics, everything was fine except for the left-hand LED strip... which happened to be buried quite deep in the kit. I was very annoyed as I had been very careful with everything.
The second snag was that the flipper came loose at the shaft, so I superglued it. In general, some wood glue here and there was also needed. Anyway, I still went ahead and playtested everything. I mounted wooden strips to the plinth perimeters to stop the game moving (using offcuts from the kit) and all worked as it should in terms of other functions, power, sensors, lights, music, etc.
Anyway, the light strip was annoying me, so I contacted Rokr and I must say their customer service was great. I filled out a service form, then a parts form, and a couple of weeks later I received two spares. I bit the bullet and stripped down the kit just enough to get the LED strip two-thirds of the way in. The very last third in my opinion is very inaccessible not noticeable enough to matter.
With that aside, actual playtesting is enjoyable. Now, please don’t be under any illusion: I see this as a taster of a real, full-sized pinball machine, not a replacement. I would imagine it has a more solid feeling than a toy game.
The detail; the ramps, tunnels, poppers, etc. gives it great repeatability. Because the play top is open, the ball does fly out from time to time, which is annoying. Some of the ramps and toys poke up past the top lip, so I don’t think it’s that straightforward to put a lid on it. The main ramp is hard to mount, and when it does, it can send the ball flying. The other ramps are relatively playable and very satisfying when you hit them.
I am really worried about the flipper mechanism. I had to glue the shaft at the beginning, and one is already slightly binding despite being waxed. It just seems like they may not hold up, which leads me to another thought: it's not clear whether this game is meant for serious sessions or merely occasional novelty use.
In conclusion, I would recommend this pinball kit. It’s a reasonable price, a fun build, and gives the user a taster of what a real pinball game could offer. Going forward, I may explore some mods and perhaps source some spare parts if possible (like the flipper mechanism, etc.), but it is definitely part of the collection now. Thanks for reading!
Controls: Use your mouse, touch or track-pad. Left-click to spawn balls.
Objective: Get any marble to touch the green "win" bar.
Level Selection: Choose anywhere from Level 1 to 5. Good luck!
I went down a serious rabbit hole with this game. The project was born from a fascination with old penny arcades. At one point, I actually tried to build the whole affair in real life as a classic physical "5 win" game. Unfortunately, I failed on both counts; with the physical version, I got stuck on the striking mechanism, and with software the gameplay itself just lacked interest.
I even tried my hand at a Godot 3D version, which was equally uninspiring. However, during that entire process, I stumbled across a unique spawn effect using box2d. That effect became the foundation for Ball Logic. I built upon the suite of love.physics and other physical elements that Love2D offers to really make the game play interesting.
I sort of set myself a challenge by creating the title menu first. I set up slots for 5 levels, which gave me a real sense of commitment to finish it. Once I had one level created, it naturally led to the next.
To speed things up, I used AI tools to help code a custom level editor. From there, I manually placed the items around to play-test what worked best. Once I was happy with a layout, I exported the coordinates and saved them to lock in the level design.
In terms of visuals, I had a few influences in mind, but my main philosophy was that ball games don’t need heavy instructions. Like snooker or pinball, you just let the ball do its thing and watch the result. Because of that, I decided to completely remove any UI clutter. I’ve used high-contrast black and vibrant, colourful neon aesthetics in the past and always loved that look.
I wanted the game to feel a bit alien, especially the spawn triangle and the strange winning sequence. I wanted the player to feel immersed.
For the audio, I played around with recording actual marble rolling sounds, but found it way too distracting, so I opted for subtle sound effects instead. The final piece of the puzzle was the music, which was something I was really looking forward to. Over a few evenings, I created custom tracks for each level using Ableton Live Suite to achieve a deep ambient feel.
Note: Audio public domain asset pack available on itch.io and opengameart.org!
Since I didn’t want to use timers, I used the music to dictate the pacing and mood:
Level 4: Features an urgent, pulsing track to indicate the need to get the level completed, adding a lot of midway tension.
Level 5: Releases that tension with a much more relaxed, ambient atmosphere.
Unfortunately, my grand vision for Level 5 didn't quite pan out perfectly on the web. While the level is totally playable, it features a smooth double loop that works flawlessly on the desktop version, but struggles slightly on the Love.js web build.
Update: For a few reasons, I’ve managed to get the game hosted on GitHub as well (linked via the GIF on the main page). Surprisingly, the game works really well on mobile, so I’ve integrated both mobile-friendly and desktop features for full-screen play on all devices!
Reusable E-Ink Vehicle Dashboard
Goal: A low-power, sun-visor-mounted display to provide critical reminders (e.g., London bridge weight limits) for driving larger vehicles, using a Raspberry Pi Zero 2 and an E-Ink (Electronic Paper) display.
Controller: Raspberry Pi Zero 2 W (running 64-bit Raspberry Pi OS Lite for memory efficiency).
Display: E-Ink HAT (chosen for its bi stable nature, the message remains visible even after power is disconnected).
Assembly: Hand-soldered headers onto the Pi Zero to interface with the display HAT.
Form Factor: Compact enough to fit directly over the Pi, making it perfect for visor mounting.
Logic: Custom Python script utilizing specific e-Paper drivers.
Interface: Bluetooth Serial communication.
Mobile App: Integrated with the Kai Morich Bluetooth Serial app to send text updates from a smartphone.
Setup: Managed via SSH for initial configuration and driver installation.
Pi Zero 2 W with Raspberry Pi OS Lite (64-bit)
I already had a Male 40-pin GPIO Header.
Waveshare 2.13" ePaper HAT+ for Raspberry Pi.
inUpdate: I have since moved the display to the lower right near the cup holder. I realised this thing on the sun visor is a pretty nasty potential hazard in a bump. Happy so far, but I will need to use it for six months to a year before I can call whether this has benefited me. Thanks!
Update 2: The Pi failed on a recent turn on unfortunately, the SD tray had disloged causing a no boot. I got things up and running with my old model 3B+ which was as simple an SD swap. As the 3B+ is a bit bulky I opted to attach the screen off site for updates and simply blue tacted the screen in place.
Driver Management: Located the script within the driver’s demo directory to ensure all dependencies and libraries were correctly mapped.
Memory Optimization: Using the headless 64-bit OS allowed the Pi Zero to handle updates smoothly without the overhead of a desktop environment.
The "Overkill" Factor: While a simple printed card could work, this digital version allows for instant updates and "future-proofing" for other driving alerts.
Interface Stability: Currently paired specifically to a work phone; I plan to develop a more robust multi-device input system.
User Experience: Moving away from SSH for minor tweaks by building a dedicated input UI or a more flexible Bluetooth handshake.
Expansion: Adding more "snippets" of info beyond just bridge weight limits (e.g., parking timers or height clearances).
Club Sorted is a DJ and crowd simulation game born from a long-time love of electronic music and the ritual of clubbing.
Developed using Love2D on a Chromebook, the project maintains a lean footprint by using mono OGG files with reduced bitrates. Inspired by how well Love2D runs on limited resources like the Raspberry Pi 4, I chose this efficient framework to ensure the game performed well within a web-based player.
The visual style draws inspiration from classic isometric games. The crowd agents utilize the Universal LPC spritesheet system, providing comprehensive movement and animation. To implement these, I developed a custom tool to identify and extract the specific animations that best fit the game's clubbing theme.
The UI was influenced by Home Assistant dashboards, utilizing meters and gauges for guest satisfaction and "Vibe" to keep players engaged.
Vibe Tracking: Rather than complex agent-to-agent AI, success is measured by the player’s timing and rhythmic input to keep the Vibe Meter in the green.
Session Length: A full mixing session takes about 20 minutes. Tracks were edited to be concise to keep the gameplay pace active.
Technical Hurdles: Due to limitations with Web Audio effects, I implemented a "bass-kill" EQ system by creating separate high-pass versions of every track in Audacity and rigging the logic to swap between files in real-time.
The playlist includes my own original tracks produced on an Elektron Syntakt Groovebox. The title screen is a "photobashed" composition starting from a Google Street View image of Shoreditch. I processed the image with AI for a pixel-art aesthetic, converted it to a night scene, and separated it into four layers in Photopea to create a multiplane parallax composition animated within Love2D.
Elektron Syntakt I used to produce one of the tracks (981)
LPC Sprite sheet
Some unused concpet art for logo and banner
Title Screen ref Image (@2026 Google)
Filtered through Google AI
Layered in PhotoPea sections cut out
Added train element and extra cut out.
Moving the game from a Chromebook development environment to a web-ready state presented several porting hurdles. Using AI tools to navigate Linux commands and compilation headaches proved invaluable.
Key fixes included:
Exit Logic: Web-based Love2D games do not support a standard "Exit" command; I repurposed the logic to return players to the main title screen instead.
Scaling & Resolution: The index.html wrapper presented issues with scaling and full-screen alignment. While not perfect, I optimized the CSS to fit the standard skill set for web deployment.
Loading: The game begins with a brief black screen while the assets load. At only 36MB, the initial wait is minimal and keeps the experience snappy.
Cross-Browser Oddities: I noted some minor graphical glitches in Chrome for Windows that don’t appear on ChromeOS—a reminder that browser-based gaming always brings unique bugs to surface over time.
Note: For the best experience, please play in Full Screen and use Headphones.
Your goal is to keep the party going and the Vibe Meter in the green. Start by previewing the DJ tracklist on the title screen to get a feel for the set.
The Crossfader: Use your mouse to slide the fader between Deck A (Left) and Deck B (Right).
Cueing Tracks: When the fader is pushed fully to one side, a CUE button will appear for the opposite (silent) deck.
The Transition: Once a track is cued, click NEXT TRACK to load it, then slowly slide the fader across to transition the audio.
Pacing: Don’t rush! The crowd penalizes "Fader Slamming" or switching tracks before a song is at least 80% complete. Keep your movements smooth.
FX & Atmosphere: Click the icons for Lazers, Beams, or Smoke. These triggers last 15 seconds and provide a significant boost to the Vibe.
Whiskey Meter: Keeping the DJ hydrated gives your vibe a small passive boost. If the meter runs low, click the + button to refill your drink.
Overview on how to play
The DJ console was developed separately then added in.
The 'sim' elements added in to support the core DJ function.
Further unused assets. The cross-fader was 'drawn' in.
Finally, to play for free in the browser, please click in the button above. Enjoy!
The game evolved from my last project, Phantom Leap (see My Game). While playing that, I thought, "This could be a beanstalk!" and the idea progressed from there. The original Phantom Leap logic wouldn't quite work because that was an infinite jumper, whereas this needed to be more structured to capture the complete story of Jack and the Beanstalk.
Framework: Developed in LÖVE (Love2D). I chose this because I know from experience it works well on RetroPie.
Hardware: I developed this on my Chromebook. It’s definitely the limit for the type of graphical workflow I needed, but it got the job done.
Prototyping: I started with a "Pong-style" block to test the mechanics. Once I saw they worked, I began iterating and letting the ideas evolve.
Testing mechanics
Sprite sheet of main charater (isaiah658 OpenGameArt.org)
Tiled spritesheet used for drawn textures like the ground and the beanstalk
Example of an animated sprite sheet used (OpenGameArt.org)
I leaned heavily on OpenGameArt.org for the sprites and music. I also used AI-generated pixel art for the backgrounds, which worked great.
For smaller assets like beans and leaves, I used Photopea to remove backgrounds, recolor, and cut out elements. Without that tool, the assets wouldn't have come together.
I toyed with AI music, but it sounded awful for the 8-bit/16-bit aesthetic I wanted. I’m aiming for a 16-bit nostalgia burst, about 10-20 minutes of gameplay with bright, pleasing sprites and catchy tunes (mostly by MintoDog). I actually omitted sound effects because the music is such a big part of the experience.
All assets are CC0
A python script was developed to find co-ordinates of game interation areas
Photopea is a powerful online image editor
It got a little frustrating with the constant iterations and tweaking, but I finally got the story, difficulty, and visuals to a place where I’m happy.
I originally tried to share the game on GitHub via a recompilled .js format but the scaling was inconsistent and buggy. I’ve since scrapped that and instead compiled a .exe for Windows and a Linux version, both shared via a zip file on cloud storage. This also acts as my backup.
I’m sorry I can’t share the actual game just yet. My next goal is to port it to my RetroPie. I’ve done this before, so I just need to add the key mapping and a few other tweaks. That will be another little weekend project, so watch this space!
The core gameplay revolves around a vertical climb where the narrative unfolds through various cutscenes and a day-to-night transition. To keep the experience engaging, players encounter different enemies on each level and can collect Golden Eggs for extra lives. The game follows a distinct structural rhythm: an upward ascent (complete with hud level indicator), followed by a timed downward descent and narrative interludes. Level 3 introduces a challenging boss encounter to heighten the retro-inspired appeal. The entire experience is framed by a polished title screen and closing credits, complemented by custom-designed box art to complete the package.
After another four evenings of intensive work, I’ve successfully ported the game to RetroPie.
The Challenges:
Resolution & Draw Control: I ran into several hurdles with screen scaling and rendering, but these have finally been ironed out.The Process: It was a fairly laborious experience—specifically, having to bring in the large joystick panel for debugging before the game was fully stable.
Final Polish:
Audio Balance: The cutscene music is currently a bit quiet compared to the main levels. This is a simple gain adjustment on that specific MP3 file, which I’ll handle down the road.
Timeline: The entire development process for the PC and RetroPie versions took approximately three weeks of evenings and weekends. I’m incredibly relieved to have this milestone behind me!
One of the many errors during develoment.
Finally getting the game on the 'home' system.
The core of the game is driven by a State Machine managed within main.lua.
Sequential Loading: I utilized a sequence table to define the linear flow of the game, ensuring a smooth transition between scenes.
Dynamic Requirement: The loadLevel function uses package.loaded[name] = nil followed by require. This "hot-reloading" technique ensures that every time a level starts, its global and local variables are reset to their initial state, preventing state leakage.
Delegation: I designed main.lua to act as a proxy, forwarding standard Love2D callbacks (update, draw, keypressed) directly to the currently active level module.
I implemented a custom AABB (Axis-Aligned Bounding Box) collision system to keep the game lightweight, bypassing the need for a heavy physics library like Box2D.
Kinematics: In the level logic, I calculated vertical movement using constant GRAVITY and JUMP_POWER applied to a delta-Y (dy) variable.
One-Way Platforms: I scripted the collision logic to check if player.dy >= 0, which allows the character to jump through the bottom of platforms but land securely on top of them.
Dynamic Scroll: Rather than using a traditional camera, I shifted the y position of all game objects (platforms, hazards, house) based on the player's height to create a vertical climbing/descending effect.
Trunk Generation: The beanstalk is rendered procedurally in "slices" using a math.sin wave and a multi-strand system. By sorting these strands by a depth value, I created a pseudo-3D rotating vine effect.
Sprite Sheet Handling: I used love.graphics.newQuad to slice the player sprite sheet into distinct animation states, such as idle, walk, jump, and hit.
Particle Systems: I built a manual particle loop for the house smoke, managing the life-cycle and movement of individual smoke circles within a table for a more organic feel.
Level
Technical Highlight
Intro
Implemented Easing Functions (easeOut) to create smooth camera interpolation from the ground to the castle.
Level 1
Developed a "Taper" system where the beanstalk physically thins as the score approaches the goalScore.
Level 1 Cut
Created a Stealth/Sense mechanic using a custom UI element and timer to determine the Giant's detection range.
Level 3b
Built an AI Stalking Hand that uses linear interpolation (Lerp) to track the player's X-coordinate with a slight delay.
Memory Management: By clearing package.loaded, I prevented memory leaks and ensured that level data didn't accumulate over long play sessions.
Visual Polish: I implemented "Invincibility Frames" using a flashing alpha effect (math.floor(flashTimer * 15) % 2 == 0) to provide clear visual feedback to the player.
Coordinate Mapping: I utilized a map() helper function to effectively translate values across different ranges, such as converting an object's depth into brightness or scale.
So the final piece in the jigsaw you can now play online via itch.io :-)
The "Smart Kitchen Assistant"
I wanted to share a weekend project born from a specific household frustration: the ever-disappearing shopping list. Often, while ordering groceries online or working in the kitchen, I’d forget to note down missing items. I needed a way for anyone in the house to quickly log a need or raise an issue (like product quality).
While commercial apps exist, I wanted to put my spare Raspberry Pi 4 and Ubuntu dev server to good use.
Hardware: Raspberry Pi 4, I2C Screen, WebCam for microphone, and a physical button.
Case/Build: Recycled materials, including old pencils for standoffs, clear plastic, and hot glue.
Voice Recognition: Vosk (running via Python).
Backend: SQLite for data storage.
Web Interface: A Flask app to receive the SQL data and present an internal web page for list management, hosted on existing Ubuntu home server.
Trigger: Press the physical button.
Input: Speak the item name or a short phrase.
Feedback: The I2C screen displays the interpreted text.
Confirmation: Single Tap: Save and send the data to the server. Long Press: Discard and start over.
Review: The Flask app displays the list with logic to manage shopping deadlines and completion status.
The system has survived one full shopping cycle and has been helpful to date. The real test will be long-term adoption by the rest of the household! I initially considered a barcode scanner for a "tactile" experience (and even purchased one), so I may still integrate that as a secondary input method.
Update: I've repurposed an old enclosure to give the device a cleaner, more professional "control box" look. The previous prototype looked a bit too janky in the boiler cupboard, and honestly, it was an embarrassment when people saw it. Because this device is something we've grown to rely on, I decided it deserved a more permanent home.
I encountered a few minor issues along the way: the switch was surprisingly tricky to mount, and as you might be able to see, the screen suffered some pressure damage while I was wrangling the wiring into the new enclosure. Aside from that, we are now all set!
The initial incarnation was a Pi Zero 2 powered 'Newsfeeder'."
Early password idea and breadboard/code devleopment
Build in full swing
Final look of the product. Not pretty but functional.
Front end flask web page hosted in my Ubuntu server
The arcade build is alive and well, but I couldn’t leave it alone. Feeling pretty pleased with the RetroPie setup, I decided on a "quick" project: porting my own game, Phantom Leap, over to the cabinet. Five evenings later, I’ve hit the 99% mark...
I started with what I thought was an easy win, using Chromium or Electron to run the web version natively. Big mistake. While that works fine on a beefy Linux desktop, trying to run it on a Pi 4, even one as capable as this, is like watching a snail race. It was bloated and sluggish. Things got messy fast because I was working via SSH and "vibe coding" commands directly into the terminal, and before I knew it, I was accidentally copying files into places they didn't belong.
By mid-week, I hit the brakes. I picked up a used Pi4 to act as a dedicated sandbox, mirroring my setup so I could break things in a safe environment without nuking my actual garage build. I briefly flirted with Godot, but it was unintuitive for my workflow and very simple game. That’s when Gemini recommended Love2D. It was a game-changer, as it fit my environment much better and allowed me to push code ideas back and forth with the AI to rebuild the logic from my original main.js.
The difference was night and day. When I first tested the Love2D port, I couldn’t believe the speed. It showed me exactly how inefficient a browser engine is on a small system like the Pi, whereas Love2D is lean, mean, and perfectly optimized. This is where the real iterations began, building back the elements of the script with me acting as the designer and the AI as the lead engineer.
By the second day of sandbox coding, I was ready to move the game to the garage rig. I initially planned to copy the code over, but after some folder-path headaches with the box art, I took the easy route and just swapped the sandbox SD card into the garage Pi.
I hit one final hurdle I couldn't quite get around, which was a slight glitch with the controllers. Currently, Start and Select trigger the jump instead of the A and B buttons. This means the hand placement is a little stretched while playing, but for the short, snappy sessions that Phantom Leap is designed for, it’s totally workable.
In the end after breaking other elements of the code to try and f fix this, I made a call to stick with this 'safe', working code. The game now runs beautifully in the "Ports" section. While it isn't strictly native to RetroPie, it’s alive in the garage and ready to share with friends and family. If you prefer the original experience, the JS version is still available to play in the "My Games" section of my site.
Enjoy :-)
*Updated*
I spent a few hours tightening up the game to get it feeling "solid." I fixed the button mapping and added a "hold to escape" feature for a cleaner exit. Pause is now gone as result but I'm totally fine with that in this type of game. On the gameplay side, I slowed down the coffins to lengthen gameplay and implemented a three-tier scoring system where 300+ souls triggers the top-tier message. I also shrunk the ghoul sprites and stopped soul-spamming to make the overall experience much more balanced.
The development process was much smoother this time since I moved away from "cowboy coding" via SSH /Pi and set up a proper Love2D environment on my Linux machine. The only real headache was the final deployment; because of the custom paths in the port setup, getting the files onto the Raspberry Pi is still a bit of a manual chore. It was a massive amount of work for a one-level project, but despite the "one and done" nature of it, porting this was a total blast.
Project Update: "Phantom Leap" Intro Implementation
In the great arcade tradition, I’ve added a proper cinematic intro sequence to Phantom Leap. This was built as a standalone element before being integrated into the main game. The sequence features:
Visuals: AI-generated artwork (via Gemini) for the splash screen.
Typography: Google’s "Creepster" font, rendered dynamically via Lua.
Audio: CC0-licensed music sourced from OpenGameArt.org.
Once the intro sequence was perfected, I merged it with the primary main.lua script. Using a State Machine approach, I successfully tied the 15-second "attract mode" to the core gameplay.
Development Workflow:
I’ve found it’s much more efficient to develop on my Linux environment and then use SSH/SCP to push the updated scripts and assets to the RetroPie. This avoids manual file handling and speeds up testing.
Known Issues:
I’ve noticed a slight flicker on the title text during the intro when running on the Pi hardware. This artifact doesn't appear on my Linux desktop, so it may be related to the Pi's performance or the specific environment. However, the core game engine remains rock solid, and all ROMs are running great!
Welcome to another project and build!
This one took about five evenings of work. The main spark for the build was having an unused Raspberry Pi 4 gathering dust. In my opinion, the Pi 4 is a very capable and established platform (it was previously used as a HamClock). Having cleared some space in the garage and finding a few spare plywood panels, I decided to see what I could make with them.
The idea of a wall-mounted arcade came to mind. I was already familiar with RetroPie, which became the basis of the project. I didn't want this in the house—it’s its own thing. I wanted proper joysticks and a nice monitor, but it needed to be built as cheaply as possible using tools and equipment I mostly had on hand.
I managed to source most of the build from my spare parts bin:
The Brain: Raspberry Pi 4 with a heatsink case.
Power: Standard power supply, SD card, and a USB drive.
The Screen: A new old stock monitor with a ratio very close to 4:3, which is perfect for retro games.
Connectivity: I used a VGA to mini-HDMI adapter to link the screen to the Pi.
Controls: A "Zero Delay Encoder" joystick kit.
Audio: Cheap PC speakers I already owned.
Finishing: A label printer (clear on black labels) and some leftover varnish.
Total Project Spend: The joystick kit, VGA adapter and monitor was the main purchase, coming in at around £60 ($76 USD / €72 EUR).
Things got fun but challenging here. I started by mounting the backboard to the garage wall. To hide the cables, I stood the board off the wall using spacers made from an old plastic rod I had. I drilled a large hole for the VGA and power cables, took apart the monitor stand, and used that for a clean mount for the 18-inch screen.
Next came the joystick board—a classic 2-player SNES-type setup. I used the "Slagcoin" template online for the layout. I went with a classic Sega-style button curve but simplified it to the 6-button RetroPie/SNES layout (A, B, X, Y, L, R) plus Start and Select.
I cobbled together a hinge mechanism using an old hinge, some stay wires, and rubber tiles at the contact points. This created a stable playtop that can be lifted to access the Pi and wiring underneath, just like lifting the hood of a car.
Drilling the holes with an old spade bit and hole saw was a messy job. The joysticks took some faffing because I didn't have carriage bolts. I realized the 18mm plywood was strong enough on its own, but to get the joystick shaft to protrude enough, I had to spend some messy time with a chisel to recess the mounting plate. Being a "hobbyist" woodworker, the underside is a bit hairy! I gave the whole thing a quickvarnish job just to keep the garage dirt out of the grain.
Once everything was wired up with hot glue and washers, it fired up perfectly. However, I ran into a major headache: the joysticks kept flipping directions inside the games. After 2 to 4 hours of calibration and searching, I realized I hadn't mirrored the physical orientation of the Player 2 joystick. This was upsetting the software. After unscrewing it and flipping the stick around, everything worked wonders!
The finishing touches were the labels and testing the games. This is one of those classic projects that seems easy and probably would be if you just used a TV and an Xbox controller, but building it from scratch is challenging and fun. It’s amazing that you can get the same gaming experience on a handheld for half the price, but you lose the joy of building your own. Now, it's just about the short blasts of nostalgia -hearing the music, feeling the click of the sticks, and seeing those pixels.
I may add a surround or a fascia to the monitor later to give it more of an arcade feel, but for now, I’m going to stick with things as they are. Below some AI enhanced pics to visulize different layouts.
Thanks for taking the time to read!
This project has been on my mind for a while. The primary goal was to find a modern way to host and share approximately 90GB of photos. While that volume might not seem massive by today's standards, it consists of tens of thousands of lower-resolution images taken 10–15 years ago, before I fully transitioned to Google Photos. I also had a sizable backlog of my own music tracks and files that I wanted to share seamlessly between computers as an alternative to Google Drive.
After researching trends on sites like XDA, I decided to use a mini PC rather than a Raspberry Pi 5. The value and perfomacne seemed good. I opted for a refurbished Lenovo ThinkCentre with the following specs:
CPU: Intel i5
RAM: 16GB
Storage: 120GB SSD (Internal) + My pre owned 320GB HDD (External USB) - Old Pics
Cost: €125-$145 USD (Early 2026) + €5-$7 USD for an HDMI adapter.
My plan was to wipe Windows 11 and install Ubuntu Server (headless) via a USB stick. Once the OS was running, I installed CasaOS. I chose CasaOS for its user-friendly interface and its "App Store" feel, which is meant to make managing Docker containers much simpler.
The setup process had a few hurdles, mostly getting the PC to boot from the USB but once Ubuntu was live, the installation of CasaOS via a single command was nearly instant.
The real fun began with the apps:
Immich: Initially, I installed an outdated version. After some troubleshooting, I switched to the "BigBear" version, which worked better with the mobile app. It gives the old pics a Google Photos experience.
Jellyfin: While the basic file explorer in CasaOS was lacking ie no video thumbnails, I eventually landed on Jellyfin for media. It reminds me of Kodi and is an excellent way to browse my archive of music and video projects. I made these web pages into 'apps' via Chrome PWA 'save page as app' option on my devices.
While internal expansion is a bit limited in my mini PC I plan to add extra high-quality external HDD storage soon. Long-term, I want to explore running Local LLMs (Large Language Models) with over 120B parameters to match the quality of current online AI bots without the privacy concerns. This would be a modern server with Unified Memory. Watch this space.
Small footprint mini PC
Ubuntu Server
I wanted to share an interest I've cultivated, particularly during the dark winter months. It's the simple pleasure of observing the night sky and taking joy in using our old Orion "Maksutov-Cassegrain" reflecting telescope.
With its 90mm diameter and 1250mm focal length, it allows us to glimpse Saturn, Jupiter, or even occasionally catch a faint image of Mars or Venus. Using a star-mapping app makes it possible to identify celestial objects with some accuracy.
What started off as a novelty has actually become a source of meditation and inspiration. Often alone in the late hours on a clear night simply observing from the garden, it's fascinating to behold the vastness of space. I first make observations just by eye, and then seek more clarity with the telescope.
While the actual image is quite small, it offers an extra insight into some of the planets, whether it's the rings of Saturn or the unique atmosphere of Jupiter.
Personal Music Player & Visualizer
I decided to create my own music player as a proof-of-concept project some time ago, aiming for a "vibe code" web experiment. Back then, it was just a rough idea and wasn't fully developed. Fast forward a year or so, and I had decided to no longer use third-party platforms to host my original music tracks. I returned to my player and began developing it further.
After literally hundreds of iterations and several months of work, I improved everything from the look, feel, and performance of the player and its visual interface. Along the way, I realized I wanted to focus on showcasing only my "top tier" tracks while still having a need to neatly present my back catalog. After much thought, I settled on a simple, one-drive folder-based system for my back catalog, which works as a simple offline-only player instead of relying on continuous public-facing hosting.
The end result is a project I use quite often as my primary personal music player when on the go, whether streaming in a vehicle using Bluetooth or listening via headphones. I would recommend this project to anyone whose hobby involves listening to and creating music.
The player utilizes MP3 format for all tracks. uced storage demands,
Please take a look at the project on GitHub!
News Image AutoPrompt
I've been playing around with text-to-image AI for a while, and recently, Google Gemini and other services have started offering APIs to do this programmatically. Initially, I got my flask program to work quite well, but it soon started running into API rate limits. Thinking of a fun use for the system, I liked the idea of combining a random news story of the day with a text-to-text AI generation to create a prompt. This prompt could then be manually entered into Google Gemini to produce an image.
While this sounds simple, if one were to simply paste in a news title and append 'image,' the AI has to interpret this first and will often provide a less than pleasing result or include unwanted text. The text-to-text API provides a cleaned-up prompt that normally gives a more pleasing illustration style. For sensitive words, it sometimes defaults to a safe-mode image despite filtering being in place, but this is a work in progress. To facilitate this, an API key is needed for the Python Flask program, but the text-to-image work is done within the Gemini app itself and can then be downloaded. The final layer was to incorporate the original headline and news source to provide context to the generated
Technical Details and Troubleshooting
The whole thing runs as a single script, with some added .sh and .desktop shell scripts to quickly launch it from a desktop. It actually took a lot of tinkering to get the terminal working properly. My Penguin terminal would refuse to execute the .sh start script, so I had to switch to a more lightweight terminal.
This has been a fun side project to produce some interesting and random content from a news source. Enjoy!
This is a fun project to build a tool that generates prompts for AI image generators using real-time news headlines. The application will fetch a random news story, filter out sensitive content, generate a creative prompt, and finally, allow the user to overlay the original headline onto a generated image.
First, a developer needs to set up their project environment. This involves installing the necessary Python libraries to handle web requests, data parsing, and image manipulation.
Create a Project Folder: Make a new directory for the project.
Set Up a Virtual Environment: This is a best practice to manage project dependencies.
Install Libraries: Using the terminal, install Flask (for the web app), Pillow (for image editing), feedparser (for RSS feeds), and BeautifulSoup4 (to clean HTML from summaries).
Bash
pip install Flask Pillow feedparser beautifulsoup4
The main logic will be placed in a single Python file (e.g., app.py). It needs three key functions.
1. Fetching a News Story
This function will:
Use a list of RSS feeds from various news sources.
Randomly select a feed and an article.
Extract the headline and a clean summary.
Implement a keyword filter to skip headlines with sensitive words like "war" or "death" to avoid inappropriate AI prompts.
2. Generating the AI Prompt
This function will:
Take the cleaned news summary as input.
Format it into a structured prompt using prompt engineering techniques. This can include adding phrases like "cinematic fine art photo" or "a poetic abstraction" to guide the AI's style.
3. Overlaying Text on an Image
This function will:
Accept a user-uploaded image file.
Use the Pillow library to draw a semi-transparent bar and place the original headline and source text on top of the image.
The text should be formatted nicely (e.g., centered, italic, white color) to create a finished look.
The final step is to create a simple web application using Flask to tie the functions together and provide a user interface. This involves:
Setting up Flask routes (@app.route) for the home page, a route to generate a new prompt, and a route to handle the image upload.
Using a basic HTML template to display the generated prompt and an upload form.
Using Flask's built-in functions like request to handle file uploads and send_file to send the final image back to the user's browser.
By completing these steps, someone can build a similar project that showcases their skills in web development, API interaction, and data processing.
VCV Rack
I came up with my own theory about modern minimal techno while trying to distill the sound into a single track in Ableton. I soon realized why the result didn't resonate with me. While I enjoy listening to other genres of electronic music on a daily basis, I don't typically listen to 3 or 4 minute minimal techno tracks instead it would be a set at a festival, a party, or listening to a live recorded session. For this reason, I thought it would be better to approach the genre in a different way.
I'm a big fan of the concept of generative synthesis in general, and I really enjoy videos of modular racks, especially techno ones. I came across VCV Rack, which has a completely free edition and is open-source.
This is where I found a very nice-sounding patch that was labeled "minimal techno," but in my opinion, it was more on the progressive techno side. Still, it captured more of what I was looking for, and in making the video, I hoped to capture a sort of live performance. I hoped this would give context to the track and add a story and meaning to what you're hearing.
I familiarized myself with a few functions and had to figure out how to record the whole thing, do some light EQ, and then re-sync it in editing software. I also had to figure out OBS (to capture screens), which is a great tool. All in all, it was a fun experience.
I'd like to figure out another patch that sounds a bit more metallic and Germanic, perhaps with more elements to bring in. Oh and the whole thing gives me Reason vibes (Propellerhead Software)ware.
So, that's enough rambling, please take a listen!
Satellite VHF Downlinks
Quadrifilar Helix Antenna
While constructing it, I joked that it could at least double as a good umbrella stand if it didn’t work! Unfortunately, the finished project produced slightly worse images than my inexpensive V-dipole. I’ve decided to repurpose it to capture SSTV signals when they’re available from the ISS and similar sources. Not every project is a win!
Preliminary tests showed that the antenna captures crystal-clear FM radio stations, and I even picked up some distant beeping from nearby polar-orbiting satellites. So, it might still prove useful. The QHA appeared most resonant at 139 MHz on my nano VNA, which is slightly off. Interestingly, I discovered that the design creates an electrical short between the two helix elements —a very intriguing aspect of the design.
As a side note, I've learned that the most detailed weather images are from the Russian Meteor M2-3 and M2-4 satellites.
**Update**
I recently made some updates to this project after freeing up a couple of Raspberry Pi 3s by shutting down a few other automations. Lately, weather satellite imagery has captured my attention, and I discovered there's already a project to automate the capture, decoding, and presentation of weather imagery.
In theory, setting this up should have been straightforward, but it ended up taking me nearly two days of back-and-forth troubleshooting to install the chosen software. I decided to use Raspberry NOAA V2. Initially, I started on its GitHub page and was led to an image file hosted elsewhere. I focused on this pre-built image first, as the steps to clone from GitHub seemed complicated and I wasn't confident in following that route.
# There is a Discord server and a Facebook group available to assist users. However, I somehow managed to let my Discord link expire or forgot to save the page originally. I also don’t use Facebook, but it’s worth keeping in mind as a resource for others
While the image loaded, I was still in the dark regarding the setup of the program itself. Although AI suggested the correct steps, the program simply wouldn’t update the base station settings correctly. It did decode passes, but they weren’t configured properly. I probably reinstalled it three times and even tried the GitHub cloning approach. I looked into alternatives, but just before giving up, I stumbled across an official Raspberry Pi guide.
By following this guide (without building the cool QFH antenna and using an older Raspberry Pi 3B+), I was able to get the program up and running in about an hour. From the images I captured, I noticed my bandwidth for good decoded signals was quite narrow—likely due to my loft setup—but at least it covers the UK.
There are a few small tweaks I plan to make. One major improvement might be to try building a QFH antenna since they’re quite expensive to purchase. For now, I’ll stick to high-elevation passes, as they provide the best coverage.
Enjoy the results!
This interest probably stemmed from watching the excellent YouTube channel Save It For Parts. While the channel often focuses on some of the tricky (to me) L-band frequencies, I found that it also covers downlink on the VHF frequency. After a few simple Google searches, I came across a very up-to-date article that proved extremely helpful.
Before discovering that guide, I spent a day or two trying out older software, However, the newer guide made a big difference. I was surprised to learn that with just two pieces of software and an app on my phone, I could obtain some halfway decent images. For instance, I’ve noticed specialized NOAA (National Oceanic and Atmospheric Administration) filters with built-in amplifiers that might prove useful. For now, though, I’m testing what results I can achieve with my current setup.
Software: SDR++, SatDump, and the Look4Sat app
Hardware: An RTL-SDR4 connected to my Windows 11 PC using 5 meters of RG213 coaxial cable. The cable runs to a pre-built V-dipole antenna in the loft-mounted on a horizontal plane facing due north, as outlined in the guide.
The main difference from the guide is that I opted to purchase a pre-built antenna rather than build one myself.
Moving forward, I’ll continue testing and may consider adding the NOAA amplifier and filter. SDR++ as recommended in the guide includes a dedicated NOAA IF filter, which my current Airspy version lacked.
Eventually, I’d like to automate the process for local copies, I will update here if so.
This animation shows different types of space debris objects and different debris sizes in orbit around Earth. ©ESA
I used this guide but with an Ebay prebuilt V-dipole
Android app used for tracking NOAA
V-dipole loft antenna
Developing a Cross Platform Game 2.0 2024 🎃👻
Encouraged by the positive feedback from friends and family on the first iteration of my platform game, I decided to take the project to the next level.
Initially, I wanted the theme to be personal, perhaps related to the type of work I do. However, with Halloween approaching and so many great assets available for that theme, I decided to shift in that direction.
At first, I envisioned a pumpkin-head sprite jumping on bones (hence the name "SpriteBone"), but that didn’t quite look right. So, I switched to coffins instead. To add a bit of flair, I included a ghost or soul rising from the coffin as the sprite jumps up and down. I was mindful not to make it too similar to classic 8-bit games, and I think I managed to avoid that.
I envisioned a Halloween-themed game with enhanced art, animation, effects, and music. Since the core mechanics and game play were fairly developed, my main task was identifying which assets I needed and where to place them. Initially, I thought it would be a simple matter of overlaying assets, but it took more iterations and tweaks than I anticipated. However, I leveraged what I had learned to streamline the process.
Most of the assets were sourced from opengameart.org, all under the CC0 license to avoid any attribution issues. I found the pumpkin sprite, music, sound effects, and ghost there. The coffin was generated using Craiyon ai, while I created the starry background, text, and restart button myself. I opted for a pixel art style, given its nostalgic feel and suitability for the game's aesthetic. Although I sought crisp-looking sprites, they were somewhat hard to find. I did, however, come across a large collection of 8-bit style music and art, which fit the theme well.
I’ll provide a screenshot of the assets as a snapshot of this stage. It took a whole weekend of iterations and tweaks with ChatGPT to refine everything. This time, I developed the game offline on my Chromebook, using a text editor and testing it by running the index.html file locally before uploading to GitHub. This time, I achieved a single deployment that worked almost immediately. Only the music needed a few extra page refreshes to load properly.
After finishing the game's design over the weekend, I spent two evenings packaging it as efficiently as I could. I explored Progressive Web Apps (PWA), a lightweight Google innovation that allows the game to work offline and function like a native app. While it’s been hit-or-miss on some devices, it works smoothly on my Android phone.
I also tackled a steep learning curve with preview images, particularly for platforms like WhatsApp. I found that using images of a certain resolution or higher solved the issue. Now, I’m nearly ready for deployment.
Thank you, and I hope you enjoy the game!
Game assets
Preview image (Craiyon.com)
Mobile screenshots - Progressive Web Application
Developing a Cross Platform Game 2024
The premise behind developing my own game started with an interest in watching others develop theirs. I've watched people like Ben Heck write code in C from scratch and draw out assets one pixel at a time. This approach seems to be the ultimate in 2D game production. While I have watched one or two other developers as well, Ben stands out to me.
Recently, I became fascinated by the story of Ryan Evan Kadrian. Watching an early YouTube documentary about his life and struggles he developed a video game, seemingly from scratch, called 'Platform Masters' which looks like a lot of fun. It seems to be a cross between Sonic the Hedgehog and Lemmings.
This is what encouraged me, one Saturday afternoon, to give it a try myself—with the help of ChatGPT.
The whole endeavour took around 7 hours from start to finish. It wasn't just about producing a single prompt but involved an iterative process. (see Initial development image). I'm going to share the code with a description so we can dissect what's happening. I already knew it was going to be a JavaScript based game, which reminded me of the early Flash games. When I looked into how to host the game on my site, ChatGPT recommended GitHub to host the necessary files and serve the page and I already had an account.
I hit a lot of dead ends along the way. For instance, I was aiming for something like Nick's 'Platform Masters' but ended up with something completely different. Of course, it has similarities with other platform games, and I'm sure I could find a similar game if I searched.
Some of the hurdles during development included trying to prevent long button presses to limit jumps. I couldn't get the code generated to sort this out without dozens of attempts. The block logic was tricky to pin down in terms of game-over conditions. I also had another hurdle with dynamic canvas resizing. I got it working and it looked good, but it broke the block generation mechanics, so I couldn't implement it. Instead, I set the resolution to match my own mobile device.
The touch controls on mobile work pretty well, as do the desktop controls. I hope the game itself is intuitive. The and sprite bouncing up and down were entirely ChatGPT's choices, picking what it thought was best. These are, of course, classic 8-bit themes and colours, come to think of it.
The sprite art isn't mine but from a freeware site. I wasted hours trying to get the JavaScript code to animate the sprite using a single frame sheet image, but in the end, I quickly cropped out two images (left and right). That's why one sprite is slightly different; I just quickly picked two frames from the walking animation set. I'm sure there's a program that can deal with these sprite sheets.
Anyway, that’s it. I hope you enjoy it. I think my high score is 199, but I am working toward 450, at which point the scrolling speed increases from 0.5 to 5. I've already found a slight glitch to get more points.
Happy gaming!
Frame set sheet.
Source: https://opengameart.org/users/sogomn
Intial development of the game play mechanics
Platform Masters screenshot.
Source: https://kylekukshtel.com/platform-master
Building My Own Private Offline AI ChatBot 2024
### 1. **Initial Setup**
```javascript
window.onload = function() {
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const restartButton = document.getElementById('restartButton');
canvas.width = 400;
canvas.height = 600;
```
- **`window.onload`**: This event handler ensures that the code runs only after the entire page (including images, scripts, etc.) has fully loaded.
- **`canvas` and `ctx`**: The `canvas` element is used for drawing graphics on the screen, and `ctx` is the 2D rendering context that provides methods and properties for drawing.
- **`canvas.width` and `canvas.height`**: Set the canvas dimensions.
- **`restartButton`**: Reference to the restart button element used to restart the game.
### 2. **Game State Variables**
```javascript
let isGameOver = false;
let gameSpeed = 0.5;
let score = 0;
let gravity = 0.2;
let playerVelocityY = 0;
let playerVelocityX = 0;
```
- **`isGameOver`**: Tracks whether the game has ended.
- **`gameSpeed`**: Determines the speed at which blocks scroll. Starts at `0.5`.
- **`score`**: Tracks the player's score.
- **`gravity`**: The force that pulls the player down, simulating gravity.
- **`playerVelocityY`** and **`playerVelocityX`**: Control the player's vertical and horizontal speed.
### 3. **Player Properties**
```javascript
const playerWidth = 30;
const playerHeight = 30;
const playerSpeed = 2;
const jumpStrength = 4;
const maxSpeed = 5;
let playerX, playerY;
```
- **`playerWidth`** and **`playerHeight`**: Size of the player.
- **`playerSpeed`**: Horizontal speed of the player.
- **`jumpStrength`**: How high the player jumps.
- **`maxSpeed`**: Maximum vertical speed due to gravity.
- **`playerX`** and **`playerY`**: The player's current position on the canvas.
### 4. **Player Images**
```javascript
const playerImageRight = new Image();
playerImageRight.src = 'sprite_sheet_R.png';
const playerImageLeft = new Image();
playerImageLeft.src = 'sprite_sheet_L.png';
let currentPlayerImage = playerImageRight;
```
- **Player Sprites**: Load images for the player's right and left-facing sprites.
- **`currentPlayerImage`**: Tracks which sprite to display (left or right).
### 5. **Blocks Properties**
```javascript
const blocks = [];
const blockWidth = 50;
const blockHeight = 15;
const blockSpacing = 200;
```
- **`blocks`**: An array to store block objects.
- **`blockWidth`** and **`blockHeight`**: Size of each block.
- **`blockSpacing`**: Distance between blocks.
### 6. **Image Load Handling**
```javascript
let imagesLoaded = 0;
playerImageRight.onload = playerImageLeft.onload = function() {
imagesLoaded++;
if (imagesLoaded === 2) {
resetGame();
requestAnimationFrame(gameLoop);
}
};
```
- **`imagesLoaded`**: Tracks if both player images have loaded.
- **`onload`**: Once both images are loaded, it calls `resetGame()` to start the game.
### 7. **Reset Game**
```javascript
function resetGame() {
isGameOver = false;
gameSpeed = 0.5;
score = 0;
playerX = canvas.width / 2 - playerWidth / 2;
playerY = canvas.height / 2 - playerHeight / 2;
playerVelocityY = 0;
playerVelocityX = 0;
blocks.length = 0;
generateInitialBlocks();
restartButton.style.display = 'none';
}
```
- Resets game variables, positions the player at the center, and generates the initial blocks.
- Hides the restart button when the game restarts.
### 8. **Generating Blocks**
```javascript
function generateInitialBlocks() {
for (let i = 0; i < 5; i++) {
generateBlock(canvas.height - i * blockSpacing);
}
}
function generateBlock() {
if (blocks.length === 0 || blocks[blocks.length - 1].y > blockSpacing) {
const block = {
x: Math.random() * (canvas.width - blockWidth),
y: -blockHeight,
width: blockWidth,
height: blockHeight,
color: 'blue',
hit: false,
missed: false
};
blocks.push(block);
}
}
```
- **`generateInitialBlocks`**: Creates the initial set of blocks.
- **`generateBlock`**: Generates new blocks at random x-positions and adds them to the `blocks` array.
### 9. **Player Controls**
```javascript
document.addEventListener('keydown', function(event) {
if (event.key === 'ArrowLeft') {
playerVelocityX = -playerSpeed;
currentPlayerImage = playerImageLeft;
} else if (event.key === 'ArrowRight') {
playerVelocityX = playerSpeed;
currentPlayerImage = playerImageRight;
} else if (event.key === 'ArrowUp' || event.key === ' ') {
if (!jumpRequested) {
jump();
jumpRequested = true;
}
}
});
document.addEventListener('keyup', function(event) {
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
playerVelocityX = 0;
} else if (event.key === 'ArrowUp' || event.key === ' ') {
jumpRequested = false;
}
});
```
- **Keydown** and **Keyup Events**: Handle player movement and jumping.
- Arrow keys or spacebar make the player move left, right, or jump.
- **`jumpRequested`**: Ensures the player jumps only once per key press.
### 10. **Touch Controls**
```javascript
let isTouchingLeft = false;
let isTouchingRight = false;
let jumpRequestedTouch = false;
canvas.addEventListener('touchstart', function(e) {
e.preventDefault();
const touch = e.touches[0];
if (touch.clientX < canvas.width / 2) {
isTouchingLeft = true;
} else {
isTouchingRight = true;
}
if (!jumpRequestedTouch) {
jump();
jumpRequestedTouch = true;
}
});
canvas.addEventListener('touchend', function(e) {
isTouchingLeft = false;
isTouchingRight = false;
playerVelocityX = 0;
jumpRequestedTouch = false;
});
```
- Adds support for touch controls on mobile devices.
- Left and right touches control horizontal movement, while tapping makes the player jump.
### 11. **Game Mechanics**
```javascript
function jump() {
playerVelocityY = -jumpStrength;
}
function checkBlockCollision() {
blocks.forEach(block => {
if (
playerX + playerWidth > block.x &&
playerX < block.x + blockWidth &&
playerY + playerHeight > block.y &&
playerY < block.y + blockHeight
) {
playerVelocityY = -jumpStrength;
playerY = block.y - playerHeight;
block.color = 'green';
block.hit = true;
score += 1;
if (gameSpeed < maxSpeed) {
gameSpeed += 0.01;
}
}
});
}
function checkGameOver() {
if (playerY > canvas.height) {
isGameOver = true;
}
blocks.forEach(block => {
if (block.y > canvas.height && !block.hit) {
block.missed = true;
isGameOver = true;
}
});
}
```
- **`jump()`**: Makes the player jump by setting a negative vertical velocity.
- **`checkBlockCollision()`**: Checks if the player collides with any block, allowing the player to jump off it. Increases the score and speed with each block hit.
- **`checkGameOver()`**: Ends the game if the player falls off the screen or misses a block.
### 12. **Game Loop**
```javascript
function gameLoop(timestamp) {
if (isGameOver) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'black';
ctx.font = '30px Arial';
ctx.fillText('Game Over', canvas.width / 2 - 80, canvas.height / 2);
ctx.fillText('Score: ' + score, canvas.width / 2 - 50, canvas.height / 2 + 40);
restartButton.style.display = 'block';
return;
}
updateControls();
playerVelocityY += gravity;
if (playerVelocityY > maxSpeed) playerVelocityY = maxSpeed;
playerY += playerVelocityY;
playerX += playerVelocityX;
if (playerX < 0) playerX = 0;
if (playerX + playerWidth > canvas.width) playerX = canvas.width - playerWidth;
if (playerY < 0) playerY = 0;
blocks.forEach(block => {
block.y += gameSpeed;
});
if (timestamp - lastBlockGenerationTime > blockGenerationInterval) {
generateBlock();
lastBlockGenerationTime = timestamp;
}
if (blocks.length > 0 && blocks[blocks.length - 1].y > canvas.height) {
blocks.shift();
}
checkGameOver();
checkBlockCollision();
ctx.clearRect(0, 0, canvas.width, canvas.height);
blocks.forEach(block => {
ctx.fillStyle = block.color;
ctx.fillRect(block.x, block.y, block.width, block.height);
});
ctx.drawImage(currentPlayerImage, playerX, playerY, playerWidth, playerHeight);
requestAnimationFrame(gameLoop);
}
```
- **Game Loop**:
- Updates the player's position based on velocity and gravity.
- Moves blocks down the screen.
- Checks for collisions and game-over conditions.
- Clears the canvas and redraws everything (blocks and player).
- Calls `requestAnimationFrame` to keep the loop going.
### 13. **Restart Button**
```javascript
restartButton.addEventListener('click', function() {
resetGame();
requestAnimationFrame(gameLoop);
});
};
```
- **Restart Button**: When clicked, the game is reset and restarted.
### Summary of Game Flow
1. **Initialization**: Set up game elements, load images, and wait for player input.
2. **Game Loop**:
- Update player position and velocities.
- Scroll blocks and check for collisions.
- Increase score and speed if the player hits blocks.
- Draw the player and blocks on the canvas.
- End the game if conditions are met (e.g., player falls off the screen).
3. **Player Controls**: Allow player movement through keyboard and touch events.
4. **Restart**: Allow restarting the game after it ends.
### File Structure
```
/project-folder
│
├── index.html
├── style.css
├── script.js
│
└── assets
├── sprites
│ ├── sprite_sheet_R.png
│ └── sprite_sheet_L.png
│
└── other-assets
└── (if you have any other assets like sound effects or additional images)
```
### Explanation of the Structure
- **`index.html`**: The main HTML file that sets up the structure of the webpage.
- **`style.css`**: The CSS file that styles the elements on the webpage, including the canvas and buttons.
- **`script.js`**: The JavaScript file containing all the game logic.
- **`/assets/sprites`**: A folder containing the player sprites (`sprite_sheet_R.png` and `sprite_sheet_L.png`).
- **`/assets/other-assets`**: An optional folder for other game assets, like sounds or additional images.
### Sample File Content
#### 1. `index.html`
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas Game</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<canvas id="gameCanvas"></canvas>
<button id="restartButton">Restart</button>
<script src="script.js"></script>
</body>
</html>
```
- **Canvas**: The game is drawn on this canvas element.
- **Restart Button**: Used to restart the game.
- **CSS and JS**: Linked the stylesheet (`style.css`) and JavaScript (`script.js`).
#### 2. `style.css`
```css
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
font-family: Arial, sans-serif;
}
canvas {
border: 2px solid #000;
background-color: #e0e0e0;
}
#restartButton {
display: none; /* Initially hidden */
margin-top: 20px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
}
```
- **Body Styling**: Centers the canvas on the screen.
- **Canvas Styling**: Adds a border to the canvas.
- **Restart Button**: Initially hidden until the game ends.
#### 3. `script.js`
(The game code you provided goes here.)
#### 4. Sprites (`sprite_sheet_R.png` and `sprite_sheet_L.png`)
- These are image files for the player sprite.
- Place these in the `/assets/sprites` folder.
### Summary of Usage
1. **`index.html`**: Sets up the basic structure and links to `style.css` and `script.js`.
2. **`style.css`**: Handles the layout and appearance of the game canvas and button.
3. **`script.js`**: Contains the game logic.
4. **Sprites**: Images used for player animations, stored in the `assets/sprites` folder.
This file structure organizes the game files in a clear and logical manner, separating HTML, CSS, JavaScript, and assets.
I apologise for any confusion regarding the demo of my offline AI ChatBot! As you can see from the Welcome page, I have had a lot of fun with HTML and JavaScript...
To be honest, we’re not far from making a useful ChatGPT system that could run on a Raspberry Pi 5 or a similar device, these bots could then be shared over the internet... However, that's a bit ahead of our current progress!
LM Studio is essentially an offline less powerful version of ChatGPT, which requires a relatively up-to-date gaming type PC—fortunately, I have one available. LM Studio comes with a built-in server that supports API endpoints. With assistance from another advanced AI, I was able to develop an initial flask script to interact with this server via HTML.
The next challenge was automating server access without keeping the PC running continuously, which would cause fan noise and waste resources At present I was to be the only user via my main Monitor in the house. My first attempt was to use Wake-on-LAN. After considerable troubleshooting, I managed to get it working, though it was not entirely reliable. Ultimately, I discovered that a simple mouse click was a more consistent method to wake the PC.
Following that, I created a PowerShell script with Task Scheduler to launch LM Studio, load the model, and start both the Flask server and the LM server. Although this setup worked, it was a bit slow and overly complex due to the Flask layer and HTML.
In the end, I decided to use my spare Raspberry Pi Zero 2W, which runs a Python script in a CLI/terminal emulator framework that directly interacts with the LM server's API endpoints via POST commands.
What you’re seeing in the ChatBot demo is a representation of the graphical user interface that is implemented at home. The pi is currently connected to my TV via HDMI and wireless keyboard and is ready to respond to requests, enabling offline interactions. I’m free to load and test different models through the LM programme itself, which currently has an AI capability comparable to ChatGPT-2.
It took numerous iterations to get everything right—figuring out remote startup for LM Studio and fine-tuning the AI’s parameters to avoid repetitive responses. Creating the template demo also required several revisions with the help of ChatGPT4. But here we are, with a functioning demo and my own private offline AI ChatBot!
Hidden PC Setup 2024
View when entering the room: PC power and coms are linked through a split white pipe. The PC is too large to fit inside the unit.
View from the back of the TV
View from top
Based on Twin Slotted shelving system
HamClock, created by WB0OEW, Elwood C. Downey, is fantastic. I can't recommend it enough. I discovered it while watching videos about shack setups and was immediately drawn to the Geochron displays and HamClock running on a Raspberry Pi. Given the Geochron's price, HamClock was the obvious choice—and it’s no compromise, as it's fully featured.
I'm still learning, but HamClock is amazing. For example, it shows when specific regions are open for HF communication at certain power levels. You can track the ISS orbit, monitor live clusters via DX Spider, and the sun monitoring is fascinating.
Initially, I wanted this for my radio setup, but it's become a great project in its own right. I had a Pi Zero W, which probably would have run HamClock, but I wanted it on my website. The developer offers a web server friendly demo version. I tried setting it up but struggled with SSL certificates. I ended up creating a script using JavaScript and Puppeteer to take screenshots, upload them to Google Drive, and embed them in a Google Site. This took two weeks of tinkering mind! The Pi Zero couldn’t handle both programmes so I upgraded to a Raspberry Pi 4, as recommended. Be sure to use Bullseye Raspberry Pi OS, as the developer suggests.
I loved the "On-Air" function, which is simply a switch on GPIO 21 to Ground. I added a red LED status too. I also added a nifty case designed by a UK eBayer. It features a heat-sink and a 5V fan. I didn’t want the fan running all the time, so I simply added a switch. I had considered a small script with a transistor to turn on the fan according to the temperature, but I think I managed to burn out the only PNP transistor I could salvage. Oh well! I've learned a lot through this process and built on my previous knowledge.
Have fun with your HamClock!
Art Deco French Radio Receiver: Visual Inspection and Repair Assessment for 2024
I bought this old radio receiver around 15 years ago because I have always loved the art deco style, and this radio matches the aesthetics of my home. Currently, it sits in the living room bookcase. I purchased it on eBay for about £100. The seller mentioned that it was a French model and required 120 volts to operate. After buying it, I got a transformer and, perhaps mistakenly, powered up the receiver. It did pick up some AM stations, but there was a lot of static, so I decided to leave it as is.
Over the years, I considered adding a Bluetooth speaker or using it as a decorative piece with a back light, but I haven't done much with it—it's just a beautiful object to look at. Recently, my interest in radios has been renewed, thanks to Mr. Carlson's Lab, a YouTube channel. This inspired me to think about re-capping the radio to get it working again. However, I'm still not sure about it, and for now, it's just a showpiece.
Build ⬇
After successfully deploying my automated mesh node project, I wanted to utilize a spare Raspberry Pi 3 I had. I enjoyed the ease of use and the results from the BME280 pressure, humidity, and temperature sensor in a previous project, so I decided to create a weather bot with it. While I knew this was theoretically possible from prior projects, I faced some challenges on the software side. I started with a Python script to handle the BME280 results. The script reads temperature, pressure, and humidity data from the BME280 sensor using `read_bme280()`, and updates specific placeholders in a Google Docs document using `update_google_doc()` every 90 seconds. I built in some logic to generate a predicted weather report based on the sensor readings. I used Google Docs because I had already hit my API limit with Google Sheets.
Next, I wanted to provide a nice-looking output for Google Sites. I wasted two evenings trying to get Google Apps Script to handle the Google Doc directly and load images, which was clunky and didn't work. So, I deployed a web app and used simple SVGs as images, which did the job well on all platforms. I also added a time and date function with the Google Apps Script HTML template.
In conclusion, I am really impressed with Google's suite of tools, especially since they are free. However, one problem with these API projects is their reliance on continuous service from a third party, which may eventually break or stop working. If that happens, it might be necessary to build a new application. But for now, I am happy to sit back and enjoy the sweet automation!
Update:
Another memo on the updates: Given our changeable weather, I found myself revisiting this project to compare its results with actual weather data. Clearly, improvements were needed. I started with the rain sensor. The problem was that once the sensor was triggered and the rain stopped, it didn't want to turn off! After tweaking the threshold, I found what I hope is the sweet spot. Today, as the wet weather rolled in, I observed a very good correlation between when it started and stopped raining. Now, I have a feeling that this sensor may need adjusting or fine-tuning from time to time, but all good for now.
Next on the software side of things, i moved on to the Google Web App, managing to locate and modify the HTML template to update SVG icons for 'night time'. Next, the weather logic, implemented in a Python script on a Raspberry Pi, required refinement. Below is a detailed summary of the technical aspects of the Python scripts and the weather logic.
The Python script utilizes a BME280 sensor for measuring temperature, pressure, and humidity, facilitated by the `smbus2` and `bme280` libraries. It employs Raspberry Pi's GPIO capabilities, configured with `RPi.GPIO`, to detect rainfall using a connected rain sensor. The script integrates with Google Docs API through `google.oauth2.service_account` and `googleapiclient.discovery` libraries, allowing it to append real-time weather data to a specified document. Logging functionality, managed by the `logging` library, provides detailed event tracking and error handling throughout script execution. Together, these components enable continuous monitoring and reporting of weather conditions while ensuring robust data management and operational oversight.
Explanation:
Rain Detection (rain_detected):
Uses GPIO.input(DO_PIN) to check the state of the rain sensor.
If GPIO.LOW is detected (rain_detected is True), it indicates rain is detected.
Night time Determination (nighttime):
Calls is_nighttime() function to check if the current time (hour) is between 6 PM and 6 AM.
Returns True if it's night time, otherwise False.
Handling Rain Conditions:
If rain_detected is True:
Checks temperature:
If temperature is less than 0, returns "Heavy Snow".
If temperature is between 0 and 5, returns "Light Snow".
Otherwise, returns "Raining".
Handling Sensor Errors:
If any of the temperature, pressure, or humidity values are None, returns "Sensor Error".
Thunderstorm Condition:
Checks if pressure is less than 1000 hPa, humidity, is greater than 90 and If temperature is greater than 16°C:
If true, returns "Thunderstorm".
Temperature-Based Conditions:
If none of the above conditions are met:
Checks temperature:
If temperature is 10°C or lower, returns "Overcast".
If temperature is between 10°C and 20°C
If nighttime is True, returns "Overcast".
Otherwise, returns "Partly Cloudy".
If temperature is 20°C or higher:
If nighttime is True, returns "Clear Night".
Otherwise, returns "Sunny".
7. Default Condition:
If none of the specific conditions are met, returns "Foggy"
The seemingly simple weather bot project has again increased in complexity by adding a rain sensor. I didn't want the typical setup, which would include this and perhaps a wind speed indicator. I wanted to keep things stealthy and contained within the house if possible. I can see how LoRa would work well for this type of setup and did, in fact, try to get my LoRaWAN TTN setup to work, but with no success (using custom webhooks).
The actual sensor is a very cheap but workable rain and snow drop sensor that outputs digital and analog signals. The sensor itself will probably need to be replaced over time, but at its price point, that's understandable. I proved it works in tests, but it has yet to rain with the actual roof deployment. The Python script that serves the weather report has been updated with logic to accept the rain sensor, and I made a few other tweaks.
I will wait until I get some rainy day results before I tackle the Google Scripts and templates, which need to be improved. For instance, the scalable vector graphics don’t update for night time. The problem is the current function to delete the incoming updates doesn’t seem to work as it causes errors. I will have to address this in another phase. Will update.
Cheap Snow and Raindrops Detection Module
The sensor itself
The digital pin for was used for the Pi. A test script was used to tune the threshold of the sensor.
Retro Fitting the sensor wiring in the loft (quite fiddly)
I ran the digital pin and 3.3V and GND to the other side of the loft where a tile was wedged up a little to allow the sensor to fit under.
I used a stiff cable tie as a depth gauge and guide for the sensor and wire. I glued the sensor to this and protected the pins.
The sensor module was housed in a junction box. I am actually looking forward to it raining now!
Once the pthon code was complete for the Pi, the BME280 was wired in
The board was protected with heat shrink, with the sensor left cut out
The cables were drawn up though with a draw cable
The location of the BME280 Sensor (cabled tied to the top of sofit vent in roof)
The juncion box allows disconnection of the sensor for maintenance or removal
A custom USB power cable was made and the wires routed along the beams
Off Grid
Communicator & Tracker 2024
Image source OpenAi.com
Below is the build process for my Meshtastic project, inspired by my interest in the platform. I had a spare Heltec V3 board and was intrigued by the idea of using a rotary encoder with canned message functionality.
My goal is to get this device working at a specific local site.
The board is powered by a spare Samsung 2500 mAh battery and incorporates GPS, allowing for potential tracking features. I used a Wiska box to house the components, along with some 'Lego gem pieces' for buttons. I was delighted when the board worked perfectly on the first power-up.
I will provide more information in my Meshtastic blog as I continue to test the range and explore other features of the build.
Test fitting the board in the Wiska box.
Allowing a port for the USB plug. It needed to be set quite far back.
Adding the rotary encoder in the case
Test fitting the board in the Wiska box.a
Allowing a port for the USB plug. It needed to be set quite far back.
Adding the rotary encoder in the case
Hot gluing in all loose wires
Trying to close the lid jammed the main board buttons so I allowed a gap and hot glued the lid to secure.
A pleasant surprise was that the board LED indicator illuminated the transparent switch and gave a cool effect.
Meshtastic 2024
My shiny new NanoVNA meter. I'm sure this will prove useful
Above is a work in progress by a Discord member, blueskies76, which I found very useful. I often prefer a good illustration with labels to help explain things, (HeltecV3 screen) as you can probably tell here!
Trends in Meshtastic 2024
Image source: https://openai.com/dall-e-2
Broadcasting from a commercial airliner.
Connecting via MQTT.
Text messaging from hilltops and high places while out hiking.
Mobile Broadcasting during Discord chats.
A Possible Future for Meshtastic?
Image source: https://openai.com/dall-e-2
Loft Setup 2024 ⬇
Above, perhaps last, significant update to my Meshtastic base setup. I decided to incorporate a temperature, humidity, and pressure sensor (BME280) module into an additional node. The second node boasts a reported higher gain of 13dBi and points west over the Chilterns, offering an excellent line of sight. Despite the lengthy wait times, I managed to prepare the bracket, pole, Wiska case, glands, and power supply in readiness for the Heltec V3.
In hindsight, I would have ordered the T-Echo with the BME280 sensor pre-installed rather than wiring one in myself (guide here). This choice was influenced by the T-Echo's excellent Bluetooth connectivity, easily reaching from the ground floor living room to the loft. Conversely, the Heltec V3 struggles to connect in the same spot. Lacking documentation on how to retrofit the BME280 into the rather expensive and scarce T-Echo, I opted for the safer and well-trodden route with the Heltec. Additionally, I've configured the T-Echo as a repeater to avoid confusion with having two nodes. I'll observe how things unfold.
Regarding the rest of the year, I will shortly gift a mobile node to a friend in London. I happen to possess a spare node, which he may pass on to another Londoner, potentially expanding my cohort. Just as I reach what might be the peak of my build status, I continue learning from others far more qualified and experienced than myself. Naturally, the YouTube videos that initially sparked my interest in this hobby continue to evolve, featuring numerous new experiments around LoRa. I'm also keen to experiment again with LoRaWAN if possible.
Mobile Setup 2024 ⬇
After a couple of weeks of waiting, my Heltec LoRa 32 V3 finally arrived. Unfortunately, the 3D-printed case I made, based on RoGa's Heltec V3 18650 case design, didn't quite work out for me, hence the tape holding it together. Fortunately, a colleague at work who's skilled with his Ender V2 offered to help me print a new case, which was a lifesaver. I've also ordered another Heltec for a friend who's become interested in the hobby, although he's a bit less hands-on. I'll set up the radio for him, and hopefully, he'll find it enjoyable. I'll share a photo of the finished case once it's ready. On a side note, I encountered some trouble trying to upgrade the firmware on my old Windows laptop. Thankfully, my Chromebook recognised the device, and everything went smoothly from there. I'm also curious to see how long the Samsung 18650 2500mAh battery will last on field trips once the weather improves.
Moving on to its replacement, the Heltec V3 Lora device seems to be very popular, and I managed to get hold of one before they sold out again. I plan to incorporate my Paradar 5dbi dipole antenna and utilize an 18650 battery in my setup.
I placed one of the Radios in my loft rig to act as a fixed Node. I was pleased to find a few other Nodes intermittently with the stock antenna.
I bought two LILYGO T-Echo 868MHz Wireless Modules for experimentation. Initially, I thought Meshtastic was exclusive to the US, but have discovered a growing network in the UK (2024)
Commuter E-Newspaper
2023
I recently worked on a cool project that showed how handy Python can be. I love the look of E-Ink displays, so I decided to bring our old Kindle E-reader back to life to make it more useful.
I enjoy reading free newspapers at London Underground stations, but I don't like the waste and clutter. When I found out that many Kindle newspaper subscriptions weren't supported anymore, I started thinking about a solution.
That's when I discovered two handy Python tools—feedparser and BeautifulSoup. With these, I could gather various free RSS feeds and blogs, smoothly putting them together into one easy-to-read document. The result? About 30 minutes of interesting content on topics I enjoy—a perfect addition to my Kindle reading experience.
Now, whenever I want fresh news, I just run the code. The document gets sent to my Kindle from my computer using the 'Send to Kindle' app. Easy! It quickly shows up in my Kindle library, ready to join me on a relaxed commute or tube ride into London.
Using feedparser and BeautifulSoup made it a breeze to handle syndicated content, turning raw data into a neatly organized and readable format for the Kindle E-reader.
RSS News Feeder 2023
Intro:
Hello, and thank you for taking the time to visit this page. This project, along with the next two (RFID Reward System and E-Ink Dashboard), were developed consecutively over several weeks. Each build provided me with new insights that I continued to build upon. These pages are intended as a showcase and repository to catalogue what went into these projects. The information here is intended not as how-to, more how I did it. I have built upon my own technical abilities in tackling these projects and this is a huge factor in the final outcome. I would be delighted however, if someone reading felt inspired through these ideas.
Background:
The concept for the RSS News Feeder project originated when I stumbled upon a Raspberry Pi Zero 2 W, which I had acquired around 2018 after setting up my first Raspberry Pi 3 (OctoPrint 3D printer controller application). Luckily I had pre-installed Raspberry Pi OS on an SD card.
Component Inspiration:
The next component I came across was a 12C LCD display that came bundled in a an Arduino Uno starter kit (shown below). I was drawn to the idea of creating a device with a singular purpose, much like a one-way pager. With that in mind, I decided to display an RSS news feed on this screen, powered by a battery.
Research:
As is often the case with these embedded system projects, I was pleased to discover that someone had already tackled a similar task with the same hardware -Les Pounder. It was mainly a matter of tweaking the original code Les provided to accommodate different article lengths, a loop, and a different RSS page from Les's original. Here's the significant difference from my previous projects: I am attempting to move beyond MakeCode, and this time, I had ChatGPT to assist me. More on this in the next two projects.
The article I found was very helpful in guiding me on how to wire up the screen controller and the Raspberry Pi GPIO pins. I encountered a bit of a learning curve with crontab, discovering that a 15-second delay was required to allow the network connection to establish so that the script would work upon boot-up.
Powerbank detail:
Once the Python script, Raspberry Pi and screen were working as intended, it was time to complete the project with an enclosure and a power solution. In this regard, I discovered that power banks are an economical way to provide the 5V needed for the Raspberry Pi. A 2600mAh high-capacity rechargeable Li-Ion battery was ordered. It just was a matter of wiring in some leads from the female type A USB Vcc and Gnd part and connecting them to a salvaged double-pole switch and type B micro USB cable. I must admit that I accidentally damaged my mini dash-cam while testing this out, so I was cautious of the polarity going forwards, testing it on an old, unwanted phone first.
Finishing the Enclosure:
Now, onto the enclosure. I like the look of a finished product, not just a tangle of wires, so I did my best to create a housing for the entire setup. I have a 3D printer but I find the PLA I have not robust enough for this application. Fortunately, I had some sturdy semi-transparent white acrylic on hand, which I used. Additionally, I used a generous amount of hot glue, partly because I'm familiar with its application and like its strength and immediacy. The acrylic had the added benefit of allowing the status light of the Raspberry Pi and powerbank to show through so this worked out well.
Conclusion:
That's about it for the project. I sanded down the edges as best I could and added a label with my Dymo printer to indicate the correct orientation. The sturdy, mains-rated switch felt right. The battery capacity has been well suited to the allow for many wireless news sessions.
In terms of development, it would be fun to play around with a GSM module to enable true wireless operation. I hadn't quite worked out SSH at this stage so this is something that I now can implement to allow remote editing of the Python script.
The Final Python Code:
from rpi_lcd import LCD
from time import sleep
import feedparser
import textwrap
def display_articles_on_lcd(entries, lcd):
for entry in entries:
title = entry['title']
split_title = textwrap.wrap(title, 16)
# Display the article title on the LCD
lcd.text("BBC WORLD NEWS", 1)
for line in split_title:
lcd.text(line, 2)
sleep(0.5)
sleep(1)
lcd.clear()
# Parse the RSS feed
tom = feedparser.parse("https://feeds.bbci.co.uk/news/world/rss.xml")
lcd = LCD()
sleep(5)
# Define the number of articles you want to display
num_articles = min(25, len(tom['entries']))
# Loop to continuously display the top 25 articles
while True:
top_articles = tom['entries'][:num_articles]
display_articles_on_lcd(top_articles, lcd)
sleep(5) # Wait for 5 seconds before displaying the articles again
RFID Reward System 2023
Background:
The background for this project starts right after the RSS News Feeder. I wanted to incorporate an Arduino Uno and an RFID sensor with tokens. It occurred to me that tokens are typically associated with money, so I came up with the idea of a reward system involving a counter or LEDs.
Component Considerations:
With the basic concept in mind, I began to explore the display options. I had some stylish seven-segment displays that came with the Arduino kit. However, unlike the I2C LCD display, they lacked a driver board or electronics. The Arduino is designed to control these displays in-house, but despite extensive research and time invested, I ended up damaging a single seven-segment display and couldn't get the 4x7 segment display to function correctly, despite following various guides.
So, it was on to plan B: traditional LEDs. I aimed for a slightly retro, exposed electrical board aesthetic, which led me to use plywood as a backing material. Wiring up the LEDs, identifying the cathode and anode, and selecting the right-sized resistors still presented challenges, but I had the essential components in place.
Code and AI:
Now, it was time to focus on the code and logic. Chat GPT played a pivotal role here. While I understood that the components I'd assembled should be compatible with each other, I had very little experience with Arduino code, its dependencies, syntax, or required libraries. I had tinkered with the Arduino editor and managed to make an LED blink, but that was the extent of it.
I needed a custom script that couldn't be found elsewhere. While the functionality of the code was still contingent on my understanding, the essential thing was that it gave me a foundation to build the project I wanted. However, assuming that a few prompts would produce a fully functional piece of code wasn't the case for me. I went through many iterations, manually updating sections of the code, sometimes affecting other parts and causing the entire system to stop working.
In the end, I successfully created a working product that satisfied my creative urge and provided me with both frustration and education. My daughter also enjoyed watching the build evolve.
To summarize the script logic: the Arduino Uno starts with no lit LEDs. If the RFID detects the 'reward' token, LED one lights up and remains lit. Additional rewards light up subsequent LEDs until the green £5 LED is illuminated, signifying the £5 reward. A 'deduct' button, when held down, turns off the last lit LED. This works for all LEDs although the green £5 LED I later decided should not be deducted. Please see the GIFs below to see a summary of this logic.
Power:
I had initially planned to power the device with a 9V battery, primarily because the component kit included a 9V battery connector. However, in the end, it lasted only around 9 hours and then went flat, needing to be hardwired. The device ended up under the stairs, as it didn't fit the kitchen setting where I originally intended to place it.
One final addition was a display toggle switch to conserve battery and reduce brightness when unnecessary. I attempted to achieve this through logic but ultimately achieved it by using a latching switch on the negative side of the LEDs.
Conclusion:
And that's it. With a few cable ties, screws, hot glue, and printed labels, I achieved the look and functionality I desired. It was fun working with the Arduino Uno and the Chromeduino IDE (see below). The micro controller concept is familiar to me through the BBC micro:bit platform and it was good to finally work closely with an Arduino. Finally I thought Reward'O'Meter sounded more fun as a last minute branding idea. I could now focus on the Grand Finale project...
#include <SPI.h>
#include <MFRC522.h>
#define RST_PIN 9 // Define the reset pin
#define SS_PIN 10 // Define the slave select pin
#define BUTTON_PIN 2 // Define the button pin
#define LOW_POWER_LED 13 // Define the built-in LED pin
MFRC522 mfrc522(SS_PIN, RST_PIN); // Create MFRC522 instance
int currentLed = 3; // Start with LED 1 on pin 3
byte lastUid[4] = {0, 0, 0, 0}; // Store the last detected RFID card's UID
bool uid1Detected = false;
unsigned long lastCardDetectionTime = 0; // Time of the last card detection
int consecutiveDetections = 0; // Count of consecutive card detections within 3 seconds
const unsigned long detectionInterval = 3000; // 3 seconds
unsigned long buttonPressedStartTime = 0; // Store the time when the button was first pressed
const unsigned long buttonPressDelay = 5000; // Button press delay in milliseconds (5 seconds)
bool buttonPressPending = false; // Flag to track if a button press is pending
void setup() {
Serial.begin(9600); // Initialize serial communication
SPI.begin(); // Initialize SPI communication
mfrc522.PCD_Init(); // Initialize MFRC522 RFID reader
Serial.println("Place your RFID card/token near the reader...");
pinMode(BUTTON_PIN, INPUT_PULLUP); // Set up the button pin as input with pull-up resistor
pinMode(LOW_POWER_LED, OUTPUT); // Set up the low-power LED as an output
digitalWrite(LOW_POWER_LED, LOW); // Turn off the low-power LED initially
for (int i = 3; i <= 7; i++) {
pinMode(i, OUTPUT);
digitalWrite(i, LOW); // Turn off all LEDs initially
}
}
void turnOnNextLed() {
if (currentLed <= 7) {
digitalWrite(currentLed, HIGH);
currentLed++;
}
}
void turnOffLastLed() {
if (currentLed > 3) {
currentLed--;
digitalWrite(currentLed, LOW);
}
}
void turnOffAllLeds() {
for (int i = 3; i <= 7; i++) {
digitalWrite(i, LOW);
}
currentLed = 3;
}
bool isButtonPressed() {
if (digitalRead(BUTTON_PIN) == LOW) {
if (!buttonPressPending) {
// Button has just been pressed, record the start time and set the pending flag
buttonPressedStartTime = millis();
buttonPressPending = true;
}
// Check if the button has been held for the buttonPressDelay or more
if (millis() - buttonPressedStartTime >= buttonPressDelay) {
buttonPressPending = false; // Reset the pending flag
return true;
}
} else {
// Button is not pressed, reset the start time and pending flag
buttonPressedStartTime = 0;
buttonPressPending = false;
}
return false;
}
void loop() {
// Check if the button is pressed and turn off the last lit LED if it is
if (isButtonPressed()) {
// Delay added here to ensure the button press is intentional
delay(200); // Adjust the delay time as needed
turnOffLastLed();
}
// Look for new cards
if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
// A card/token is detected
Serial.println("Card detected!");
// Read the card's UID
byte newUid[4];
for (int i = 0; i < 4; i++) {
newUid[i] = mfrc522.uid.uidByte[i];
}
// Check if the detected card has UID1
uid1Detected = memcmp(newUid, lastUid, 4) == 0;
if (!uid1Detected) {
// If UID1 is not detected, turn off the last lit LED
turnOffLastLed();
} else {
// If UID1 is detected, turn on the next LED
turnOnNextLed();
// Reset the card detection count and timer
consecutiveDetections++;
lastCardDetectionTime = millis();
}
// Store the UID of the detected card for comparison
memcpy(lastUid, newUid, 4);
}
// Check if we need to turn off the last lit LED
if (consecutiveDetections >= 3 && millis() - lastCardDetectionTime < detectionInterval) {
// If 3 consecutive card detections have occurred within 3 seconds, turn off the last lit LED
turnOffLastLed();
consecutiveDetections = 0; // Reset the detection count
// Check if all LEDs are off, and if so, reset the LED sequence
if (currentLed == 3) {
// Turn off all LEDs
turnOffAllLeds();
}
}
// Check if the Arduino is running low on power
if (millis() < 2000) { // You can adjust the threshold as needed
// Flash the built-in LED twice every second
digitalWrite(LOW_POWER_LED, HIGH);
delay(250); // Delay for half a second
digitalWrite(LOW_POWER_LED, LOW);
delay(250); // Delay for half a second
digitalWrite(LOW_POWER_LED, HIGH);
delay(250); // Delay for half a second
digitalWrite(LOW_POWER_LED, LOW);
delay(250); // Delay for half a second
}
// No card/token is present
mfrc522.PICC_HaltA(); // Halt the current card
mfrc522.PCD_StopCrypto1(); // Stop encryption on the card
}
Ear Training
2021
Embedded Systems Buggy
2018