; --------------------------------------------------------------------------- ; Hold Slayer lab dialplan — a fake bank phone tree ; --------------------------------------------------------------------------- ; Each extension is one scenario Hold Slayer must handle. Everything here is ; deterministic on purpose: real hold music varies per call, so a classifier ; regression on the PSTN is indistinguishable from noise. Against a fixed ; prompt the answer is binary. ; ; Hold Slayer dials these as `number` with SIP_TRUNK_HOST pointing here. ; --------------------------------------------------------------------------- [globals] ; Lab-generated audio (tests/lab/sounds/, built by generate.py). The Asterisk ; image ships with no sounds at all, and these are synthesised from a fixed ; seed so the classifier sees byte-identical input on every run. ; lab-speech -> must classify LIVE_HUMAN ; lab-music -> must classify MUSIC ; lab-silence -> must classify SILENCE GREETING=lab-speech INVALID=lab-speech [hold-slayer-lab] ; --- 1001: immediate answer, speech, hangup ------------------------------- ; Baseline. Proves INVITE→200→ACK→RTP→BYE and that audio flows both ways. ; The classifier should report LIVE_HUMAN throughout. exten => 1001,1,NoOp(LAB 1001: immediate answer) same => n,Answer() same => n,Wait(1) same => n,Playback(${GREETING}) same => n,Playback(lab-speech) same => n,Wait(20) same => n,Playback(lab-speech) same => n,Hangup() ; --- 1002: IVR menu, branches on DTMF ------------------------------------- ; THE important one. Proves send_dtmf genuinely emits RFC 2833 and that ; Asterisk receives the digits — currently a no-op in MockSIPEngine. ; Press 1 → accounts (answers as human). Press 2 → cards (hold, then human). exten => 1002,1,NoOp(LAB 1002: IVR menu) same => n,Answer() same => n,Wait(1) same => n,Set(TRIES=0) same => n(menu),Background(lab-speech) same => n,WaitExten(8) same => n,Set(TRIES=$[${TRIES} + 1]) same => n,GotoIf($[${TRIES} < 3]?menu) same => n,Playback(lab-speech) same => n,Hangup() ; Option 1 — straight to a "human" exten => 1,1,NoOp(LAB 1002: caller pressed 1 -> accounts) same => n,Playback(lab-speech) same => n,Playback(lab-speech) same => n,Wait(15) same => n,Hangup() ; Option 2 — hold queue, then a "human" exten => 2,1,NoOp(LAB 1002: caller pressed 2 -> cards, hold) same => n,Playback(lab-speech) same => n,Playback(lab-music) same => n,Playback(lab-speech) same => n,Wait(15) same => n,Hangup() exten => i,1,NoOp(LAB 1002: invalid entry) same => n,Playback(${INVALID}) same => n,Goto(1002,menu) exten => t,1,NoOp(LAB 1002: entry timeout) same => n,Goto(1002,menu) ; --- 1003: hold music, then a human --------------------------------------- ; The whole hold-slayer loop in one call: classify music → stay on hold → ; detect the human → ring the owner. 60s of MoH is long enough for several ; classifier windows (CLASSIFIER_WINDOW_SECONDS defaults to 3.0). exten => 1003,1,NoOp(LAB 1003: hold then human) same => n,Answer() same => n,Wait(1) same => n,Playback(lab-speech) same => n,Playback(lab-music) same => n,Playback(lab-music) same => n,Playback(lab-speech) same => n,Playback(lab-speech) same => n,Wait(30) same => n,Hangup() ; --- 1004: long hold ------------------------------------------------------ ; Exercises MAX_HOLD_TIME and HOLD_CHECK_INTERVAL. 10 minutes. exten => 1004,1,NoOp(LAB 1004: long hold) same => n,Answer() same => n,Wait(1) same => n,Playback(lab-speech) same => n,Playback(lab-music) same => n,Playback(lab-music) same => n,Playback(lab-music) same => n,Playback(lab-music) same => n,Playback(lab-speech) same => n,Wait(15) same => n,Hangup() ; --- 1005: busy ----------------------------------------------------------- ; Failure path: the call must be marked FAILED with no stuck leg. exten => 1005,1,NoOp(LAB 1005: busy) same => n,Busy(20) same => n,Hangup() ; --- 1006: ring, never answer --------------------------------------------- ; Timeout path. Rings for 120s without answering. exten => 1006,1,NoOp(LAB 1006: ring no answer) same => n,Progress() same => n,Wait(120) same => n,Hangup() ; --- 1007: answer, then remote hangup after 5s ---------------------------- ; Proves remote-BYE handling and that the call persists to the DB on hangup. exten => 1007,1,NoOp(LAB 1007: quick remote hangup) same => n,Answer() same => n,Playback(${GREETING}) same => n,Wait(5) same => n,Hangup() ; --- 1008: answer, then silence ------------------------------------------- ; Classifier SILENCE vs the no-audio case. 45s of nothing. exten => 1008,1,NoOp(LAB 1008: silence) same => n,Answer() same => n,Playback(lab-silence) same => n,Wait(40) same => n,Hangup() ; --- 2001: ring the registered softphone ---------------------------------- ; The transfer target. Asterisk is the registrar for devices, so the gateway ; reaches a desk phone by dialling this rather than by registering it itself. ; Fails fast when nothing is registered — a silent 30s ring would look like a ; gateway bug rather than an absent softphone. exten => 2001,1,NoOp(LAB 2001: ring softphone) ; Count registered contacts rather than DEVICE_STATE: device state follows ; the OPTIONS qualify, which is off for this AOR (the pjsua CLI does not ; answer OPTIONS), so a registered softphone would still read UNAVAILABLE. same => n,GotoIf($[${PJSIP_AOR(softphone,contact)} = ""]?nodevice) same => n,Dial(PJSIP/softphone,30) same => n,Hangup() same => n(nodevice),NoOp(LAB 2001: no softphone registered) same => n,Answer() same => n,Playback(lab-speech) same => n,Hangup() ; --- echo test ------------------------------------------------------------ ; Not a scenario — a debugging aid. Echoes audio back so you can confirm ; bidirectional RTP by ear when something looks wrong. exten => 1099,1,NoOp(LAB 1099: echo test) same => n,Answer() same => n,Playback(lab-speech) same => n,Echo() same => n,Hangup() ; Anything else: reject explicitly rather than failing obscurely. exten => _X.,1,NoOp(LAB: unknown extension ${EXTEN}) same => n,Answer() same => n,Playback(${INVALID}) same => n,Hangup()