The Millennium Project breathes new life into the Nortel Millennium public telephone by replacing much of its original electronics with modern hardware and software. The project integrates a Raspberry Pi Zero 2 W, two Arduinos, and a VoIP software stack while retaining all the original hardware, including the display, keypad, coin acceptor, magstripe reader, handset, and ringer speaker.
- Overview
- Architecture
- Features
- Directory Structure
- Setup
- Configuration
- Development
- Resources
- License
This project reimagines the functionality of the Nortel Millennium telephone by combining modern hardware and software while preserving its iconic hardware features:
- Raspberry Pi Zero 2 W: Manages the system, runs the VoIP stack, and interfaces with the original hardware.
- Arduinos: Control the keypad, display, and other peripherals.
- Custom PCB: Consolidates the connections between components.
- Host Software: Includes a daemon, plugin system, web dashboard, and ALSA audio configuration.
- Preserved Hardware: The original display, keypad, coin acceptor, magstripe reader, handset, and ringer speaker are retained and functional.
┌─────────────────────────────────────────────────────┐
│ Raspberry Pi Zero 2 W │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │
│ │ Daemon │ │ Baresip │ │ Web Dashboard │ │
│ │ │──│ (VoIP) │ │ :8081 │ │
│ │ Plugins: │ └──────────┘ │ - Phone state │ │
│ │ - Phone │ │ - Plugin switching │ │
│ │ - Fortune│ ┌──────────┐ │ - OTA updates │ │
│ │ - Jukebox│ │ ALSA │ │ - Health/metrics │ │
│ │ │ │ Audio │ └───────────────────┘ │
│ └────┬─────┘ └──────────┘ │
│ │ USB Serial (9600 baud) │
└───────┼─────────────────────────────────────────────┘
│
┌───────┴──────────────────────────────────────────┐
│ Custom PCB (phonev5) │
│ │
│ ┌─────────────────┐ I2C ┌───────────────┐ │
│ │ Display Arduino │◄────────│ Keypad Arduino │ │
│ │ (millennium_beta)│ │(millennium_alpha)│
│ │ │ │ │ │
│ │ ► VFD Display │ │ ► 4x7 Keypad │ │
│ │ (parallel 8b) │ │ ► MagStripe │ │
│ │ ► Coin Validator │ │ ► Hook Switch │ │
│ │ (SoftSerial) │ │ │ │
│ └──────────────────┘ └─────────────────┘ │
└──────────────────────────────────────────────────┘
Data flow: Keypad/hook/card events originate on the Keypad Arduino, are sent via I2C to the Display Arduino, which forwards them to the Raspberry Pi over USB serial. The daemon processes events through an event processor, dispatches them to the active plugin, and sends display updates back down the same path. VoIP calls are handled by Baresip with audio routed through ALSA (dmix + route plugins for mono channel splitting).
- Pay phone operation: Insert coins, dial numbers, make VoIP calls — authentic payphone experience
- Plugin system: Swap between Classic Phone, Fortune Teller, and Jukebox modes at runtime
- Emergency calls: Dial 911, 311, or 0 without coins (configurable free numbers)
- Magstripe card support: Swipe a registered card for free calling or admin access
- Web dashboard: Real-time phone state, plugin switching, health monitoring via WebSocket
- OTA updates: Check for and apply updates from the web dashboard (git pull, build, restart)
- Audio tones: DTMF, dial tone, busy tone, coin tone, ringback via ALSA
- Idle timeout: Automatically resets phone state after configurable inactivity period
- State persistence: Saves and restores coin balance and plugin state across restarts
- Health monitoring: Tracks serial connection, SIP registration, and daemon activity
- Metrics collection: Counters for calls, coins, keypresses, card swipes
- Version tracking: Build version and git hash displayed on dashboard
- Scenario testing: Simulator-based integration tests with simulated time
Arduino/: Arduino sketches for the keypad and display microcontrollers, plus a Makefile for building and flashing.case/: 3D model files (.blendand.stl) for a custom enclosure. See case/README.md for dimensions and print settings.pcb/: KiCad schematic, PCB layout, BOM, and Gerber files for the custom PCB (phonev5).HARDWARE.md: Physical assembly reference — USB topology, cable routing, power budget, and component list.host/: Raspberry Pi software:daemon.c— Main daemon loop and event routingplugins/— Plugin implementations (classic_phone, fortune_teller, jukebox)web_server.c— HTTP server with dashboard and REST APIaudio_tones.c— Tone generation (DTMF, dial tone, etc.)updater.c— OTA update checker and appliersimulator.c— Test runner with scenario file support and simulated timetests/— Unit tests and scenario test filessystemd/— Service file for the daemondaemon.conf.example— Example configuration fileasoundrc.example— ALSA audio configuration for mono channel splitting
Navigate to the Arduino/ directory and follow the instructions in its README to build and flash firmware to the microcontrollers.
The PCB files are in the pcb/ directory. Upload the Gerber files to a PCB fabricator like JLCPCB for manufacturing. See the pcb README for details.
Follow the setup guide for full step-by-step instructions, or use the quick install:
cd host
make daemon # Build the daemon
sudo make install # Install to /usr/local/bin and set up systemdSee the host README for dependency installation (Baresip, ALSA) and audio configuration.
Copy and edit the configuration file:
sudo cp host/daemon.conf.example /etc/millennium/daemon.conf
sudo nano /etc/millennium/daemon.confSee Configuration below for all available options.
The daemon reads configuration from /etc/millennium/daemon.conf. See host/daemon.conf.example for a complete annotated example. Key settings:
| Key | Default | Description |
|---|---|---|
hardware.display_device |
/dev/serial/by-id/usb-Arduino_LLC_Millennium_Beta-if00 |
Serial device for display Arduino |
hardware.baud_rate |
9600 |
Serial baud rate |
call.cost_cents |
50 |
Cost per call in cents |
call.timeout_seconds |
300 |
Maximum call duration |
call.free_numbers |
911,311,0 |
Comma-separated numbers that bypass coin requirement |
call.idle_timeout_seconds |
60 |
Seconds of inactivity before phone resets |
card.enabled |
true |
Enable magstripe card support |
card.free_cards |
(empty) | Comma-separated card numbers for free calling |
card.admin_cards |
(empty) | Comma-separated card numbers for admin access |
web_server.enabled |
true |
Enable the web dashboard |
web_server.port |
8081 |
Web dashboard port |
system.source_dir |
/home/matzen/millennium |
Source directory for OTA updates |
cd host
make test # Build simulator + unit tests, run both
make daemon # Build the full daemon (requires Baresip/libre on the Pi)
make clean # Remove all build artifactsTests run on any platform (macOS, Linux) without Baresip or ALSA — the simulator stubs out hardware dependencies:
cd host
make testThis runs:
- Unit tests: Config, daemon state, plugins, emergency numbers, card config, updater
- Scenario tests: Full integration tests (basic call, timeout, emergency, card swipe, hook lifecycle, state persistence, display scrolling, idle timeout)
- Create
host/plugins/your_plugin.c - Implement handler functions for the events you care about (coin, keypad, hook, call state, card)
- Register with
plugins_register()in aregister_your_plugin()function - Call the registration function from
plugins_init()inplugins.c - Add the
.ofile to the Makefile build targets
- C89 for most files (
-std=c89) — no mixed declarations/code - C99 for files that need it (
-std=c99) — simulator, jukebox (block-scoped declarations) -Wall -Wextra— all warnings enabled, treat them seriously- No unnecessary comments — code should be self-documenting
This project draws on several resources for understanding and interfacing with the Nortel Millennium telephone's hardware:
-
General Millennium Payphone Documentation The Millennium Payphone Wiki provides extensive documentation on the phone's hardware and protocols.
- Coin Validator Protocol: Used the documented higher-level protocol to interface with the coin validator.
- EEPROM Data: Referenced for configuring the coin validator to accept Canadian currency.
-
Display Documentation A detailed discussion on the Noritake CU20026SCPB-T23A VFD display can be found in this HardForum thread. This was essential for understanding and controlling the display.
-
Device Pinouts The New Fire Millennium Payphone Page includes pinout diagrams for various components, which were critical when developing the Arduino sketches.
-
Millennium Phone Source The Millennium phone used in this project was sourced from Ballard Reuse in Seattle, a store specializing in salvaged and reusable materials.
This project is licensed under the MIT License. See LICENSE for details.