Introduction: Pipboy Built From Scrap
This is my working Pipboy, built from random junk from the garage and a raid of my electronic components stock. I found this a challenging build and it took me several months of work, so I would not categorise this as a complete beginners project. Skills needed include plastic and wood work, electronics and coding. The body is built from various pieces of scrap plastics cut and welded together. I used a Raspberry Pi 0 as the micro-controller, with a display header mounted to part of the GPIO pins. The remaining pins are used to drive LEDs and connect buttons/controls. I wrote a "Pipboy" style user interface with some demo screens in Python to complete the project.
My goals for the project were:
- Had to be working - i.e. needed to actually have a display that did stuff
- I wanted it to have a "dial" to select the different screens as that always stood out for me as an iconic part of the UI in Fallout
- The entire build had to be completed using stuff I already had in the garage or in my office (this wasn't entirely achieved, but I got close - upwards of 90% of this was found items or stuff I already had laying around)
- Needed to be wearable
One goal I didn't have was to make it an exact replica of one of the in-game models - I prefer to build stuff "in the style" of a thing, as it gives me room to adapt random junk I find, and lets me be a bit more creative. Finally, yes I know you can buy these but that wasn't the point either ;)
- Wide bore pipe (such as a piece of drain pipe)
- Scrap plastics (both for creating the body and for decorative purposes)
- Small container
- Foam floor mat
- Raspberry Pi
- 3.5" display
- KY040 Rotary Encoder
- 3x LEDs
- 2x Push buttons
- Screws, glues, paints, filler etc
- Multi-tool with cutter and sanding attachments
- Soldering Iron
- Hot glue Gun
- Screw driver(s)
- Sharp knife
Step 1: Building the Heart of the Pipboy
First thing I needed to do was to ensure I could get a display and micro-controller in a form factor I could work with. I happened to have a 3.5" display kicking around that sits as a HAT onto of the GPIO pins of a Raspberry PI, so I decided to use this. I paired it with a Raspberry Pi 0 and made sure it worked OK, there are a few steps to getting Linux to recognise the display that you have to run through.
As you can see in the second picture I added a small cardboard/foam platform that I glued to the case to help support the display. I did this as I knew I would be handling this part a lot and didn't want to break pins or the display through lack of support. Eventually this got replaced, but it was a good bit of added protection during the build process.
Its also worth noting at this point, that later in the build I ran into performance issues with this setup - predominately the refresh rate over the interface between the Pi and the display, I'll go into this more later in the build but if I did this again I might consider different hardware here.
Here are some useful links for this:
I will also include in the github associated with this some notes for what I actually did to get this working (although from my reading of the topic there is a lot of variability in how this works for specific instances/drivers, so your millage may vary).
Step 2: Cardboard Prototype
I found some old guttering/pipe that I could use for the body, but I needed to come up with a design for the actual screen area and control panel. For this I just made cardboard mock ups and used masking tape to fix them to the pipe. The first was a simple "box" but it felt too simple, so I modified it to make the screen area more interesting and added a separate control panel area. This more or less became the final design (there were a few tweaks as you'll see, but its close).
Step 3: From Prototype to Template
Now I had a prototype I was happy with, I could flatten out the cardboard and turn it into a template which I then transposed onto part of an old PC case I had kicking around. Any similar tough plastic would work, I was just using junk I had to hand. Once marked out, I was then able to cut out the pieces so I could start to assemble the main body. A useful tip here, in order to make it easier to both mark up and subsequently cut the plastic, I covered the areas I would need to cut with masking tape first, this both gave me an easier way to draw the template onto the plastic, and something to help stop the cutting disc from slipping as I made first cuts.
Step 4: Add Case for Screen & Pi
I wanted the corners of the screen area to be curved, and I needed something to actually hold the Pi and display in - my solution was to use a small plastic container I had. I cut a hole out of the top of the body and glued the container through this. Then I glued all the sides together. I used superglue here with copious baking soda to help strengthen the welds. Later I filled and filed/sanded everything to tidy it all up and give it a more "moulded" feel.
Step 5: Repeat for Control Panel
Next, I did exactly the same template transposing, cutting and gluing to build the control panel housing.
Step 6: Cut Out the Pipe
As you can see the container I'm planning on using to house the main electronic components now sits proud inside the black plastic surround, this means that I need to make an opening in the pipe for it to situate into. I used masking tape again to line up where I wanted to cut, and cut out a square of the pipe so that the parts would fit.
Step 7: Bezel
One challenge I accidentally forced upon myself was trying to come up with a bezel that would fill the area around the display upto the edges of the container. Unfortunately the way the display is made it also has nothing useful in its design (like holes or anything) to help mount it, so the bezel also had to hold the display in place. My first attempt (seen here) was a mixture of plastic and foam. I eventually ended up iterating on this several times and it ended up being one of the more challenging parts of the build. Made worse by the small tolerances and delicate nature of both the bezel itself and the display.
Step 8: Battery Test
At this point, I turned my mind to how to make this run independently of a mains supplied USB. I tested various batteries and found that the Raspberry Pi + display didn't actually draw that much power and it was perfectly happy running on even one of my smaller battery packs (a freebie from a trade show). This was really lucky as the pack fit perfectly into a gap inside the build (photos later). Now we can temporarily tape the main body components together, and get our first test run of it powered up on my arm!
Step 9: Testing Fit
Here you can see where I've modified the core pipe further to allow access to the underside of the components. You can also see how I got lucky with the battery nicely fitting in a cavity to one side of the Pi container. Finally started the process of cleaning up the bonds, filling, sanding and did a test coat of primer to get a sense of the finished look (I knew at this stage I would be sanding this many more times and almost all of that primer would go, but I wanted to get a feel for how it would look).
Step 10: Add Controls and Detailing
I wanted a series of red/yellow/green LED's to form a gauge, as well as a rotary dial and at least 2 push buttons. These were all fitted to the control panel section - simply a case of drilling all the right holes. I also started to add small bits of scrap plastic components (basically kit bashing) to add details and more interest to the body and control panel.
Step 11: Bezel Rebuild No. 3
As I mentioned earlier, I struggled with the bezel for this build and rebuilt it several times. This is the third iteration that I stuck with. My approach here as to use hardboard and cut 2 different shapes, one thinker than the other and then glued (and clamped) them together to form the middle picture. These shapes allowed the square display to sit inside this and then it held the display in place inside the container (as in picture 3). This gave me just enough material to use 4 very small screws as fixtures - which I used to fix this firmly in place inside the case, and it in turn would hold the screen stable and safe. In retrospect I'd find a display which came with some decent mounting options (or use a 3D printer - which I didn't have at the time).
Step 12: Prototyping the Electronics
I use a breadboard to layout my simple circuits like this, and as I often do this part of the project in a different space to the main body construction, I also paired it up with a different Raspberry PI. Here I used a model 3, which gave me a bit more power to actually directly connect to it and run an IDE onboard. This just made quick code prototyping a little easier for me. There are plenty of other ways to remotely connect/code/debug, this is just want I preferred to do here.
The design here is fairly straight forward, we have;
- Rotary encoder - this uses a ground and bunch of GPIO pins to deal with the click direction and the push button.
- Pair of push buttons, these simply use a single GPIO pin each and a common ground
- 3 LED's, each with an inline resister to stop them popping, all going to a common ground, but with an individual GPIO pin each so that each can be addressed individually.
This gave me 3 LED's for my gauge, a rotary encoder to rotate through the screens on the pipboy and 3 push buttons to drive actions (one on the rotary encoder and 2 separately wired). This was about all I could fit, and with the display taking up a bunch of pins, pretty much consumes what you have on a standard Pi GPIO layout. However it was fine for my purposes.
The second picture pretty much shows the final internal layout I went with. I spent some time here testing out ways of driving the components and verifying it all worked out before I transposed this into the body of the build. All the code is in github.
A note about rotary encoders. I did spend a bunch of time writing my own Rotary Encoder state machine to track the GPIO high/low changes and map these to rotary positions. I had mixed success here, I got it working for "most" cases, but theres always edge cases and (de)bouncing etc to deal with. Its far, far easier to use a ready made library and theres a great one for these available to install for Python. I used that in the end as it allowed me to focus on the fun part of building, rather than spending ages debugging issues. All the details for that are included in the source code.
If you are new to Raspberry Pi, GPIO and electronics, I highly recommend the following tutorials which walk you through everything you need to do the above layout;
Step 13: Transposing the Electronics Into the Body
After I completed the layout using a breadboard, it was time to start thinking about how to mount these into the body of the pipboy. I decided I wanted to make it so that I could dismantle and remove all the electronic components in case I needed to repair or change anything in the future. In order to accomplish this, I decided to make all the sub-parts plug-able using dupont connectors.
For the buttons I soldered on some extension wires and used wire wrap to insulate the ends, this allowed me to assemble and disassemble these from the body (e.g. for testing, then painting etc). The Rotary Encoder already had pins that could accept dupont connectors, so I just needed to make some wires the right length.
The LED's took a little more work - for this, I decided to use a bit of scrap plastic I had (cut to fit) to make a removable panel to mount the LED's into. Then I hot glued them in place and soldered the resisters and wires. This made a removal unit that I could fit and remove and made painting and finishing easier.
Note my soldering is terrible, so I kept this simple and avoided anything too detailed/fine. In the final picture you can see I also had some very tiny broadboards (5x5), I used one of these mounted inside to provide a panel to connect everything to/from the GPIO. In particularly this was useful to create a common ground rail that I could use and avoid having lots of ground wires snaking back to the Pi.
I then cut various holes into the container to feed the wires through to the Pi and connect up to the GPIO. This design allowed me to complete strip everything out if I needed to (something I did several times whilst I was finalising the build).
Step 14: Fine Tuning the Fit
At this point I ran into some "fit" issues. Firstly the use of dupont connectors for the wiring meant that it was hard to get them to fit on the pins with the display hat in place as there wasn't enough height clearance. I solved this by buying (this is one of the few things I actually purchased for this project) a small GPIO pin extender so that I could have the display hat sat higher and leave room to access the remaining GPIO pins using dupont connectors.
I also cut up some small pieces of foam floor mat to make some side padding inside the container, this helped to seat the Pi + Display in the right place and stop it moving around.
Step 15: Retro Up the Rotary Encoder
Rotary encoders often come (as did mine) with a nice shiny modern "hi fi" style knob. This was totally out of character for the build, so I had to come up with something else. In my random box of parts I came across an old cog from a drill I broke a long time ago. This looked good, but didn't fit the rotary encoder. My solution here was to try out various wall plugs until I found one that fit rotary dial, and then cut it to shape so I could use it as an "inner collar" to seat the drill cog onto the rotary encoder as a more theme appropriate control.
Step 16: Inner Lining
More foam floor tiles! This time, I used them to build a soft lining to make it a more comfortable fit (without its too loose). By cutting a hole out of the foam I was also able to absorb some of the "lump" that the container for the Pi makes. Overall this made it much more wearable. Not shown in these photos, but I made it slightly larger than the main body so its visible at the ends, which I later painted and it all helped to add a bit of contrast and interest to the finished item.
Step 17: Adding Detail
Time to start adding some decoration and make it more interesting. First of all I added some scrap strips of plastic along one face to give it a bit of visual interest. Then I added some fake wires to some terminals and just pushed them into a hole I drilled into the body. This was all later painted different colours.
Step 18: Painting and Finishing Body Build
I wasn't too concerned with a pristine finish - as is supposed to be old and well used anyway (in fact I may come back and do even more weathering on it at some point). But I did want it to look like a consistent and complete object that wasn't hobbled together from random junk (even though thats exactly what it was). I went through numerous iterations of sanding, filling (milliput is my filler of choice for plastic), and repeat. Then several layers of primer and paint to further help smooth off all the joins. Then more sanding and more filling, and more painting.
Once I had a look and feel of the body I was happy with, I started adding some detailing. I used rub and buff on the grills on the controls to give them a more wire mesh feel. I also added small details of paint here and there using acrylics.
I dug into my collection of random stickers and added a few to finish up the effect. Then I did a weathering wash with some mixed paints to add some grime and dirt into the hard to reach areas that would be difficult to clean. This is maybe a bit too subtle at the moment, and I may come back and add some more later.
Step 19: Coding
Part of my ambition for this project was to make it react like an actual pipboy - and for me the most iconic part of that in-game is turning the dial to flip between different screens. To achieve this, I decided to write a pipboy user interface that would be able to display a series of screens and allow you to scroll between them. I wanted to make the content of the screens something that I could easily change, and indeed be able to add/remove screens.
I choose to write this in Python due to the excellent support for Raspberry Pi, GPIO etc. Python is pretty low on my list of languages I'm familiar with, so this was a big learning curve for me, and much of the code is messy as a result. I'll be updating this over time as I've not completely finished everything I wanted to do here - but its close enough to share now as all the main concepts are there.
My design for the UI code is reasonably straight forward, there is a main Python script that sets up the display, configures the GPIO, loads the screens and enters an infinite update loop, waiting for user events and updating the display as necessary. In addition there are various support scripts that help to generate the UI screens ahead of time.
Main libraries used:
- pygame: I use this as the engine to run the UI as it allowed me to draw arbitrary graphics, manipulate images, fonts, go full screen etc.
- pyky040: This provides the handling for the rotary dial and saved me a lot of time (many thanks to Raphael Yancey for releasing this.
- RPi.GPIO: For well GPIO driving, I toyed around with a few options here, but this gave me the right level of flexibility I wanted, particularly with things like using a spare GPIO as another 3.3v to drive the rotary encoder etc.
- noise: For generating perlin noise, to allow me to create a random waveform for the radio screen that looks more natural
- queue: I ran into a frustrating bug with timing of events from the rotary encoder being turned and the (very) slow refresh rate of the LCD display. In the end the way I solved this was to en-queue inbound events from the rotary encoder and pick them off one at a time as the screen refreshed.
- os, sys, threading, time: all used for standard python functions
A note on the design of the screen handling. Screens are defined as a list of names within the code. Each entry in the list can have either a png or a txt file (or both) associated with it, the code builds a cache for each screen after looking for files in the linux directory structure associated with the code.
The contents of those files are generated elsewhere (by hand or by other scripts), the output of which are saved as png or txt files which the pipboy UI can then load as a screen to display. This keeps the main "update" loop relatively clean and simple, but also gives me the flexibility to update or change the contents of the screens with new data as an when I see fit.
There are odd exceptions where a few things are coded - such as the waveform for the random radio screen as that is computed in real time and animated.
If an analogy helps, think of the UI design as an extremely crude and simple web browser - each "screen" is like a really simple webpage which can only consist of one png, one txt file or a combination of the two. The contents of those are independent and are just drawn by the UI like a browser would draw a webpage.
Here are links to the main libraries I used here:
Step 20: Stats Screen
No pipboy would be complete without the classic pipboy silhouette stats screen. For this, a friend of mine created a static PNG which I just display as a place holder. At some future date, I may come back and make this more dynamic with some random damage representation or similar, but for now its a static screen.
Step 21: Inventory Screen
Something thats always useful with Pi projects is having a way to display basic info like the IP address its DHCP'd etc. I decided to overload the Inventory screen as a display of the Pi "inventory" - what CPU, memory, IP address etc. I wrote a small Linux script to collect this info and just redirect it to an appropriately named text (.txt) file that the UI system then picks up and displays. This way if I'm ever in a different location I can fire the script and pick up a fresh .txt file with the up to date IP address etc.
Step 22: Map Screen
This screen was one of the more complex screens to work on. The Raspberry Pi 0 doesn't come with a GPS module, but I wanted to make the Map have some validity to where the Pi was. My solution to this is a separate script that pulls the Pi IP address, uses http://ipinfo.io to look up an approximate location. The JSON response is captured and then I convert the co-ordinates so I can pull down an openstreetmap.org tile for the approximately location.
The tiles come down in multiple colours, but I wanted a green-scale image to match the look and feel of the Pipboy and I couldn't find one exactly like that, so I wrote a green-scale filter in Python to remap the colours of the openstreetmap tile and then cache the new image to a png file.
During the above process a text file is generated with the approximate location and co-ordinates, and the map tile is created as a png. The pipboy UI pulls up both these files and overlays the contents to create a Map screen that works (to within the accuracy of the IP address resolution to a location).
Step 23: Data Screen
This is just a test card (generated by another python script and output to a png file) that is displayed to help test out sizing/layout. I left it in because its still kind of useful to verify how much real estate I have to play with when mocking up screens.
Step 24: Radio Screen
Along with the map screen, this is the other screen that really caused me a lot of work. This is the only screen where I played around with animation - and it mostly works as intended, but performance is still a problem with the LCD screen refresh rates. The structure of the screen is a text file containing some randomly chosen radio names (these are just arbitrary strings and don't do anything other than present a list on the screen), a png file that contains the axis of the graph area (I wrote another script to generate these and create the png, so I could vary the steps etc if I wanted to change the look) and finally overlaid on that is a dynamic area where I create a "live" waveform using perlin noise. The use of perlin noise helps to make the waveform feel more natural and not chaotically noisy as a totally random number sequence would get. I use 2D of perlin noise here, 1D to generate the peaks and troughs along the line, and then a 2nd dimension to vary these in a time sequence so the peaks and troughs rise an fall with a nice smooth feel as if the frequencies are being tuned.
This is the only screen where the pygame loop does any real work as each cycle, it has to calculate the new waveform, erase the part of the screen in this lives in and redraws.
Step 25: Final Thoughts
This is possibly the most challenging build I've done, with many different concepts and skills called upon, but it is also one of the most enjoyable with real thing that works as a result. I'm still in the process of tidying up some of my more technical notes as well as the github repo for the code. All of which I'll make available shortly, so pop back again soon for more details and info as I get time to add them to the writeup.
If you get around to doing something like this, I'd love to see the results and if you have any questions please feel free to get in touch and I'll endeavour to add more info in any steps where you want to help.
Step 26: Code Open on Github
I finally got around to opening the code up on Github. Its available at this link: https://github.com/jshart/pipboy_release/
Participated in the
Trash to Treasure Contest