infra_wiki

Screen callers on the office phone

How the folly PBX screens unknown callers on line 4 and hands them to the ElevenLabs troll agent, how to add or remove a contact who skips the screen, what the star codes do, how to render the prompts, and how to test the screen.

The folly PBX answers unknown callers on line 4 and asks them to press 5. This runbook adds and removes a contact who skips that screen, lists the star codes, renders the prompts and tests the screen after a merge. Operate the office phone covers the lines, the trunks and a failed call.

How the screen works#

  • Only line 4 screens. Its trunk carries SCREEN=yes in clusters/folly/apps/pbx/config/pjsip.conf. Line 1 does not screen: the handset dials 911 on it, and 911 calls back there.
  • config/inbound.conf rings a contact, an unscreened line, and a screened line within an hour of a 911 from the handset. It answers anyone else and plays the press-5 prompt twice. A 5 rings the handset as [5] <number>. Any other answer goes to config/agent.conf, which dials Jonathan, the troll agent on ElevenLabs, for up to ten minutes.
  • Before it dials Jonathan, config/agent.conf transfers the caller with a SIP REFER to the voip.ms internal extension in PBX_TROLL_EXTENSION, which belongs to the 168847_elevenlabs sub-account that the offsite PBX registers. voip.ms moves the call and folly drops out of it. Folly dials Jonathan itself only when voip.ms refuses the transfer, and the sinks below hold whoever he does not take. A transfer voip.ms challenges for credentials, or does not finish in 45 seconds, goes to the sinks instead, since voip.ms may still move that caller. PBX_TROLL_EXTENSION is empty in pbx-env until the owner sets it (Turn on the offsite hand-off), and while it is empty folly keeps every call.
  • A call that rings the handset buffers the caller's audio with an adaptive jitterbuffer before the handset hears it. The prompt and Jonathan's calls do not.
  • Folly's daily count covers both sites: it counts each call Jonathan answers on folly and each call that ends on folly during or after its transfer, and at twenty folly neither transfers nor dials. Folly's two-call cap covers only the calls folly keeps; offsite and ElevenLabs cap the ones it hands on.
  • When the agent is off, is on two calls, has taken twenty today, or does not answer in 60 seconds, config/spam.conf holds the caller for up to ten minutes in the Endless Queue or with Robo-Lenny. Two callers hear the prompt at once and three are held: the next stranger gets a busy signal, and the next held caller hears goodbye.
  • A contact rings as [OK] <name> with the Friend ring; a caller who pressed 5 rings with the Human ring. Both are Ring10 and Ring11 in provision/cathy.xml.
  • Each step logs one line, pbx-event kind=<kind> line=<line> caller=<digits>, from config/events.conf. kind=troll is a hand-off, kind=held sink=troll a call Jonathan answered, and why= on kind=troll-miss says why he did not take one. The PBX dashboard counts kind=screened, and so does the Smiirl counter.
  • kind=troll site=offsite is a transfer attempt, and kind=troll-miss site=offsite why=<status> one that did not happen, which folly's own kind=troll follows when folly dials Jonathan itself: why=FAILURE-<code> names the SIP code voip.ms answered with, why=FAILURE-500 is also a REFER voip.ms never answered, why=FAILURE-1 a REFER that Asterisk could not send or whose subscription ended with no final status, why=FAILURE with no code a transfer Asterisk could not start, and why=timeout one voip.ms does not finish in 45 seconds.

Before you start#

Turn on the offsite hand-off#

note

A caller voip.ms takes leaves folly for good: if the offsite PBX does not answer the extension, folly cannot fall back to its own Jonathan or the sinks. Set the extension only while pjsip show registrations on offsite lists the one registration clusters/offsite/apps/pbx/config/pjsip.conf declares as Registered. Each row names the registration and its auth, never the sub-account; pjsip show registration <name> shows 168847_elevenlabs in its client_uri.

  1. In the voip.ms portal, turn on the internal extension of the sub-account 168847_elevenlabs and note the number voip.ms assigns.

  2. Set PBX_TROLL_EXTENSION to that number in the pbx-env literals in clusters/folly/apps/pbx/kustomization.yaml, open a PR, and merge it when core show channels shows no active call. The merge rolls the pod.

    Result: after the pod starts, dialplan show dial@agent shows Transfer(sip:<extension>@...).

  3. Call line 4 from a number that is not a contact, and press nothing.

    Result: Jonathan answers. The folly log has pbx-event kind=troll with site=offsite and no kind=troll-miss with site=offsite, and core show channels on folly shows no active call.

Add a contact#

note

A contact's name and number are in the PBX log in VictoriaLogs each time they call. The item is not the only copy.

  1. In the homelab vault, open the item pbx contacts, or create it as a Secure Note. Keep one item with that title.

  2. Add a text field. Make the label the name the handset shows and the value the ten-digit number, in any North American format. Keep every label in the item unique.

    note

    External Secrets fails the item on a repeated label or a second item with the title, and then no contact skips the screen.

  3. Make sure the ExternalSecret has synced. It refreshes every hour, and the pod's copy of the Secret follows within a minute or two.

    kubectl --context folly -n pbx get externalsecret pbx-contacts

    Result: STATUS is SecretSynced.

  4. Reload the dialplan. Asterisk reads the contacts file when the dialplan loads, and nothing restarts the pod for this Secret.

    kubectl --context folly -n pbx exec deploy/pbx -c asterisk -- \
      /bin/asterisk -C /etc/asterisk/asterisk.conf -rx 'dialplan reload'

    Result: Dialplan reloaded. dialplan show globals then lists one CONTACT_<ten digits> line per contact.

Remove a contact#

note

