FALLOUT TERMINAL Rev 1.0

 

If you've played the games, you'll know these terminals are scattered throughout offices, factories, installations, and underground bunkers. They're used to access computer logs, retrieve information from connected machinery, unlock restricted areas, control doors and security systems, and uncover bits of lore about the world before the bombs dropped.

 

At its core, this project is essentially a cyberdeck powered by the LattePanda Mu, an x86-based compute module paired with a Lite Carrier Board. Unlike a typical microcontroller-based project, this setup runs a full desktop operating system and can handle everything from everyday computing tasks to running applications and games.

The entire enclosure was designed and built from scratch. I used in-game references to recreate the terminal's appearance as accurately as possible, paying attention to its proportions, enclosure, and overall design language. I also added a physical keyboard to my version, something I wanted for a more practical, computer-like experience.

This is actually Version 2 of my Fallout terminal project. In the first version, I took inspiration from the terminals in the games but created my own design with a similar aesthetic. This time, I wanted to go a step further and build a much more faithful replica of the original in-game hardware, with a few modifications of my own.

 

 

The biggest addition is a secondary display that brings some extra functionality to the build. I integrated three features into this display:

 

  1. Air Quality Monitor: Using an SGP40 VOC sensor, the system monitors changes in room air quality and displays the readings as RADS, inspired by the radiation levels shown in the Fallout games.
  2. PC Fan Control: A dedicated interface lets me monitor and control the speed of the cooling fan.
  3. Terminal Screensaver: A screensaver displays randomly generated terminal-style text, recreating the look of a computer running commands in a Linux terminal.
  •  

The secondary display is controlled using a physical rotary encoder, allowing me to navigate the interface and switch between its different functions. The entire module is managed by a DFRobot FireBeetle 2 ESP32-S3 microcontroller, which handles the sensor readings, display, and fan control independently of the main computer.

In this article, I'll walk you through the complete build process, including the hardware selection, enclosure design and fabrication, electronics assembly, wiring, programming, and software setup required to bring this Fallout terminal to life.

HARDWARE LIST
1 Lattepanda MU N350
1 Lattepanda Lite Carrier board
1 7-inch HDMI Generic Display
1 DF ROBOT Firebeetle 2 S3 Dev Board
1 SGP40 Sensor
1 Gravity Rotary Encoder Module
1 NVME SSD 256GB Gen 3
1 Wifi/BT Card M.2.E Key
1 ILI9225 Display
1 Gravity Mosfet Controller Module
1 12V PC Fan
1 Ethernet Extender
1 USB 3.0 Extender
1 Push switch, panel-mounted
1 DC Barrel jack
1 3D Printed Parts
HDMI cable
Power adaptor 12V 4A
M2 screws
M6 Nuts and bolts

FALLOUT TERMINAL

The Fallout franchise is a retro-futuristic, post-apocalyptic RPG universe that combines 1950s sci-fi aesthetics with the aftermath of nuclear war. My entry point into the series was Fallout: New Vegas, and it quickly became one of my favorites, thanks to its writing, atmosphere, and freedom of choice.

One thing that has always fascinated me about Fallout is how differently technology evolved compared to our world. In the Fallout universe, transistors never became as widely adopted as they did in reality. Instead, technological development retained much of its 1950s-inspired character, even as society advanced in other ways. This alternate technological history shaped almost everything, from the vehicles and weapons to the computers found throughout the wasteland.

If you've played the games, you've probably spent quite a bit of time interacting with these terminals. They generally come in two forms: desk-mounted terminals and wall-mounted units, commonly found in offices, factories, bunkers, and installations.

I've always loved their design. 

They're bulky, industrial, and very different from the computers we use today. With their chunky metal enclosures, physical buttons, knobs, and green CRT-style displays, they look like something straight out of an alternate 1950s-inspired future.

But these terminals aren't just set pieces; you can access computer logs, read old emails, unlock doors, control security systems, and uncover stories about the people who lived in the world before the bombs dropped.

As a Fallout fan, recreating one of these terminals has been on my project list for a while. My previous build was loosely inspired by their design, but this time, I wanted to get much closer to the actual in-game hardware.

 

PREVIOUS TERMINAL PROJECT

A few months ago, I built the Vault-Tec Air Terminal, a Fallout-inspired cyberdeck based on the terminals from the games. It was a fully functional Ubuntu computer powered by a Raspberry Pi 5 with an M.2 HAT for storage expansion.

https://www.hackster.io/Arnov_Sharma_makes/vault-tec-air-terminal-b479cb

One of the unique features was a secondary display that showed real-time room air-quality readings as RADS, inspired by the radiation indicators in Fallout. This was powered by a TTGO T-Display ESP32-S3 Long with a 640 × 180 pixel display, paired with an SGP40 VOC sensor to monitor changes in air quality.

For the main display, I used a 4-inch square HDMI screen from Waveshare, running Cool-Retro-Term to recreate the look of a classic Fallout terminal.

I also designed and 3D-printed the enclosure, then painted and weathered it with artificial rust and mold effects. Custom knobs, logos, and other small details helped give it the worn-out appearance of a terminal straight out of the Fallout universe.

This project was my first take on bringing a Fallout terminal into the real world, and it became the starting point for the new version I'm currently working on.

HARDWARE- LATTEPANDA MU N305 AND LITE CARRIER BOARD

For the brain of this project, I'm using the LattePanda Mu compute module with the Intel Core i3-N305 processor. It's an 8-core, 8-thread x86 processor with a base frequency of 1.8 GHz and a maximum turbo frequency of 3.8 GHz, making it capable of handling desktop applications, multitasking, and even some gaming.

I'm using the 16GB RAM variant, which features 16GB of LPDDR5 memory running at 4800 MT/s, along with 64GB of onboard eMMC storage. However, for this project, I'll be using an external NVMe SSD for additional storage and faster access to my files. The module also features integrated Intel UHD Graphics, making it suitable for running a full desktop operating system without requiring a dedicated graphics card. (Will do some hella gaming on this setup)

The LattePanda Mu is a compute module rather than a conventional single-board computer, so it doesn't provide the usual external ports on its own. To make it usable in this project, I'm pairing it with the DFRobot LattePanda Mu Lite Carrier Board, which exposes the necessary connectivity and expansion interfaces.

The carrier board provides two USB 3.2 ports supporting speeds of up to 10Gbps, two USB 2.0 ports, Gigabit Ethernet, HDMI 2.0 with support for 4K output, and M.2 slots for storage and wireless connectivity. It also includes a PCIe 3.0 x4 expansion slot, along with UART and I²C interfaces for connecting additional hardware. The board supports a 12–20V DC input through its barrel jack, making it suitable for compact computing projects.

For storage, I've installed a 256GB NVMe SSD in the M.2 M-key slot and an M.2 Wi-Fi card in the E-key slot. I'm powering the entire setup using a 12V, 4A power adapter, which provides up to 48W of input power. This gives me a compact x86 computing platform with the storage and connectivity needed for the Fallout terminal.

