2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief Device state management
23 * \author Mark Spencer <markster@digium.com>
28 ASTERISK_FILE_VERSION(__FILE__
, "$Revision$")
30 #include <sys/types.h>
36 #include "asterisk/channel.h"
37 #include "asterisk/utils.h"
38 #include "asterisk/lock.h"
39 #include "asterisk/linkedlists.h"
40 #include "asterisk/logger.h"
41 #include "asterisk/devicestate.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/app.h"
44 #include "asterisk/options.h"
46 /*! \brief Device state strings for printing */
47 static const char *devstatestring
[] = {
48 /* 0 AST_DEVICE_UNKNOWN */ "Unknown", /*!< Valid, but unknown state */
49 /* 1 AST_DEVICE_NOT_INUSE */ "Not in use", /*!< Not used */
50 /* 2 AST_DEVICE IN USE */ "In use", /*!< In use */
51 /* 3 AST_DEVICE_BUSY */ "Busy", /*!< Busy */
52 /* 4 AST_DEVICE_INVALID */ "Invalid", /*!< Invalid - not known to Asterisk */
53 /* 5 AST_DEVICE_UNAVAILABLE */ "Unavailable", /*!< Unavailable (not registred) */
54 /* 6 AST_DEVICE_RINGING */ "Ringing", /*!< Ring, ring, ring */
55 /* 7 AST_DEVICE_RINGINUSE */ "Ring+Inuse", /*!< Ring and in use */
56 /* 8 AST_DEVICE_ONHOLD */ "On Hold" /*!< On Hold */
59 /*! \brief A device state provider (not a channel) */
60 struct devstate_prov
{
62 ast_devstate_prov_cb_type callback
;
63 AST_LIST_ENTRY(devstate_prov
) list
;
66 /*! \brief A list of providers */
67 static AST_LIST_HEAD_STATIC(devstate_provs
, devstate_prov
);
69 /*! \brief A device state watcher (callback) */
72 ast_devstate_cb_type callback
;
73 AST_LIST_ENTRY(devstate_cb
) list
;
76 /*! \brief A device state watcher list */
77 static AST_LIST_HEAD_STATIC(devstate_cbs
, devstate_cb
);
80 AST_LIST_ENTRY(state_change
) list
;
84 /*! \brief The state change queue. State changes are queued
85 for processing by a separate thread */
86 static AST_LIST_HEAD_STATIC(state_changes
, state_change
);
88 /*! \brief The device state change notification thread */
89 static pthread_t change_thread
= AST_PTHREADT_NULL
;
91 /*! \brief Flag for the queue */
92 static ast_cond_t change_pending
;
94 /* Forward declarations */
95 static int getproviderstate(const char *provider
, const char *address
);
97 /*! \brief Find devicestate as text message for output */
98 const char *devstate2str(int devstate
)
100 return devstatestring
[devstate
];
103 /*! \brief Find out if device is active in a call or not
104 \note find channels with the device's name in it
105 This function is only used for channels that does not implement
108 int ast_parse_device_state(const char *device
)
110 struct ast_channel
*chan
;
111 char match
[AST_CHANNEL_NAME
];
114 ast_copy_string(match
, device
, sizeof(match
)-1);
116 chan
= ast_get_channel_by_name_prefix_locked(match
, strlen(match
));
119 return AST_DEVICE_UNKNOWN
;
121 if (chan
->_state
== AST_STATE_RINGING
)
122 res
= AST_DEVICE_RINGING
;
124 res
= AST_DEVICE_INUSE
;
126 ast_channel_unlock(chan
);
131 /*! \brief Check device state through channel specific function or generic function */
132 int ast_device_state(const char *device
)
136 const struct ast_channel_tech
*chan_tech
;
138 /*! \brief Channel driver that provides device state */
140 /*! \brief Another provider of device state */
141 char *provider
= NULL
;
143 buf
= ast_strdupa(device
);
144 tech
= strsep(&buf
, "/");
147 provider
= strsep(&tech
, ":");
149 return AST_DEVICE_INVALID
;
150 /* We have a provider */
157 ast_log(LOG_DEBUG
, "Checking if I can find provider for \"%s\" - number: %s\n", provider
, number
);
158 return getproviderstate(provider
, number
);
160 if (option_debug
> 3)
161 ast_log(LOG_DEBUG
, "No provider found, checking channel drivers for %s - %s\n", tech
, number
);
163 chan_tech
= ast_get_channel_tech(tech
);
165 return AST_DEVICE_INVALID
;
167 if (!chan_tech
->devicestate
) /* Does the channel driver support device state notification? */
168 return ast_parse_device_state(device
); /* No, try the generic function */
170 res
= chan_tech
->devicestate(number
); /* Ask the channel driver for device state */
171 if (res
== AST_DEVICE_UNKNOWN
) {
172 res
= ast_parse_device_state(device
);
173 /* at this point we know the device exists, but the channel driver
174 could not give us a state; if there is no channel state available,
175 it must be 'not in use'
177 if (res
== AST_DEVICE_UNKNOWN
)
178 res
= AST_DEVICE_NOT_INUSE
;
185 /*! \brief Add device state provider */
186 int ast_devstate_prov_add(const char *label
, ast_devstate_prov_cb_type callback
)
188 struct devstate_prov
*devprov
;
190 if (!callback
|| !(devprov
= ast_calloc(1, sizeof(*devprov
))))
193 devprov
->callback
= callback
;
194 ast_copy_string(devprov
->label
, label
, sizeof(devprov
->label
));
196 AST_LIST_LOCK(&devstate_provs
);
197 AST_LIST_INSERT_HEAD(&devstate_provs
, devprov
, list
);
198 AST_LIST_UNLOCK(&devstate_provs
);
203 /*! \brief Remove device state provider */
204 void ast_devstate_prov_del(const char *label
)
206 struct devstate_prov
*devcb
;
208 AST_LIST_LOCK(&devstate_provs
);
209 AST_LIST_TRAVERSE_SAFE_BEGIN(&devstate_provs
, devcb
, list
) {
210 if (!strcasecmp(devcb
->label
, label
)) {
211 AST_LIST_REMOVE_CURRENT(&devstate_provs
, list
);
216 AST_LIST_TRAVERSE_SAFE_END
;
217 AST_LIST_UNLOCK(&devstate_provs
);
220 /*! \brief Get provider device state */
221 static int getproviderstate(const char *provider
, const char *address
)
223 struct devstate_prov
*devprov
;
224 int res
= AST_DEVICE_INVALID
;
227 AST_LIST_LOCK(&devstate_provs
);
228 AST_LIST_TRAVERSE_SAFE_BEGIN(&devstate_provs
, devprov
, list
) {
230 ast_log(LOG_DEBUG
, "Checking provider %s with %s\n", devprov
->label
, provider
);
232 if (!strcasecmp(devprov
->label
, provider
)) {
233 res
= devprov
->callback(address
);
237 AST_LIST_TRAVERSE_SAFE_END
;
238 AST_LIST_UNLOCK(&devstate_provs
);
242 /*! \brief Add device state watcher */
243 int ast_devstate_add(ast_devstate_cb_type callback
, void *data
)
245 struct devstate_cb
*devcb
;
247 if (!callback
|| !(devcb
= ast_calloc(1, sizeof(*devcb
))))
251 devcb
->callback
= callback
;
253 AST_LIST_LOCK(&devstate_cbs
);
254 AST_LIST_INSERT_HEAD(&devstate_cbs
, devcb
, list
);
255 AST_LIST_UNLOCK(&devstate_cbs
);
260 /*! \brief Remove device state watcher */
261 void ast_devstate_del(ast_devstate_cb_type callback
, void *data
)
263 struct devstate_cb
*devcb
;
265 AST_LIST_LOCK(&devstate_cbs
);
266 AST_LIST_TRAVERSE_SAFE_BEGIN(&devstate_cbs
, devcb
, list
) {
267 if ((devcb
->callback
== callback
) && (devcb
->data
== data
)) {
268 AST_LIST_REMOVE_CURRENT(&devstate_cbs
, list
);
273 AST_LIST_TRAVERSE_SAFE_END
;
274 AST_LIST_UNLOCK(&devstate_cbs
);
277 /*! \brief Notify callback watchers of change, and notify PBX core for hint updates
278 Normally executed within a separate thread
280 static void do_state_change(const char *device
)
283 struct devstate_cb
*devcb
;
285 state
= ast_device_state(device
);
286 if (option_debug
> 2)
287 ast_log(LOG_DEBUG
, "Changing state for %s - state %d (%s)\n", device
, state
, devstate2str(state
));
289 AST_LIST_LOCK(&devstate_cbs
);
290 AST_LIST_TRAVERSE(&devstate_cbs
, devcb
, list
)
291 devcb
->callback(device
, state
, devcb
->data
);
292 AST_LIST_UNLOCK(&devstate_cbs
);
294 ast_hint_state_changed(device
);
297 static int __ast_device_state_changed_literal(char *buf
, int norecurse
)
300 struct state_change
*change
;
303 if (option_debug
> 2)
304 ast_log(LOG_DEBUG
, "Notification of state change to be queued on device/channel %s\n", buf
);
308 if (change_thread
== AST_PTHREADT_NULL
|| !(change
= ast_calloc(1, sizeof(*change
) + strlen(device
)))) {
309 /* we could not allocate a change struct, or */
310 /* there is no background thread, so process the change now */
311 do_state_change(device
);
313 /* queue the change */
314 strcpy(change
->device
, device
);
315 AST_LIST_LOCK(&state_changes
);
316 AST_LIST_INSERT_TAIL(&state_changes
, change
, list
);
317 if (AST_LIST_FIRST(&state_changes
) == change
)
318 /* the list was empty, signal the thread */
319 ast_cond_signal(&change_pending
);
320 AST_LIST_UNLOCK(&state_changes
);
323 /* The problem with this API is that a device may be called with the unique
324 * identifier appended or not, but it's separated from the channel name
325 * with a '-', which is also a legitimate character in a channel name. So,
326 * we have to force both names to get their names checked for state changes
327 * to ensure that the right one gets notified. Not a huge performance hit,
328 * but it might could be fixed by an enterprising programmer in trunk.
330 if (!norecurse
&& (tmp
= strrchr(device
, '-'))) {
332 __ast_device_state_changed_literal(device
, 1);
338 int ast_device_state_changed_literal(const char *dev
)
341 buf
= ast_strdupa(dev
);
342 return __ast_device_state_changed_literal(buf
, 0);
345 /*! \brief Accept change notification, add it to change queue */
346 int ast_device_state_changed(const char *fmt
, ...)
348 char buf
[AST_MAX_EXTENSION
];
352 vsnprintf(buf
, sizeof(buf
), fmt
, ap
);
354 return __ast_device_state_changed_literal(buf
, 0);
357 /*! \brief Go through the dev state change queue and update changes in the dev state thread */
358 static void *do_devstate_changes(void *data
)
360 struct state_change
*cur
;
362 AST_LIST_LOCK(&state_changes
);
364 /* the list lock will _always_ be held at this point in the loop */
365 cur
= AST_LIST_REMOVE_HEAD(&state_changes
, list
);
367 /* we got an entry, so unlock the list while we process it */
368 AST_LIST_UNLOCK(&state_changes
);
369 do_state_change(cur
->device
);
371 AST_LIST_LOCK(&state_changes
);
373 /* there was no entry, so atomically unlock the list and wait for
374 the condition to be signalled (returns with the lock held) */
375 ast_cond_wait(&change_pending
, &state_changes
.lock
);
382 /*! \brief Initialize the device state engine in separate thread */
383 int ast_device_state_engine_init(void)
385 ast_cond_init(&change_pending
, NULL
);
386 if (ast_pthread_create_background(&change_thread
, NULL
, do_devstate_changes
, NULL
) < 0) {
387 ast_log(LOG_ERROR
, "Unable to start device state change thread.\n");