A reload sets the globals the file still holds and clears none; only a restart forgets one. Delete the field, not the item: External Secrets keeps the Secret of a deleted item.

  1. Delete the contact's field from the pbx contacts item.

  2. Make sure the ExternalSecret has synced, as in step 3 of Add a contact.

  3. Make sure core show channels shows no active call, then restart the pod.

    kubectl --context folly -n pbx rollout restart deploy/pbx

    Result: after the pod starts, dialplan show globals lists no CONTACT_ line for the number.

Star codes#

Dial a code from any line. config/handset-codes.conf sends each one to config/toybox.conf.

CodeWhat it does
*20Echo test
*21Talking clock, in the lab's time zone
*22A Morse greeting
*23Milliwatt tone
*24The monkeys
*25Listen to the held callers and the agent's calls; an error tone if there are none
*26The number of callers the sinks have held since the pod started
*27Robo-Lenny
*28The Endless Queue
*29Jonathan, the troll agent, in desk mode; an error tone if he does not answer

Render the prompts#

clusters/folly/apps/pbx/sounds/lines.yaml declares each prompt. mise run pbx:voices renders the lines that have no .ulaw beside them with ElevenLabs text-to-speech, and reads the key from the 1Password item rowbutt elevenlabs api key. Pass line ids to render those, or --all. Add each new file to pbx-sounds in clusters/folly/apps/pbx/kustomization.yaml; the task names a file that is missing there. The prompts share a 700 KiB budget, and mise run pbx:check fails if the dialplan plays a file the ConfigMap does not carry.

Test the screen#

Place these calls after the pod rolls and the phone has fetched its profile (Change the phone).

  1. Call line 4 from a contact's number.

    Result: the handset shows [OK] <name> and plays the Friend ring.

  2. Call line 4 from another number and press 5.

    Result: the prompt plays once, then the handset shows [5] <number> and plays the Human ring.

  3. Call line 4 from that number again and press nothing.

    Result: Jonathan answers. With PBX_TROLL_EXTENSION set, offsite carries the call and *25 on the handset has nothing to play; with it empty, *25 plays the call.

  4. Dial *29.

    Result: Jonathan answers and plays up the demo.

  5. Dial *20 from each line.

    Result: the phone plays the echo test. A reorder tone means the line's Dial_Plan on the phone does not send *xx.

  6. Dial 933 from line 1.

    Result: voip.ms reads the e911 address back.

If something goes wrong#

SymptomCauseAction
A caller on line 4 hears "press five", talks to Jonathan, or is held in the queue or with LennyLine 4 screens every caller who is not a contact.Add them as a contact, or tell them to press 5.
Screened callers are held, never trolled, and the log has why=offThe pod started without the pbx-elevenlabs Secret, which it reads only at start.Make sure kubectl --context folly -n pbx get externalsecret pbx-elevenlabs shows SecretSynced, then restart the pod when no call is up.
The log has why=dailyThe PBX's own cap: twenty calls today, counting each call Jonathan answers on folly and each call folly hands to offsite.Wait for the next day.
The log has why=CONGESTION, CHANUNAVAIL, BUSY or NOANSWERElevenLabs refused the call, was unreachable, or did not answer in 60 seconds. How it treats a call over its own limits is unverified; with call queueing on, it plays hold audio.Read the agent's SIP (Diagnose a failed call).
A caller on line 4 gets a busy signalTwo callers are in the screen.Wait, or raise the cap in config/inbound.conf.
The log has kind=troll-miss site=offsite why=FAILURE-<code>, then Jonathan answers from follyvoip.ms answered the REFER, or a NOTIFY about the call to the extension, with that SIP code, and folly kept the call.Read the REFER and its NOTIFYs (Diagnose a failed call). Check PBX_TROLL_EXTENSION against the portal and that the offsite PBX is registered.
The log has why=FAILURE-401 or FAILURE-407 with site=offsite, and the caller is held in a sinkvoip.ms challenged the REFER. Asterisk sends it again with vms-1994's credentials after Transfer() has given up, so folly holds the caller in a sink rather than dial Jonathan, and voip.ms may still move them.Read the second REFER's answer. When voip.ms takes it, the transfer works after a challenge.
Screened callers never reach offsite, and the log has no site=offsitePBX_TROLL_EXTENSION is empty.Turn on the offsite hand-off.
The log has kind=troll site=offsite and nothing after it, the caller is dropped, and offsite logs no kind=inboundvoip.ms accepted the REFER and reported the call to the extension ringing and answered within the same second, so folly hung up, but no INVITE reached the offsite registration. This is what the extension does today.Empty PBX_TROLL_EXTENSION, so folly dials Jonathan itself. Check the sub-account's internal extension in the portal, then place an internal call from the handset to it and watch for kind=inbound on offsite before setting it again.
A screened caller hears silence after the prompt, then Jonathan or a sinkvoip.ms accepted the REFER, and Transfer() plays nothing while it waits for the NOTIFY that reports the call to the extension up, for up to 45 seconds.Read the NOTIFYs. A silence ending in why=FAILURE-<code> is a call to the extension that failed, and one ending in why=timeout gets no final NOTIFY.
pbx-contacts shows SecretSyncedErrorThe pbx contacts item is missing, has a repeated label, or shares its title with another item. No alert covers it.Fix the item. The ExternalSecret retries within the hour.
A new contact still hears the screenAsterisk read the contacts file at its last dialplan load, or the value is not ten digits and the template skipped it.Run dialplan reload after the Secret has caught up. If dialplan show globals has no line for it, fix the value.
A removed contact still skips the screenA reload keeps the global, or the item was deleted and its Secret kept.Restart the pod. If the item is gone, recreate it without the field first.
[OK] or [5] shows with the line's usual ringThe phone has not fetched the profile with Ring10 and Ring11.Resync the phone (Change the phone).

Edit on GitHub

↑↓ move ↵ open esc close