LATTEPANDA MU BENCHMARKS

To test the graphics performance of the LattePanda Mu, I installed Steam and ran 3DMark's Steel Nomad Light benchmark on its integrated Intel UHD Graphics.

The system scored 454 points, slightly above the displayed average of 435 for the same hardware. The graphics test ran at an average of around 3.37 FPS, so this is definitely not a gaming powerhouse, but it's an interesting result for such a compact x86 computing platform.

I'll also be testing a few games on the system to see how well it handles actual gameplay.

HARDWARE- DFRobot FireBeetle S3

The main microcontroller I'm using to control the sensor, fan, and other modules is the DFRobot FireBeetle 2 ESP32-S3-U. It's based on the ESP32-S3-WROOM-1-N16R8 module and provides more than enough processing power for handling sensor readings, display updates, and fan control.

The board features a dual-core 32-bit Xtensa LX7 processor running at 240 MHz, along with 16MB of Flash and 8MB of PSRAM. It also supports Wi-Fi and Bluetooth 5, making it suitable for connected IoT projects.

For interfacing with the other components, it offers 26 digital I/O pins, two I²C interfaces, and three UART interfaces.

Specifications:

  • Processor: Dual-core Xtensa LX7, 32-bit
  • Clock speed: 240 MHz
  • Flash: 16MB
  • PSRAM: 8MB
  • SRAM: 512KB
  • Connectivity: Wi-Fi 802.11 b/g/n and Bluetooth 5 LE
  • Operating voltage: 3.3V
  • Power input: 5V DC via USB Type-C or VCC
  • Battery charging current: Up to 1A
  • Interfaces: 26 digital I/O, 2 × I²C, 3 × UART, USB 2.0 OTG
  • Operating temperature: -20°C to 70°C
  • Dimensions: 25.4 × 60 mm

HARDWARE- FERMION SGP40 VOC AIR QUALITY SENSOR

For measuring air quality, I'm using the DFRobot Fermion SGP40 VOC Sensor, a compact sensor from Sensirion designed to detect volatile organic compounds in the air.

The sensor provides a VOC Index ranging from 0 to 500, giving a standardized indication of changes in air quality. It can also measure VOC concentrations in the range of 0–1000 ppm ethanol equivalent, with a response time of approximately 2 seconds.

The SGP40 communicates over I²C using the address 0x59 and operates on a 3.3–5V DC supply, consuming around 2.6 mA during operation. It requires approximately 10 seconds to warm up and supports operation in temperatures ranging from -10°C to 50°C, with humidity compensation for 0–90% RH.

HARDWARE- FERMION EC11 ENCODER MODULE


For navigating the terminal interface, I'm using the DFRobot 360° Rotary Encoder Module, based on the EC11 rotary encoder. It allows users to scroll through menus and make selections using a physical knob, keeping the controls simple and consistent with the retro terminal aesthetic.

The module provides two output signals, A and B, to detect the direction and movement of rotation, along with a separate push-button output for selection. Since it supports continuous 360-degree rotation without a stopping point, it works well for navigating menus and adjusting settings.

The encoder operates on 3.3–5V and generates 20 pulses per rotation. It has a rated rotational life of approximately 30, 000 ± 200 cycles and an operating temperature range of -30°C to +80°C. Its compact 33.8 × 22.4 mm form factor and XH2.54 connection pads also make it convenient for prototyping.

HARDWARE- FERMION DC-DC BUCK CONVERTER

To power the electronics in this project, I'm using a 40W DC-DC buck converter that steps down the input voltage to a stable 5V supply. It's capable of delivering up to 8A of output current, making it suitable for powering the various components in the setup.

The module accepts an input voltage of 6–14V and provides a regulated 5V output with a voltage accuracy of ±1%. It also features an output ripple of less than 30mV at full load and a conversion efficiency of over 93% under specified operating conditions (12V input at full load).

With a low standby current of less than 200µA and an operating temperature range of 0°C to 70°C, this compact module provides an efficient power solution for the project.

Specifications:
  • Input voltage: 6–14V DC
  • Output voltage: 5V DC
  • Maximum output current: 8A
  • Maximum rated power: 40W
  • Voltage accuracy: ±1%
  • Output ripple: <30mV at full load
  • Conversion efficiency: >93% at 12V input and full load
  • Standby current: <200µA
  • Operating temperature: 0°C to 70°C

HARDWARE- GRAVITY MOSFET POWER CONTROLLER

I'm using this DFRobot MOSFET Power Controller to drive a 12V PC fan using the microcontroller. It allows the controller to switch the fan on and off using a low-voltage GPIO signal, without directly powering the fan through the microcontroller.

The module supports 5–36V DC input, up to 20A current, and 3.3–10V logic-level control, making it suitable for controlling higher-power devices with a microcontroller.

Specifications:
  • Input voltage: 5–36V DC
  • Maximum current: 20A
  • Logic voltage: 3.3–10 V DC
  • Switching frequency: Up to 1kHz
  • Interface: PH2.0-3P Gravity connector
  • Dimensions: 44 × 32 mm

HARDWARE- ILI9225 DISPLAY

For the secondary display, I'm using a 2-inch TFT LCD with the ILI9225 display controller. It has a resolution of 176 × 220 pixels and supports up to 65K colors, making it suitable for displaying sensor readings, system information, and the retro-style interface for this project.

DFROBOT SERVICE

A huge thanks to DFRobot for supporting this project and providing the hardware that made this build possible. From the LattePanda Mu N305 and FireBeetle ESP32-S3 to the SGP40 VOC sensor, rotary encoder, and other modules, their support played a big part in bringing this Fallout terminal to life.

DFRobot is a great resource for makers, engineers, and electronics enthusiasts, offering a wide range of development boards, sensors, modules, robotics components, and other electronics hardware for all kinds of projects.

Their product wiki was also incredibly helpful throughout this build, with detailed documentation and examples that helped me understand how the hardware works and figure out the code for different modules.

I'm genuinely grateful to the DFRobot team for supporting my work and helping me bring this project to life. If you're planning your next DIY electronics project, make sure to check out DFRobot and explore their range of hardware. You might just find what you need for your next build!

BREADBOARD SETUP

