Give your family a home phone for calling friends without handing over a smartphone. Open-source, self-hosted calling with room routing, bedtime rules and your choice of provider.
翻訳待ち
ソーシャルGo · AstroGitHub @ericdmooreオンライン所有者未確認
Have you tried it? One tap is enough. No sign-in needed.
The home phone, with a lobby and bouncer, my kids never saw it coming.
翻訳待ち
Loading the full README.
Read the full README ↗A programmable home phone. Known callers ring the whole house; everyone else meets the bouncer. Runs happily on a spare Raspberry Pi.
The home phone my kids never saw coming.
A programmable home phone. A Raspberry Pi is the obvious host — cheap, silent, and it only has to do one thing — but anything that runs Asterisk will do.
Known callers hear "Welcome, I'll connect you" and the whole house rings. Everyone else meets the lobby: dial a 6-digit extension, or the bouncer says "Good day" and hangs up.
No port forwarding. No inbound firewall rules. The Pi registers outbound to a VoIP provider and the trunk rides that registration back in.
┌─ known caller ──→ "Welcome, I'll connect you" ──→ 🔔 whole house
inbound call ──→ ┤
└─ unknown ───────→ "Welcome to the phone lobby.
Please dial an extension." ──→ ✓ that handset rings
└─→ ✗ "Good day." *click*
Repo metadata — description: A programmable home phone. Known callers
ring the whole house; everyone else meets the bouncer. Runs happily on a spare
Raspberry Pi. · topics: asterisk voip sip raspberry-pi pbx homelab
self-hosted home-automation golang telephony
Asterisk does SIP, RTP, and DTMF detection. It is very good at those and there is no reason to reimplement them. Everything about who gets in lives in doorman, a single static Go binary that talks to Asterisk over ARI on loopback — one call is one goroutine, and the caller hanging up cancels a context that unwinds everything.
graph LR
subgraph provider["VoIP.ms"]
T["SIP trunk<br/>(registration-based)"]
end
subgraph pi["Raspberry Pi"]
A["Asterisk<br/>SIP · RTP · DTMF"]
D["doorman<br/>one static Go binary"]
P[("policy.toml<br/>allow-list · PINs")]
W["prompts/*.wav<br/>pre-rendered"]
A <-->|"ARI · ws + REST<br/>127.0.0.1:8088"| D
D --> P
A --> W
end
subgraph lan["LAN"]
H["Grandstream handsets<br/>register inward"]
end
T <-->|"outbound REGISTER<br/>+ 30s keepalive"| A
A <--> H
The only connection that crosses the WAN is one the Pi opens itself.
Beyond the lobby, the handsets get the classic PBX toolkit — paging/intercom
(500), call parking (700), a family conference (600), BLF busy lamps, hold
music, voicemail with email delivery (*97), ringer ladders that escalate
kids → adults → voicemail, per-extension afterhours windows, an outbound
console (*4) for calling out as another of your numbers, and an optional
dial-555 hook into Home Assistant's Assist. There is also an optional webhook
that fires when the house starts ringing and again when a call ends, so Home
Assistant can announce "call from Grandma" on a speaker — doorman sends an
event and knows nothing about speakers, so every target HA supports comes
free. Details in docs/RUNBOOK.md.
sequenceDiagram
autonumber
participant C as Caller
participant A as Asterisk
participant D as doorman
participant H as Handsets
C->>A: INVITE (via trunk)
A->>A: Answer()
A->>D: StasisStart
D->>D: normalise caller ID → E.164
D->>D: look up in allow-list
alt Known caller
D->>C: ▶ "Welcome, I'll connect you"
D->>A: create bridge + ring indication
D->>H: originate all house endpoints
H-->>D: StasisStart (first to answer wins)
D->>A: bridge winner, hang up the rest
Note over C,H: 🔊 talking
else Unknown caller
D->>C: ▶ "Welcome to the phone lobby…"
loop 10s for first digit, 3s between
C->>A: DTMF
A->>D: ChannelDtmfReceived
end
alt Valid 6-digit extension
D->>H: ring that handset/group
else Timeout — whatever [line] on_no_input says
D->>C: ▶ "Good day." / take a message / ring the house
else PIN attempts exhausted
D->>C: ▶ "Good day."
D->>A: hangup
end
end
stateDiagram-v2
[*] --> starting
starting --> greeting_known: on allow-list
starting --> greeting_lobby: unknown
starting --> dismissing: rate limited
greeting_known --> ringing: greeting ends
greeting_known --> collecting: caller barges in
greeting_lobby --> collecting: greeting ends
greeting_lobby --> collecting: caller barges in
collecting --> ringing: valid PIN
collecting --> collecting: wrong PIN, attempts left
collecting --> ringing: silence, known caller
collecting --> ringing: silence, on_no_input = ring-house
collecting --> voicemail: silence, on_no_input = voicemail
collecting --> dismissing: silence, on_no_input = dismiss
collecting --> ringing: attempts exhausted, known caller
collecting --> dismissing: attempts exhausted
ringing --> bridged: handset answers
ringing --> voicemail: nobody home, mailbox configured
ringing --> dismissing: nobody home
bridged --> [*]: hangup
voicemail --> [*]: released to the dialplan
dismissing --> [*]: "Good day"
Two edges are worth reading twice. The known-caller greeting is a dial
window — press a digit over it to reach one room rather than the whole
house — and pressing nothing rings the house the moment it ends, with no
silence added. And nothing at the keypad can cost an allow-listed caller
the house: every one of their exits from collecting rings it.
Which of the three silence edges applies is [line] on_no_input, and it
defaults to dismiss — what the lobby has always done.
This is the beginning of the README. Read the rest at the original. Read the full README ↗