Skip to content

Commit c26cf10

Browse files
briankwestclaude
andauthored
Add learner content: Recipes, Troubleshooting, and ESL/Scripting programming (#302)
* Add Part 10: Recipes (worked end-to-end examples) + learning-content roadmap New "Part 10: Recipes" composes the reference chapters into complete, working call flows, each verified against conf/vanilla and the registered apps/commands and cross-linked back to the chapters it builds on: - DID -> IVR -> Voicemail - Ring group / hunt group (simultaneous `,` and sequential `|` bridge operators) - Business-hours / time-of-day / holiday routing - Connecting two FreeSWITCH servers (registered gateway + IP-auth trunk) - A conference bridge with a PIN (profile pin + dialplan play_and_get_digits) - Click-to-call with originate (from fs_cli / ESL) Also adds CONTENT-ROADMAP.md: the learner-gap analysis and the blueprint for the remaining planned sections (Troubleshooting; ESL + Events programming guide). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Add Part 11: Troubleshooting & Diagnostics A diagnostics-only part (tools and reads, not internals) covering the failures operators hit most, every command verified against mod_commands / mod_sofia / switch_channel.c: - The diagnostic toolbox (fs_cli, log levels, show/status, sofia status, siptrace) - Registration problems (auth 401/403, NAT, expiry, transport) - No-audio / one-way audio (RTP path, ext-rtp-ip, proxy/bypass media, codec mismatch) - Call-setup failures (reading Q.850 hangup causes, SIP->cause mapping, gateway state) - Reading a SIP trace and a CDR to localize a fault This is a deliberate expansion beyond the manual's original reference-only scope (see CONTENT-ROADMAP.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Add Part 12: Programming with the Event Socket and Scripting A developer's companion (the programmable surface, not config), every API/event name verified against switch_types.h / mod_event_socket / libs/esl / mod_lua / mod_v8: - The event model (event types, subclasses, headers, the channel lifecycle) - Events catalog (companion to the channel-variables catalog) - The inbound Event Socket (wire protocol: auth/api/bgapi/event/filter; ESL library ESLconnection/ESLevent, with Lua + Python examples) - The outbound Event Socket (the `socket` app, async/full, connect/myevents/linger, sync vs async execute, with a worked handler) - Embedded scripting APIs (the Session object, freeswitch.API, freeswitch.Event, consoleLog; parallel Lua and JavaScript examples; Lua-vs-JS naming differences) Deliberate scope expansion (see CONTENT-ROADMAP.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Update README scope to include recipes, troubleshooting, and programming The manual now includes worked-example recipes (Part 10), operator troubleshooting/diagnostics (Part 11), and the programmable ESL + scripting surfaces (Part 12). Revise the Scope section so troubleshooting (via the supported tools) and the programming surfaces are in scope, while source-level debugging, deployment, scaling, and hardening stay out of scope. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8bf8883 commit c26cf10

24 files changed

Lines changed: 4618 additions & 6 deletions

CONTENT-ROADMAP.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Learning-Content Roadmap
2+
3+
The current manual (Parts 1–9) is a **verified configuration reference**. Its
4+
`README.md` deliberately excludes troubleshooting, diagnostics, deployment,
5+
scaling, hardening, and operations. That keeps the reference tight, but it leaves
6+
real gaps for someone trying to *learn* FreeSWITCH rather than look up a value.
7+
8+
This roadmap captures the highest-value additions for the FreeSWITCH audience and
9+
the blueprint for each. Three new sections are planned. Where a section crosses
10+
the manual's stated scope, it is flagged so the boundary is a conscious choice.
11+
12+
---
13+
14+
## Part 10 — Recipes / Worked Examples (in scope)
15+
16+
**Why:** the manual documents every *piece* but never *composes* them. Recipes turn
17+
the reference into something people can learn from. They use only features already
18+
documented elsewhere and cross-link back to the relevant chapters.
19+
20+
Every recipe is verified against `conf/vanilla/` and the documented dialplan apps.
21+
22+
| Chapter | Recipe | Builds on |
23+
|---|---|---|
24+
| 10.1 | Inbound DID → IVR → Voicemail | public context (Ch 13), IVR menus (Ch 21), voicemail (Ch 19) |
25+
| 10.2 | Ring group / hunt group (simultaneous + sequential) | `bridge` (Ch 14), directory (Ch 6) |
26+
| 10.3 | Business-hours / time-of-day routing | time conditions (Ch 15), IVR/voicemail |
27+
| 10.4 | Connecting two FreeSWITCH servers (box-to-box trunk) | gateways (Ch 8), dialplan (Ch 12) |
28+
| 10.5 | A conference bridge with a PIN | conferencing (Ch 20), dialplan |
29+
| 10.6 | Click-to-call / originate from the CLI | `originate` (Ch 31), bridge |
30+
31+
## Part 11 — Troubleshooting & Diagnostics (scope expansion)
32+
33+
**Why:** the single most-searched newcomer need ("no audio", "won't register").
34+
Diagnostics, not internals — it documents the *tools and reads*, not the C code.
35+
36+
| Chapter | Covers |
37+
|---|---|
38+
| 11.1 | The diagnostic toolbox: `fs_cli`, log levels, `sofia status`, `sofia global siptrace`, `sngrep`/pcap |
39+
| 11.2 | Registration problems (auth, NAT, `sofia status profile … reg`) |
40+
| 11.3 | Audio problems: no audio / one-way audio (NAT, RTP, codec mismatch, proxy vs bypass media) |
41+
| 11.4 | Call-setup failures: reading hangup causes, `continue_on_fail`, gateway state |
42+
| 11.5 | Reading a SIP trace and a CDR to localize a fault |
43+
44+
## Part 12 — Programming with the Event Socket & Scripting (scope expansion)
45+
46+
**Why:** the manual *configures* `mod_event_socket` and the language modules but
47+
never shows how to *program* them — the gap for every integrator.
48+
49+
| Chapter | Covers |
50+
|---|---|
51+
| 12.1 | The event model: events, subclasses, headers, the channel lifecycle |
52+
| 12.2 | Events catalog (companion to the channel-variables catalog): the key events and their headers |
53+
| 12.3 | ESL inbound socket: connect, subscribe, `api`/`bgapi`, consume events |
54+
| 12.4 | ESL outbound socket: the `socket` app, the async/sync models |
55+
| 12.5 | Scripting APIs: the `session` object, `freeswitch.API()`, event consumers (Lua and JS) |
56+
57+
---
58+
59+
## Approach
60+
61+
- One section per PR, built section by section, each `npm run build`-validated and
62+
cross-linked into the existing chapters.
63+
- Recipes and scripting examples are verified against the real `conf/vanilla/`
64+
config and the registered applications/commands — same source-of-truth bar as
65+
the reference.
66+
- Parts 11 and 12 are conscious scope expansions; if the manual should stay a pure
67+
reference, they can instead live as a separate companion guide.

README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,27 @@ and it does not teach programming.
5454
- Integration surfaces configured by the operator: Event Socket, XML Curl,
5555
CDR, scripting wiring, and SignalWire connectivity.
5656
- A channel variables reference.
57+
- Worked end-to-end examples (recipes) that compose the documented configuration,
58+
dialplan, and applications into complete call flows.
59+
- Operator troubleshooting and diagnostics: the console, `sofia status`, SIP
60+
tracing, logs, and CDRs, and the common registration, audio, and call-setup
61+
failures — diagnosed with FreeSWITCH's own tools, not by reading the codebase.
62+
- The programmable surfaces: the Event Socket protocol (inbound and outbound) and
63+
the embedded scripting APIs (the runtime objects a script or ESL client uses).
5764

5865
### Out of scope (never include)
5966

6067
- The FreeSWITCH codebase itself: internal implementation, code structure,
61-
functions, data flow, or source-level behavior.
62-
- Troubleshooting, debugging, or diagnostics of any kind.
68+
functions, data flow, or source-level behavior. (Operator-level troubleshooting
69+
using the supported tools is in scope; source-level debugging is not.)
6370
- Deployment, provisioning, scaling, hardening, or operations guidance.
6471
- High availability, performance tuning, and enterprise deployment topics.
6572

66-
Some legacy topics sit on a boundary. ACLs, authentication, and TLS/WSS are
67-
documented strictly as configuration surface (what the parameters are and what
68-
values they accept), never as security hardening guidance. Where a topic crosses
69-
into operations, the manual documents the configuration and stops.
73+
Some topics sit on a boundary. ACLs, authentication, and TLS/WSS are documented
74+
strictly as configuration surface (what the parameters are and what values they
75+
accept), never as security hardening guidance. Troubleshooting documents the
76+
diagnostic tools and the reads, not internals. Where a topic crosses into
77+
deployment or operations, the manual documents the configuration and stops.
7078

7179
## 3. Source of Truth Method
7280

docs/programming/_category_.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "label": "Part 12: Programming (ESL & Scripting)", "position": 12 }

docs/programming/esl-inbound.mdx

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
---
2+
sidebar_position: 3
3+
id: esl-inbound
4+
slug: /programming/esl-inbound
5+
title: "The Inbound Event Socket"
6+
description: "Connect to FreeSWITCH's event_socket listener from your own program: the wire protocol, the ESL client library, and a worked event-consuming loop."
7+
sidebar_label: "ESL: Inbound"
8+
---
9+
10+
# The Inbound Event Socket
11+
12+
In **inbound** mode your program is the client: it opens a TCP connection *to*
13+
FreeSWITCH's `mod_event_socket` listener, authenticates, and then issues
14+
commands and consumes events for as long as it likes. This is the mode you use
15+
to build dashboards, click-to-call backends, dialers, billing collectors, and
16+
any out-of-band controller that talks to a running FreeSWITCH.
17+
18+
Contrast this with **outbound** mode, covered in the sibling chapter
19+
[The Outbound Event Socket](/programming/esl-outbound), where the roles are
20+
reversed: the dialplan's `socket` application makes FreeSWITCH connect *out* to
21+
*your* listening server, handing you one call to control. Same wire protocol,
22+
opposite direction. Inbound is server-wide and persistent; outbound is per-call.
23+
24+
The listener itself, its configuration parameters, and ACL restrictions are
25+
documented in the integration chapter
26+
[The Event Socket](/integration/event-socket). The events you will subscribe to
27+
are described in [The Event Model](./event-model.mdx) and the
28+
[events catalog](./events-catalog.mdx). This chapter is the programmer's
29+
companion: how to *write client code* against the socket.
30+
31+
## The Connection Model {#the-connection-model}
32+
33+
By default `mod_event_socket` listens on port `8021` with the password
34+
`ClueCon`. The shipped `event_socket.conf.xml` binds `listen-ip` to `::`
35+
(all interfaces, IPv6/IPv4), but the conventional and safe target for a local
36+
controller is `127.0.0.1:8021`.
37+
38+
```
39+
listen-port = 8021
40+
password = ClueCon
41+
```
42+
43+
Source: `conf/vanilla/autoload_configs/event_socket.conf.xml`. The password is
44+
checked verbatim against the `auth` command in `mod_event_socket.c`
45+
(`parse_command`, `if (!strcmp(prefs.password, pass))`). Change it before
46+
exposing the socket to anything other than localhost.
47+
48+
When a connection is accepted, FreeSWITCH speaks first. It sends an
49+
authentication challenge:
50+
51+
```
52+
Content-Type: auth/request
53+
54+
```
55+
56+
(`switch_snprintf(buf, ..., "Content-Type: auth/request\n\n")` in
57+
`mod_event_socket.c`.) Your program must respond with `auth` before any other
58+
command is accepted.
59+
60+
## The Wire Protocol {#the-wire-protocol}
61+
62+
Every message on the socket is a block of `Name: Value` headers terminated by a
63+
blank line, optionally followed by a body whose length is given by a
64+
`Content-Length` header. This is the same framing FreeSWITCH events use, so the
65+
protocol is symmetric: commands you send and events you receive look alike.
66+
67+
### Commands {#commands}
68+
69+
The commands below are the ones `parse_command` recognizes in
70+
`mod_event_socket.c`. Send each as a single line terminated by a blank line. The
71+
argument to each comparison in the source is shown so you can trust the spelling.
72+
73+
| Command | Purpose |
74+
| --- | --- |
75+
| `auth <password>` | Authenticate. Required first. Replies `+OK accepted` or `-ERR invalid`. |
76+
| `api <cmd> <args>` | Run an API command and **block** until it returns; the result comes back as `Content-Type: api/response`. |
77+
| `bgapi <cmd> <args>` | Run an API command in the **background**; returns a `Job-UUID` immediately, the result arrives later as a `BACKGROUND_JOB` event. |
78+
| `event <format> <types>` | Subscribe to events. Format is `plain`, `json`, or `xml`. Use `event plain ALL` for everything. |
79+
| `nixevent <types>` | Unsubscribe from specific event types while keeping the rest. |
80+
| `noevents` | Unsubscribe from all events. |
81+
| `filter <header> <value>` | Restrict delivered events to those whose header matches; `filter delete ...` removes a filter. |
82+
| `sendevent <event-name>` | Inject an event into FreeSWITCH's event system. |
83+
| `sendmsg <uuid>` | Send a message/command to a specific channel (used to control calls). |
84+
| `exit` | Close the connection cleanly (replies `+OK bye`). |
85+
86+
These names are verified against the `strncasecmp(cmd, "...")` checks in
87+
`mod_event_socket.c`: `auth ` (line ~1739), `api ` (~2277), `bgapi ` (~2322),
88+
`event` (~2454), `nixevent` (~2536), `noevents` (~2590), `filter ` (~1950),
89+
`sendevent` (~2230), `sendmsg` (~2169), and `exit` (~1732). Additional commands
90+
exist for log streaming (`log`, `nolog`) and the linger/divert mechanics used by
91+
outbound mode.
92+
93+
### Command and Reply {#command-and-reply}
94+
95+
Most commands get a synchronous **Command/Reply**. FreeSWITCH answers with:
96+
97+
```
98+
Content-Type: command/reply
99+
Reply-Text: +OK accepted
100+
101+
```
102+
103+
(`"Content-Type: command/reply\nReply-Text: %s\n\n"` in `mod_event_socket.c`.)
104+
A reply that starts with `+OK` succeeded; one that starts with `-ERR` failed and
105+
carries a reason, for example `-ERR invalid` or `-ERR command not found`.
106+
107+
`api` is the exception: its result is not a `command/reply` but a separate
108+
framed block:
109+
110+
```
111+
Content-Type: api/response
112+
Content-Length: 5
113+
114+
+OK 1
115+
```
116+
117+
(`"Content-Type: api/response\nContent-Length: %ld\n\n"` in `api_exec`,
118+
`mod_event_socket.c`.) You must read exactly `Content-Length` bytes of body.
119+
120+
### Blocking vs Background {#blocking-vs-background}
121+
122+
`api uptime` blocks the socket until the command completes and returns the
123+
output inline. `bgapi originate ...` returns immediately:
124+
125+
```
126+
Content-Type: command/reply
127+
Reply-Text: +OK Job-UUID: 1b9be1e2-...
128+
Job-UUID: 1b9be1e2-...
129+
130+
```
131+
132+
(`"~Reply-Text: +OK Job-UUID: %s\nJob-UUID: %s\n\n"` in the `bgapi` branch of
133+
`mod_event_socket.c`.) The actual result is delivered later, only if you are
134+
subscribed to events, as a `BACKGROUND_JOB` event carrying the matching
135+
`Job-UUID` header and the command output in its body. The event is built in
136+
`api_exec`:
137+
138+
```c
139+
switch_event_create(&event, SWITCH_EVENT_BACKGROUND_JOB);
140+
switch_event_add_header_string(event, ..., "Job-UUID", acs->uuid_str);
141+
switch_event_add_header_string(event, ..., "Job-Command", acs->api_cmd);
142+
switch_event_add_body(event, "%s", reply);
143+
```
144+
145+
Use `bgapi` for anything slow (notably `originate`) so a single long call does
146+
not stall your control connection.
147+
148+
## The ESL Client Library {#the-esl-client-library}
149+
150+
You rarely hand-roll this framing. FreeSWITCH ships **ESL** (Event Socket
151+
Library) under `libs/esl/`, a small C library wrapped by SWIG into Lua, Python,
152+
Perl, PHP, Ruby, Java, and .NET bindings. All bindings expose the same two
153+
objects, `ESLconnection` and `ESLevent`, declared in
154+
`libs/esl/src/include/esl_oop.h`.
155+
156+
### ESLconnection {#eslconnection}
157+
158+
For inbound use you construct an `ESLconnection` with a host, port, and
159+
password. The constructor connects and performs the `auth` handshake for you.
160+
The methods you will use most, all from `esl_oop.h`:
161+
162+
| Method | What it does |
163+
| --- | --- |
164+
| `ESLconnection(host, port, password)` | Connect and authenticate (inbound). |
165+
| `connected()` | Returns nonzero while the socket is up. |
166+
| `api(cmd, arg)` | Send `api`, block, return an `ESLevent` whose body holds the result. |
167+
| `bgapi(cmd, arg, job_uuid)` | Send `bgapi`, return immediately; result arrives as a `BACKGROUND_JOB` event. |
168+
| `events(etype, value)` | Subscribe. `etype` is `plain`, `json`, or `xml`; `value` is the event list. |
169+
| `filter(header, value)` | Add a server-side event filter. |
170+
| `recvEvent()` | Block until the next event arrives; return it as an `ESLevent`. |
171+
| `recvEventTimed(ms)` | Like `recvEvent` but give up after `ms` milliseconds. |
172+
| `sendEvent(event)` | Send an `ESLevent` you built (the `sendevent` command). |
173+
| `sendMSG(event, uuid)` | Send a message to a channel (the `sendmsg` command). |
174+
| `disconnect()` | Close the connection. |
175+
176+
Under the hood these build the wire commands shown above. For example
177+
`api(cmd, arg)` emits `api <cmd> <arg>` and `bgapi` emits
178+
`bgapi <cmd> <arg>` with a `Job-UUID:` header (see `esl.c`, `esl_oop.cpp`).
179+
`events` emits `event <type> <value>` and `filter` emits
180+
`filter <header> <value>`.
181+
182+
### ESLevent {#eslevent}
183+
184+
Every event and every API result comes back as an `ESLevent`. Its accessors,
185+
from `esl_oop.h`:
186+
187+
| Method | What it returns |
188+
| --- | --- |
189+
| `getHeader(name)` | The value of a header, or null if absent. |
190+
| `getBody()` | The event body (the API output, or a message payload). |
191+
| `getType()` | The event name, e.g. `CHANNEL_ANSWER`. |
192+
| `serialize(format)` | The whole event as text (or `json`/`xml`). |
193+
| `addHeader(name, value)` | Set a header (when building an event to send). |
194+
| `addBody(value)` | Set the body (when building an event to send). |
195+
196+
## A Worked Example {#a-worked-example}
197+
198+
The example below uses the Lua binding (`libs/esl/lua`). The constructor,
199+
`con:api(...)`, and `e:getBody()` are exactly the pattern in the shipped
200+
`libs/esl/lua/single_command.lua`. The Python, Perl, and other bindings are
201+
identical method-for-method; only the dot-versus-colon syntax differs.
202+
203+
```lua
204+
-- inbound ESL: connect, run an API command, then watch channel events
205+
require("ESL")
206+
207+
-- ESLconnection(host, port, password) connects AND authenticates
208+
local con = ESL.ESLconnection("127.0.0.1", "8021", "ClueCon")
209+
210+
if con:connected() == 0 then
211+
print("could not connect to FreeSWITCH")
212+
return
213+
end
214+
215+
-- a blocking api call: result is the ESLevent body
216+
local info = con:api("status")
217+
print(info:getBody())
218+
219+
-- kick off a call in the background; we get a Job-UUID back immediately
220+
con:bgapi("originate", "user/1000 &echo")
221+
222+
-- subscribe to all events in plain format so we receive BACKGROUND_JOB
223+
-- and the CHANNEL_* lifecycle
224+
con:events("plain", "ALL")
225+
226+
-- consume events forever
227+
while con:connected() == 1 do
228+
-- recvEvent() blocks for the next event; recvEventTimed(ms) is the
229+
-- timed variant
230+
local e = con:recvEvent()
231+
if e then
232+
local name = e:getHeader("Event-Name")
233+
if name == "CHANNEL_CREATE"
234+
or name == "CHANNEL_ANSWER"
235+
or name == "CHANNEL_HANGUP" then
236+
print(string.format("%s uuid=%s number=%s",
237+
name,
238+
e:getHeader("Unique-ID"),
239+
e:getHeader("Caller-Destination-Number")))
240+
elseif name == "BACKGROUND_JOB" then
241+
-- the deferred result of our bgapi originate
242+
print("job " .. tostring(e:getHeader("Job-UUID"))
243+
.. " -> " .. tostring(e:getBody()))
244+
end
245+
end
246+
end
247+
```
248+
249+
The same program in Python is line-for-line equivalent (and matches the shipped
250+
`libs/esl/python3/events.py`):
251+
252+
```python
253+
from ESL import ESLconnection
254+
255+
con = ESLconnection("127.0.0.1", "8021", "ClueCon")
256+
if con.connected():
257+
print(con.api("status").getBody())
258+
con.events("plain", "ALL")
259+
while con.connected():
260+
e = con.recvEvent()
261+
if e:
262+
print(e.getType(), e.getHeader("Unique-ID"))
263+
```
264+
265+
## Common Uses {#common-uses}
266+
267+
**Monitoring channel lifecycle.** Subscribe with
268+
`con:events("plain", "CHANNEL_CREATE CHANNEL_ANSWER CHANNEL_HANGUP_COMPLETE")`
269+
and loop on `recvEvent()`. Each event's headers (`Unique-ID`,
270+
`Caller-Caller-ID-Number`, `Caller-Destination-Number`, `Hangup-Cause`) give you
271+
a complete picture of who is talking to whom. To reduce noise, subscribe only to
272+
the event types you need rather than `ALL`, or add a `filter` to narrow by
273+
header.
274+
275+
**Placing and controlling calls.** Use `api`/`bgapi` to run any API command. The
276+
two you will reach for most:
277+
278+
- `con:bgapi("originate", "user/1000 &park")` to start a call without blocking.
279+
- `con:api("uuid_kill", uuid)` or `con:api("uuid_transfer", uuid .. " 9999")`
280+
to act on a live channel by its `Unique-ID`.
281+
282+
Because `bgapi` returns a `Job-UUID` and the real answer arrives later as a
283+
`BACKGROUND_JOB` event, a robust dialer keeps an events loop running, records
284+
the `Job-UUID` it got back, and matches it against the `Job-UUID` header on each
285+
`BACKGROUND_JOB` to learn the outcome of each `originate`.
286+
287+
**Injecting events.** Build an `ESLevent`, populate it with `addHeader`, and send
288+
it with `con:sendEvent(...)` (the `sendevent` command) to fire custom events or
289+
trigger behavior in other modules.
290+
291+
For the listener-side configuration, ACLs, the `event_socket.conf.xml`
292+
parameters, and the raw protocol walkthrough, see
293+
[The Event Socket](/integration/event-socket). For the per-call,
294+
FreeSWITCH-connects-to-you variant, see
295+
[The Outbound Event Socket](/programming/esl-outbound).

0 commit comments

Comments
 (0)