By following the wiring diagram, we can begin assembling the breadboard setup. The process is relatively simple, with all components initially placed on the breadboard except for the MOSFET board and the buck converter.

  1. 1. First, we connected the GND pins of every module, including the display, air quality sensor, rotary encoder, and logic side of the MOSFET controller, directly to the GND pin of the DFRobot FireBeetle board to ensure that everything shares a common ground.
  2. 2. For Power, we connected the VCC of the ILI9225 display, the VIN of the SGP40 sensor, and the VCC of the rotary encoder to the 3V3 pin of the FireBeetle board.
  3. 3. Next, for the ILI9225 display data lines, CS is connected to pin D7 of the FireBeetle, RST to pin D8, RS to pin D9, SDA to pin D11, and SCL to pin D12. The LED backlight pin is connected to pin D0.
  4. 4. For the SGP40 air quality sensor, SDA is connected to pin D1 of the FireBeetle, while SCL is connected to pin D2.
  5. 5. Rotary encoder pin A is connected to pin D4 of the FireBeetle, pin B to pin D5, and pin C to pin D6.
  6. 6. For the main power supply, we used a buck converter and added a barrel jack to its VIN and GND terminals. The converter's 5V output (VOUT) is connected to the VCC terminal of the FireBeetle board, while its GND output is connected to the board's GND pin.
  7. 7. For the cooling fan, the SIG (PWM) pin of the MOSFET power controller is connected to pin D10 of the FireBeetle. On the power side of the MOSFET board, VIN (V+) is connected to the buck converter's 12V input, while V− is connected to the main power supply's ground. Finally, OUT+ is connected to the fan's red wire, and OUT− is connected to the fan's black wire.

After completing all the wiring, we checked every connection one last time. With so many wires packed into this setup, all it takes is one wrong connection to fry your hardware!

CODE & BASIC WORKING

Once we were sure everything was connected correctly, we uploaded the code to the DFRobot FireBeetle 2 ESP32-S3 board. Let's take a quick look at how it works.

#include
#include
#include
#include
#include
#include

// ==========================================
// HARDWARE DEFINITIONS
// ==========================================
#define TFT_RST 8
#define TFT_RS 9
#define TFT_CS 7
#define TFT_MOSI 11
#define TFT_SCLK 12
#define TFT_LED 0

#define I2C_SDA 1
#define I2C_SCL 2

#define ENC_A 4
#define ENC_B 5
#define ENC_SW 6
#define FAN_PIN 10

// ==========================================
// TERMINAL THEME (FALLOUT NEW VEGAS AMBER)
// ==========================================
#define NV_AMBER 0xFDA0
#define NV_DIM 0x8A20
#define NV_DARK 0x3100
#define NV_BG COLOR_BLACK
#define NV_WARN COLOR_RED

TFT_22_ILI9225 tft = TFT_22_ILI9225(TFT_RST, TFT_RS, TFT_CS, TFT_LED);
Adafruit_SGP40 sgp;

// ==========================================
// SYSTEM STATE
// ==========================================
int currentTab = 0; // 0=RADS, 1=FAN, 2=TERMINAL (Screensaver)
int lastTab = -1;

// Power Saving & Screensaver
unsigned long lastActivityTime = 0;
const unsigned long SCREENSAVER_TIMEOUT = 30000; // 30 seconds
volatile bool activityFlag = false;

// Sensor
unsigned long lastSensorRead = 0;
uint16_t vocDisplay = 0;
uint16_t lastVocDisplay = 9999;

// Fan Control
int fanSpeed = 0;
int lastFanSpeed = -1;

// Terminal Matrix Variables
int currentTermLine = 0;
unsigned long lastTermUpdate = 0;
const char* robcoLogs[] = {
"ROBCO INDUSTRIES UNIFIED OP SYS",
"SYSTEM WIDE AUDIT... OK",
"AWAITING OVERSEER INPUT...",
"BYPASSING KERNEL...",
"ERROR: VAULT-TEC COM LINK OFFLINE",
"INITIATING DIAGNOSTIC...",
"SECTOR 7G ALLOCATED",
"MEMORY DUMP: 0x4F92A",
"SGP40 AIR QUALITY SENSOR ACTIVE",
"ACCESSING MAINFRAME...",
"ACCESS GRANTED",
"COOLING ARRAY RESPONDING",
"G.E.C.K. STATUS: UNKNOWN"
};
const int numLogs = sizeof(robcoLogs) / sizeof(robcoLogs[0]);

// Encoder
volatile int32_t encoderCount = 0;
bool btnFlag = false;
unsigned long lastBtnPress = 0;
bool lastBtnState = HIGH;

const int8_t enc_states[] = {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};

// ==========================================
// INTERRUPTS
// ==========================================
void IRAM_ATTR encISR() {
static uint8_t old_AB = 3;
old_AB <<= 2;
old_AB |= ((digitalRead(ENC_B) << 1) | digitalRead(ENC_A));
encoderCount += enc_states[(old_AB & 0x0F)];
activityFlag = true;
}

// ==========================================
// UI: PIP-BOY BEZELS & CRT EFFECTS
// ==========================================
void drawCRTBezel() {
tft.drawRectangle(0, 0, tft.maxX() - 1, tft.maxY() - 1, NV_DIM);
tft.drawLine(0, 0, 15, 0, NV_AMBER); tft.drawLine(0, 0, 0, 15, NV_AMBER);
tft.drawLine(tft.maxX()-15, 0, tft.maxX()-1, 0, NV_AMBER); tft.drawLine(tft.maxX()-1, 0, tft.maxX()-1, 15, NV_AMBER);
tft.drawLine(0, tft.maxY()-1, 15, tft.maxY()-1, NV_AMBER); tft.drawLine(0, tft.maxY()-15, 0, tft.maxY()-1, NV_AMBER);
tft.drawLine(tft.maxX()-15, tft.maxY()-1, tft.maxX()-1, tft.maxY()-1, NV_AMBER); tft.drawLine(tft.maxX()-1, tft.maxY()-15, tft.maxX()-1, tft.maxY()-1, NV_AMBER);
}

void drawGridBackground() {
for(int x = 10; x < tft.maxX() - 10; x += 20) {
for(int y = 35; y < tft.maxY() - 10; y += 20) {
tft.drawPixel(x, y, NV_DARK);
}
}
}

void drawHeader() {
tft.fillRectangle(2, 2, tft.maxX() - 2, 28, NV_BG);
tft.drawLine(2, 28, tft.maxX() - 2, 28, NV_AMBER);

tft.setFont(Terminal6x8);
tft.drawText(5, 5, "ROBCO UOS", NV_AMBER);

// Adjusted for 3 tabs
const char* tabNames[] = {"RADS", "FAN", "TERM"};
for(int i = 0; i < 3; i++) {
int tx = 80 + (i * 45);
if(i == currentTab) {
tft.fillRectangle(tx, 5, tx + 40, 23, NV_AMBER);
tft.setBackgroundColor(NV_AMBER);
tft.drawText(tx + 4, 10, tabNames[i], NV_BG);
tft.setBackgroundColor(NV_BG);
} else {
tft.drawRectangle(tx, 5, tx + 40, 23, NV_DIM);
tft.drawText(tx + 4, 10, tabNames[i], NV_DIM);
}
}
}

void robcoBootSequence() {
tft.clear();
tft.setFont(Terminal6x8);
tft.drawText(5, 10, "ROBCO INDUSTRIES UNIFIED OPERATING SYSTEM", NV_AMBER);
tft.drawText(5, 20, "COPYRIGHT 2077 ROBCO(R)", NV_AMBER);
delay(800);

tft.drawText(5, 40, "-Server 1-", NV_AMBER);
delay(300);

for(int i=0; i<=64; i+=8) {
tft.fillRectangle(5, 55, 150, 65, NV_BG);
tft.drawText(5, 55, "Testing RAM... " + String(i) + "K OK", NV_AMBER);
delay(100);
}

delay(400);
tft.drawText(5, 70, "1 CPU CORE DETECTED", NV_AMBER);
delay(200);
tft.drawText(5, 80, "SGP40 SENSOR... ONLINE", NV_AMBER);
delay(200);
tft.drawText(5, 90, "MOSFET ARRAY... ONLINE", NV_AMBER);
delay(600);

tft.fillRectangle(0, 0, tft.maxX(), tft.maxY(), NV_AMBER);
delay(150);
tft.clear();
}

void drawDetailedGauge(int cx, int cy, int r, int val) {
int startAngle = 140;
int sweep = 260;
int activeAngle = startAngle + (val * sweep / 100);
static int lastVal = 0;

tft.fillRectangle(cx - 30, cy - 15, cx + 30, cy + 15, NV_BG);
int oldA = startAngle + (lastVal * sweep / 100);
tft.fillCircle(cx + r * cos(oldA * PI / 180.0), cy + r * sin(oldA * PI / 180.0), 6, NV_BG);

for (int a = startAngle; a <= startAngle + sweep; a += 26) {
int outerX = cx + (r + 8) * cos(a * PI / 180.0);
int outerY = cy + (r + 8) * sin(a * PI / 180.0);
tft.drawPixel(outerX, outerY, NV_DIM);
}

for (int a = startAngle; a <= startAngle + sweep; a++) {
float rad = a * PI / 180.0;
int x = cx + r * cos(rad);
int y = cy + r * sin(rad);
uint16_t color = (a <= activeAngle) ? NV_AMBER : NV_DARK;

tft.drawPixel(x, y, color);
tft.drawPixel(x+1, y+1, color);
}

int newX = cx + r * cos(activeAngle * PI / 180.0);
int newY = cy + r * sin(activeAngle * PI / 180.0);
tft.fillCircle(newX, newY, 5, NV_AMBER);
tft.drawLine(cx, cy, newX, newY, NV_DARK);

tft.setFont(Terminal12x16);
String pct = String(val) + "%";
tft.drawText(cx - (pct.length() * 12) / 2, cy - 8, pct, NV_AMBER);

lastVal = val;
}

// ==========================================
// SETUP
// ==========================================
void setup() {
Serial.begin(115200);

pinMode(FAN_PIN, OUTPUT);
analogWrite(FAN_PIN, 0);

pinMode(ENC_A, INPUT_PULLUP);
pinMode(ENC_B, INPUT_PULLUP);
pinMode(ENC_SW, INPUT_PULLUP);

// ONLY attach interrupts for the rotary turning, NOT the button
attachInterrupt(digitalPinToInterrupt(ENC_A), encISR, CHANGE);
attachInterrupt(digitalPinToInterrupt(ENC_B), encISR, CHANGE);

Wire.begin(I2C_SDA, I2C_SCL);
sgp.begin();

SPI.begin(TFT_SCLK, -1, TFT_MOSI, TFT_CS);
tft.begin();
tft.setOrientation(3); // Rotated 180 degrees

robcoBootSequence();

tft.clear();
tft.setBackgroundColor(NV_BG);
drawCRTBezel();
lastActivityTime = millis();
}

// ==========================================
// MAIN LOOP
// ==========================================
void loop() {
unsigned long currentMillis = millis();

// --- NOISE-RESISTANT BUTTON READING ---
bool currentBtnState = digitalRead(ENC_SW);
if (lastBtnState == HIGH && currentBtnState == LOW) { // Button was pressed down
if (currentMillis - lastBtnPress > 250) { // 250ms debounce ignores fan noise
btnFlag = true;
activityFlag = true;
lastBtnPress = currentMillis;
}
}
lastBtnState = currentBtnState;

// --- Handle Waking / Screensaver Trigger ---
if (activityFlag) {
lastActivityTime = currentMillis;
activityFlag = false;

// Wake up from terminal if we were idling
if (currentTab == 2) {
currentTab = 0; // Go to RADS tab
if (btnFlag) {
btnFlag = false; // Consume the click so it doesn't immediately skip to FAN tab
}
if (encoderCount != 0) {
encoderCount = 0;
}
}
}

// Trigger screensaver after timeout
if ((currentMillis - lastActivityTime > SCREENSAVER_TIMEOUT) && currentTab != 2) {
currentTab = 2;
}

// --- Handle Tab Switch ---
if (btnFlag) {
currentTab = (currentTab + 1) % 3;
btnFlag = false;
}

// --- Draw Layout on Tab Change ---
if (currentTab != lastTab) {
tft.fillRectangle(2, 30, tft.maxX() - 2, tft.maxY() - 2, NV_BG);
drawCRTBezel();

if (currentTab != 2) {
drawGridBackground();
}

drawHeader();
lastTab = currentTab;
lastVocDisplay = 9999;
lastFanSpeed = -1;

if (currentTab == 1) {
tft.setFont(Terminal6x8);
tft.fillRectangle(15, 30, 205, 45, NV_AMBER);
tft.setBackgroundColor(NV_AMBER);
tft.drawText(60, 34, "COOLING ARRAY PWR", COLOR_BLACK);
tft.setBackgroundColor(NV_BG);
} else if (currentTab == 2) {
currentTermLine = 0; // Reset matrix on entering tab
}
}

// ==========================================
// TAB 0: DOSIMETER (MASSIVE BARS)
// ==========================================
switch (currentTab) {
case 0: {
if (currentMillis - lastSensorRead >= 1000) {
lastSensorRead = currentMillis;

uint16_t vocRaw = sgp.measureVocIndex();
vocDisplay = (vocDisplay * 7 + vocRaw) / 8;

if (vocDisplay != lastVocDisplay) {
tft.setFont(Terminal12x16);
tft.fillRectangle(10, 35, 210, 55, NV_BG);
tft.drawText(10, 35, "RADS: " + String(vocDisplay) + " mSv", NV_AMBER);

int maxBars = 6;
int barHeight = 12;
int barSpacing = 16;
int startY = 150;

int activeBars = map(constrain(vocDisplay, 0, 500), 0, 500, 1, maxBars);

for(int i = 0; i < maxBars; i++) {
int currentY = startY - (i * barSpacing);
if (i < activeBars) {
tft.fillRectangle(10, currentY, 210, currentY + barHeight, NV_AMBER);
} else {
tft.fillRectangle(10, currentY, 210, currentY + barHeight, NV_BG);
tft.drawRectangle(10, currentY, 210, currentY + barHeight, NV_DIM);
}
}
lastVocDisplay = vocDisplay;
}
}
break;
}

// ==========================================
// TAB 1: SYS CTRL (MOSFET Fan PWM)
// ==========================================
case 1: {
int readEncoder = encoderCount / 4;
if (readEncoder != 0) {
fanSpeed += (readEncoder * 2);
encoderCount = 0;
fanSpeed = constrain(fanSpeed, 0, 100);
}

if (fanSpeed != lastFanSpeed) {
tft.setFont(Terminal6x8);
tft.drawText(85, 45, "PWM DRIVE", NV_DIM);

drawDetailedGauge(110, 105, 40, fanSpeed);
analogWrite(FAN_PIN, map(fanSpeed, 0, 100, 0, 255));
lastFanSpeed = fanSpeed;
}
break;
}

// ==========================================
// TAB 2: TERMINAL MATRIX (Screensaver)
// ==========================================
case 2: {
if (currentMillis - lastTermUpdate > 750) {
lastTermUpdate = currentMillis;

// Clear area if we loop back to top
if (currentTermLine == 0) {
tft.fillRectangle(10, 32, tft.maxX() - 10, tft.maxY() - 10, NV_BG);
}

tft.setFont(Terminal6x8);
String hexPrefix = "0x" + String(random(0x1000, 0xFFFF), HEX) + " ";
hexPrefix.toUpperCase();

tft.drawText(12, 35 + (currentTermLine * 10), hexPrefix + robcoLogs[random(0, numLogs)], NV_AMBER);

currentTermLine++;

// If we hit the bottom boundary, reset to top for the next tick
if (35 + (currentTermLine * 10) > (tft.maxY() - 15)) {
currentTermLine = 0;
}
}
break;
}
}

delay(5);
}


Libraries and Objects

#include
#include
#include
#include
#include
#include

TFT_22_ILI9225 tft = TFT_22_ILI9225(TFT_RST, TFT_RS, TFT_CS, TFT_LED);
Adafruit_SGP40 sgp;

The core libraries handle the high-speed SPI communication for the display and the I2C protocol for the air quality sensor. The math.h library is included to handle the sine and cosine trigonometry required for drawing the circular PWM fan gauge. The last two lines create the specific instances for the screen and sensor.

Pin Mapping

#define TFT_RST 8
#define TFT_RS 9
#define TFT_CS 7
#define TFT_MOSI 11
#define TFT_SCLK 12
#define TFT_LED 0
#define I2C_SDA 1
#define I2C_SCL 2
#define ENC_A 4
#define ENC_B 5
#define ENC_SW 6
#define FAN_PIN 10

This section defines where each physical component connects to the FireBeetle ESP32-S3. TFT_CS is explicitly moved to Pin 7 to isolate it from FAN_PIN on Pin 10, preventing the MOSFET's rapid PWM signals from scrambling the display's SPI communication.

System State and Timeout Thresholds

int currentTab = 0; // 0=RADS, 1=FAN, 2=TERMINAL (Screensaver)
int lastTab = -1;

unsigned long lastActivityTime = 0;
const unsigned long SCREENSAVER_TIMEOUT = 30000; // 30 seconds
volatile bool activityFlag = false;

These variables define the basic behavior of the RobCo OS navigation. currentTab tracks the active menu. SCREENSAVER_TIMEOUT defines how long the system waits without any encoder rotation or button presses before automatically dropping into the passive terminal screensaver.

Anti-Noise Button Debounce

bool currentBtnState = digitalRead(ENC_SW);
if (lastBtnState == HIGH && currentBtnState == LOW) { // Button was pressed down
if (currentMillis - lastBtnPress > 250) { // 250ms debounce ignores fan noise
btnFlag = true;
activityFlag = true;
lastBtnPress = currentMillis;
}
}
lastBtnState = currentBtnState;

This block replaces the hardware interrupt for the rotary encoder's push button. Because the fan motor creates electromagnetic interference (EMI), reading the button manually in the main loop with a 250-millisecond debounce threshold prevents those invisible voltage spikes from being interpreted as ghost button presses.

Sensor Smoothing

uint16_t vocRaw = sgp.measureVocIndex();
vocDisplay = (vocDisplay * 7 + vocRaw) / 8;

This applies an Exponential Moving Average to the raw SGP40 data. By heavily weighting the previous reading (multiplying by 7) and mixing in the new raw reading, it smooths out rapid fluctuations. This prevents the on-screen dosimeter bars from flickering erratically if the air quality shifts abruptly.

PWM Fan Control

int readEncoder = encoderCount / 4;
if (readEncoder != 0) {
fanSpeed += (readEncoder * 2);
encoderCount = 0;
fanSpeed = constrain(fanSpeed, 0, 100);
}

if (fanSpeed != lastFanSpeed) {
analogWrite(FAN_PIN, map(fanSpeed, 0, 100, 0, 255));
lastFanSpeed = fanSpeed;
}

This translates the rotary encoder's physical rotation into an electrical signal. Since the encoder registers 4 steps per physical click, dividing by 4 scales it to 1 step per click. The map function converts the 0-100% UI value into an 8-bit (0-255) PWM signal required by the ESP32's analogWrite command to drive the MOSFET correctly.

Matrix Terminal Screensaver

if (currentMillis - lastTermUpdate > 750) {
lastTermUpdate = currentMillis;

if (currentTermLine == 0) {
tft.fillRectangle(10, 32, tft.maxX() - 10, tft.maxY() - 10, NV_BG);
}

String hexPrefix = "0x" + String(random(0x1000, 0xFFFF), HEX) + " ";
hexPrefix.toUpperCase();

tft.drawText(12, 35 + (currentTermLine * 10), hexPrefix + robcoLogs[random(0, numLogs)], NV_AMBER);

currentTermLine++;

This acts as the non-blocking engine for the falling text animation. Every 750 milliseconds, it concatenates a random hex memory address with a random lore string from the robcoLogs array. currentTermLine controls the vertical placement. When it detects it will print off the bottom of the screen, it resets to 0 and clears the text area to loop the effect seamlessly.

PROTOTYPE BOARD SETUP

After confirming that everything worked correctly on the breadboard, it was time to convert the circuit into a more permanent setup. Since a breadboard wouldn't be practical to install inside the enclosure, I used one of my previously made PCB breadboards for the final assembly.

I mounted the DFRobot FireBeetle board and the DC-DC buck converter module on the PCB. The sensor, secondary display, and rotary encoder were connected to the main PCB using longer wires, allowing each sensor and module to be installed in their designated positions inside the enclosure.

Following the same wiring diagram as the original breadboard setup, I assembled the circuit on the PCB. The new setup uses the same code and functions exactly like the breadboard prototype, but it's now more compact, reliable, and easier to integrate into the final build.

TERMINAL DESIGN

For this build, I wanted to stay as true to the original game design as possible, with only a few modifications to accommodate the hardware I had available. I maintained the overall shape, proportions, and exterior design of the in-game wall terminal to make it look like a proper replica.

One addition I particularly wanted was a keyboard holder, similar to the one featured on the original wall-mounted terminal. I planned to fit a slim Bluetooth keyboard into the holder, making the terminal a fully functional PC rather than just a decorative replica.

The main design compromise was the front display. In the games, the terminal features a square screen that occupies most of the front panel. However, I'm using a conventional 7-inch HDMI display, which has a rectangular aspect ratio. To work around this, I added a secondary display below the main screen. This smaller display handles the RADS air quality monitor, PC fan speed controls, and terminal-style screensaver. A rotary encoder positioned beside it lets me navigate the interface and control these features.

I also incorporated a few functional details into the side panels. The two protruding elements on the right side house the VOC sensor and the power button, while the left side accommodates Ethernet and USB extenders for easier access to the computer's connectivity.

The keyboard holder has its own little design feature: 3D-printed bolts that secure it in position. This keeps the holder attached to the main enclosure while maintaining the overall aesthetic of the terminal.

For wall mounting, I designed a separate rear cover with two keyhole slots, allowing the entire assembly to be mounted on a wall. The rear cover and main enclosure also feature ventilation openings to help manage heat.

Cooling was another important consideration, given that the LattePanda Mu is essentially a compact PC running inside a custom enclosure. I mounted a PC fan at the top of the terminal to draw cool air into the enclosure and direct airflow across the computing hardware. The heated air then escapes through the ventilation openings in the body, helping keep the system cool during operation.

TERMINAL BODY ASSEMBLY

The terminal body consists of three main parts: the front shed, the base body, and the front display holder. These parts are designed to fit together and are secured in place using four M2 screws, as shown in the assembly diagram.

Fit the front shroud onto the base body from the front, then install the front display holder from the rear of the base body into its designated position. Once all three parts are aligned, the screws hold the assembly securely in place.

LATTEPANDA ASSEMBLY

To mount the LattePanda inside the enclosure, we designed a dedicated mounting frame.

First, the LattePanda Mu Lite Carrier Board is secured to this frame using four M2 screws.

Once assembled, the frame, along with the LattePanda, is positioned inside the right side of the base body, aligning it with the designated mounting holes. Finally, four M2 screws are used to secure the entire assembly in place.

MAIN DISPLAY ASSEMBLY

The main display is positioned at the back of the front display holder. I designed a few ribs to keep the display aligned and securely in place. Once aligned, the display is secured using hot glue.

SECONDARY DISPLAY ASSEMBLY

The secondary display is also mounted from the back of the front display holder. To improve the overall aesthetics, I designed an additional separator part that sits between the main display holder and the secondary display.

First, position the separator in place, followed by the secondary display. Finally, secure the display to the assembly using hot glue.

ROTARY ENCODER & KNOB ASSEMBLY

Position the rotary encoder on the back of the front display holder, aligning it with the two screw bosses. Secure it in place using two M2 screws. Finally, attach the rotary encoder knob to its shaft from the front side of the holder.

MICROCONTROLLER PCB ASSEMBLY

The microcontroller PCB is mounted vertically on the back of the main display holder. I designed two mounting holes on the holder to align with the PCB, allowing us to secure it in place using two M2 screws.

SIDE HOLDERS ASSEMBLY

In the original terminal design, there are two distinctive parts on the left and right sides that serve no functional purpose but add to the mechanical, boxy aesthetic. In my version, I decided to make these parts functional.

On the right side, the holder accommodates the push-button switch and VOC sensor. The switch is secured with its nut, while the VOC sensor is mounted using two M2 screws. Once both components are installed, the entire holder assembly is fitted into its designated position on the main body.

Similarly, the left-side holder is used to mount the USB and Ethernet extender assembly. The USB and Ethernet holder is secured to it using M2.5 bolts, and the complete assembly is then fitted into position on the left side of the main body.

PC FAN & MOSFET POWER CONTROLLER ASSEMBLY

At the top of the main body, on the inside, I've designed dedicated mounting points for the PC fan and MOSFET board, along with mounting holes to secure them using M3 and M2.5 nuts and bolts, respectively.

I've also added a ventilation grille directly above the fan to allow it to draw cool air into the enclosure. Additional ventilation grilles are cut into the main body and rear lid, allowing the hot air to escape and ensuring proper airflow throughout the enclosure.

LID ASSEMBLY

The lid is designed to fit on the back of the main body. I've added four mounting points, with two on each side, allowing us to secure the lid to the main body using four M2 screws.

LOWER SECTION/HOLDER ASSEMBLY

We begin the lower section assembly by positioning the DC barrel jack in the circular cutout provided on the bottom face of the main body.

Around the DC jack, I've designed four mounting holes, each 7 mm in diameter, to secure the first part of the keyboard holder using M6 nuts and bolts.

Next, we position the second part of the keyboard holder, aligning its mounting holes with those on the first part. Using two custom 3D-printed bolts, one on each side, we secure both parts together, completing the keyboard holder assembly.

KEYBOARD GREEBLES PART

The keyboard I chose for this project is a slim, Bluetooth-enabled model. It was perfect for this build because it doesn't require a USB port for connectivity and is lightweight enough to sit comfortably on the 3D-printed holder.

However, the keyboard looked a little too plain and didn't really match the Fallout aesthetic. To fix that, I designed two small greeble parts to give it a more in-game look.

The first is a Vault-Tec name tag featuring the logo, text, and decorative lines. I printed it using two colours, with a white base and the logo, text, and lines in black to create a nice contrast. The second part is a simple black strip printed in black PLA.

The idea is to attach both parts to the keyboard using superglue, covering up the original branding and making the keyboard look more like something Vault-Tec would manufacture.

3D PRINTED PARTS

After finalizing the 3D models, I exported all the parts as mesh files and 3D-printed them, mostly using Hyper PLA. I printed them at a 0.16 mm layer height with 25% infill, using tree supports where necessary.

For the colour scheme, I printed the base body, front shroud, keyboard holder, and lower holder in grey PLA. I printed the back lid, USB and Ethernet port holder, VOC sensor holder, and front display holder in black PLA.

The keyboard holder bolts, rotary encoder knob, and secondary display separator were printed in red PLA to add some contrast to the overall design.

The LattePanda mounting frame doesn't serve a particular aesthetic purpose, so I printed it in whatever filament I had available.

icon TERMINAL.rar 2.65MB Download(0)

LATTEPANDA MU CARRIER BOARD SETUP

First, we prepare the LattePanda carrier board by soldering a DC barrel jack to its DC IN terminals. On the back of the board, we solder the ground wire from the barrel jack to the GND terminal and the positive wire to the positive terminal. This allows us to extend the power input so we can power the LattePanda through the external DC barrel jack.

To test the connection, we plug in the 12V power adapter and press the power button on the carrier board. The heatsink fan starts spinning, confirming that the board is receiving power and the setup is working.

Next, we place the LattePanda carrier board onto the 3D-printed mounting frame and secure it in place using four M2 screws.

LATTEPANDA MU & RAD METER SETUP

Next, we solder two additional wires to the positive and negative terminals of the DC barrel jack. These wires are used to power the secondary display and microcontroller setup alongside the LattePanda.

We connect the positive and negative wires from the DC barrel jack to the VIN and GND terminals of the DC-DC buck converter, respectively. This allows both the main SBC and the secondary display setup to receive power from the same 12V power adapter.

WORKING TEST

We connect the 12V power adapter to the DC barrel jack, and both the secondary display and microcontroller section power on immediately.

During the startup sequence, the programmed boot screen appears. After about 10 seconds, the interface switches to the room air-quality monitor, displaying VOC readings as RADS.

Next, we press the power button on the LattePanda carrier board to turn on the SBC. With the main computer booting up, both systems are now running, confirming that our setup is working as intended.

USB & ETHERNET PORT EXTENDER HOLDER ASSEMBLY

For the USB and Ethernet extenders, we are using panel-mount USB 3.0 and Ethernet extension cables. Both feature mounting holes with threaded screws on the female port side, allowing them to be securely mounted to a surface. In our case, we'll be mounting them on the USB and Ethernet holder.

First, position the USB 3.0 port from the back of the holder, align it with the mounting holes, and secure it in place using two M2.5 bolts.

Similarly, position the Ethernet port from the back of the holder, just below the USB port, and secure it using two M2.5 bolts.

FRONT SECTION—MAIN DISPLAY ASSEMBLY

Position the main display in place from the back of the display holder. Apply hot glue around the corners and along the sides of the display to secure it firmly in place.

FRONT SECTION—RAD METER CIRCUIT PLACEMENT

The circuit is mounted on the back of the front section, just below the display. I've designed two mounting holes in this area to secure the PCB. We align the circuit with these holes and use two M2 screws to fasten it in place.

FRONT SECTION—ROTARY ENCODER ASSEMBLY

Position the rotary encoder in its designated spot, aligning it with the mounting bosses. Secure it in place using two M2 screws.

FRONT SECTION—RAD DISPLAY ASSEMBLY

We begin the secondary display assembly by positioning the small display frame in its designated place on the front section.

Next, we place the secondary display over the frame. Finally, we secure both the frame and the display in position using hot glue.

FRONT SECTION—MAIN DISPLAY WIRING

Next, we connect the positive and negative wires of the secondary display to the 5V and GND terminals on the circuit, respectively. Thanks to the DC-DC buck converter, the display receives its power from the same power supply as the rest of the setup.

MAIN BODY ASSEMBLY

The entire front section is then inserted into the main body from the back. It slides into position, fitting neatly inside the enclosure.

MAIN BODY & FRONT FRAME ASSEMBLY


The main body and front section are positioned over the front frame, bringing all three parts together. We then use four M2 screws, two on each side, to secure the entire assembly.

Thanks to this design approach, a single screw passes through all three parts, securing them together in one go. This makes the assembly process simpler while keeping the entire structure firmly in place.

PC FAN & MAIN BODY ASSEMBLY

To mount the PC fan inside the main body, we first need to install threaded inserts into its mounting holes. For this, we're using M2.5 threaded inserts.

  1. 1. Using tweezers, we position each insert over a mounting hole on the PC fan. Then, with a soldering iron, we gently press the insert into the hole. As the surrounding plastic heats up and softens, the insert sinks into place and becomes securely embedded.
  2. 2. We repeat this process for all four mounting holes.
  3. 3. Next, we position the PC fan inside the main body from the top and secure it in place using four M2.5 bolts.
  4. 4. Finally, we position the MOSFET controller board next to the fan and secure it to the main body using two M2.5 nuts and bolts.

SGP40 & POWER SWITCH HOLDER ASSEMBLY

  1. 1. First, we route the SGP40 VOC sensor through the opening on the right side of the main body.
  2. 2. Next, we install the push-button switch into the power switch holder. From the back, we tighten the switch's nut to secure it firmly in place.
  3. 3. We then position the SGP40 sensor on the back of the power switch holder and secure it using two M2 screws.
  4. 4. Finally, we push the entire power switch holder assembly into the opening on the right side of the main body. Once it fits into position, we secure the assembly using two M2 screws.

LATTEPANDA MU & MAIN BODY ASSEMBLY

  1. 1. The LattePanda Mu is positioned inside the main body, aligned with the mounting holes on the right-side frame.
  2. 2. We then secure it in place using four M2 screws.
  3. 3. Next, we connect the power switch's JST connector to the power switch header on the LattePanda Mu Lite Carrier Board. This allows us to turn the LattePanda Mu on and off using the physical push button mounted on the right side of the terminal.

DC BARREL POWER JACK ASSEMBLY

  1. 1. We now mount the DC barrel jack on the bottom of the main body, where I've designed a dedicated opening for it. We insert the jack into the cutout and secure it from the inside using the provided nut.
  2. 2. Next, we reconnect the LattePanda's VCC and GND wires to the positive and negative terminals of the DC barrel jack. We also connect the DC-DC buck converter circuit to the same terminals in parallel.

This allows a single DC barrel jack to supply 12V power to the LattePanda Mu while powering the secondary display and microcontroller electronics through the buck converter.

MAIN DISPLAY & LATTEPANDA HDMI CONNECTION

  1. 1. To connect the HDMI display to the LattePanda carrier board, we use a short, flat HDMI cable.
  2. 2. We first connect the cable to the display and then to the HDMI port on the carrier board.
  3. 3. Finally, we tuck the excess cable length inside the main body to keep the wiring neat and compact.

LOWER HOLDER ASSEMBLY


The lower holder is attached to the bottom of the main body. We position it underneath, aligning its four mounting holes with the corresponding holes on the body. Finally, we use four M6 nuts and bolts to secure both parts together.

USB & ETHERNET PORT EXTENDER HOLDER WITH LATTEPANDA

The USB 3.0 and Ethernet extender cables are routed through the openings on the left side of the main body, from the outside to the inside. The entire holder assembly is then pressure-fitted into its designated position.

Next, we connect the USB extender to a USB 3.0 port on the LattePanda carrier board and plug the Ethernet cable into the board's Ethernet port.

BACK LID ASSEMBLY

Next, we position the main lid on the back of the body and secure it in place using four M2 screws.

KNOB ASSEMBLY

We position the 3D-printed knob over the rotary encoder shaft from the front of the main section. With a gentle push, the knob fits snugly onto the shaft and stays securely in place.


KEYBOARD HOLDER ASSEMBLY

Next, we position the keyboard holder assembly in its designated place. Using two custom 3D-printed bolts, one on each side, we secure the holder in place, completing the keyboard holder assembly.

HARDWARE- KEYBOARD

The last piece of hardware I got for this project was a slim Bluetooth keyboard. I picked it up for pretty cheap on Amazon, and I really like its minimal design. It came in white, though I would have preferred grey to match the terminal, but it'll do the job just fine.

The battery life has also been pretty decent. I've been using it for about a month now and haven't had to charge it yet!

KEYBOARD GREEBLE PARTS ASSEMBLY

The keyboard looked a little too vanilla for this build, so I designed two small greeble parts to give it a more Fallout-inspired aesthetic. The first is a Vault-Tec logo tag, which I printed using dual-colour filament, while the second is a simple black strip printed in black PLA.

Using superglue, I attached the black strip just below the spacebar, followed by the Vault-Tec logo tag over the keyboard's original logo. The idea was to cover up the original branding and make the keyboard look like something Vault-Tec or RobCo would actually manufacture.

RESULT

And here's the result of this long but super fun build: a Fallout terminal built from scratch! It's designed as a faithful replica of the wall-mounted terminals manufactured by RobCo, which you'll find throughout the Fallout games, including in Vaults and locations associated with Mr. House.

As the name suggests, this is a wall-mounted terminal, so let's start by getting it onto the wall. I've designed two keyhole slots on the back of the enclosure. We simply drill two holes into the wall, install the screws, and hang the terminal onto them. Once mounted, it sits securely in position.

For the keyboard, we simply place our slim Bluetooth keyboard onto the holder, where it sits neatly in place, ready to use.

For power, I'm using a 12V, 4A power adapter. We connect it to the DC barrel jack located at the bottom of the terminal. Once plugged in, the main display turns blue, and the secondary display powers on as well, complete with a custom startup animation I added to give it that authentic terminal feel.

Next, we press the power button on the right side of the enclosure, and the LattePanda boots into the desktop in just a few seconds, thanks to the M.2 NVMe Gen 3 SSD.

And just like that, we've got ourselves a fully functional Fallout wall terminal!

AQI MEASUREMENT

Thanks to the SGP40 VOC sensor, the terminal can monitor changes in room air quality in real time. And since this is a Fallout project, I decided to display the readings as RADS, inspired by the radiation levels shown in the games.

I've added a simple bar indicator to the interface that changes according to the sensor readings. When the reading is below 100, only one bar is displayed. Every time the reading crosses another 100-point threshold, an additional bar lights up. So, crossing 100 activates the second bar, crossing 200 activates the third, and so on.

To test this feature, I lit an incense stick and placed it near the VOC sensor, located on the right side of the terminal. As the smoke reached the sensor, the VOC readings increased, and I could see the RADS values climb on the display. The indicator bars also increased as the readings crossed each threshold.

It's a simple addition, but it makes monitoring air quality a lot more fun and fits perfectly with the Fallout aesthetic.

PC FAN SPEED CONTROL


The next feature is the PC fan controller, which we can access by pressing the rotary encoder. Once we're in this menu, rotating the knob lets us increase or decrease the fan speed.

The interface displays the current fan speed as a percentage using a clean, dial-style UI. This gives us a simple way to adjust the cooling fan manually while keeping the controls and visual design consistent with the rest of the terminal.

SCREENSAVER

The last feature is the terminal screensaver, which automatically activates after 30 seconds of inactivity. Instead of leaving the display blank, I wanted it to look like an actual computer terminal running background processes.

The screensaver displays randomly generated hexadecimal addresses alongside text inspired by Fallout, including system diagnostics, memory checks, access logs, and RobCo system messages. New lines appear every 750 milliseconds, gradually filling the display with terminal-style output. Once the screen reaches the bottom, the text clears, and the sequence starts again.

PLAYING GAMES- FALLOUT NEW VEGAS

Now comes the most fun part of this project: playing games! As a Fallout fan, it would be a crime not to play Fallout 3 or Fallout: New Vegas on a Fallout-themed computer.

I installed Fallout: New Vegas through Steam, connected an Xbox controller to the terminal, and fired up the game. And it worked perfectly! I spent hours playing, and honestly, this might be one of the most aesthetically pleasing ways to play New Vegas.

The LattePanda Mu with its Intel Core i3-N305 is a great fit for older games like this. I'm pretty sure it can handle Fallout 4 as well, but let's not talk about Fallout 4 or that bloody Fallout 76. Anyway, I had a lot of fun with this, and the LattePanda Mu can handle more demanding games too, as I demonstrated in an earlier project.

If you want to see what else this little computer can do, check out my previous project from the link below.

https://www.hackster.io/Arnov_Sharma_makes/bazzite-on-lattepanda-mu-setting-up-guide-4c93b4

SIDE USB & ETHERNET PORT

For a wall-mounted terminal like this, expandability is important, especially if you want to use it to run a server or host services on the device. Having an Ethernet port and USB 3.0 connectivity is essential for that. The system also supports Bluetooth, which I use to connect a wireless keyboard and mouse, but I still wanted physical ports for connecting additional devices.

To make that possible, I added Ethernet and USB 3.0 extenders to the left side of the base body. This lets me connect USB devices directly to the terminal.

In my case, I've connected an external hard drive, but you could also use the USB port for other peripherals. The Ethernet extender lets me connect a LAN cable for a wired network connection.

CONCLUSION

And that brings us to the end of my Fallout Terminal project!

This is a fully functional, Fallout-themed wall terminal powered by the LattePanda Mu. It features a custom 3D-printed enclosure, a secondary display that monitors air quality (displayed as RADS) and controls fan speed, and a screensaver inspired by the terminals in the Fallout games. But that's not all; it can also run desktop applications and play games like Fallout: New Vegas!

This was a pretty big project, and I had a lot of fun bringing it to life.

There are a couple of upgrades I'm planning for this project in the near future. The first is a proper paint job. For this build, I thought using different coloured filaments would be enough, but the finished terminal still looks a little too 3D-printed. I want to give it a more realistic, aged metallic finish, closer to the terminals we see in the Fallout games.

The second upgrade is adding an onboard battery pack. Right now, the terminal relies on an external power adapter, but I'd love to make it completely self-contained by adding a battery capable of powering both the electronics and the LattePanda Mu itself.

The project is now complete, and everything you need to build your own is included in this article, from the instructions and design files to the source code. If you have any questions or run into any issues while building it, drop a comment below, and I'll do my best to help.

Special thanks to DFRobot for supporting this project! Do check them out for a wide range of development boards, modules, sensors, robotics kits, single-board computers, and other electronics components for your next DIY project. Visit DFRobot and see what you can build!

Until then, stay curious and keep making. Peace!

License
All Rights
Reserved
licensBg
0