make Local channel return sensible device state values
[asterisk-bristuff.git] / devicestate.c
blob4121cfbcbc3446e8ce8f01a18dc3cad3920e3336
1 /*
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.
19 /*! \file
21 * \brief Device state management
23 * \author Mark Spencer <markster@digium.com>
26 #include "asterisk.h"
28 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
30 #include <sys/types.h>
31 #include <unistd.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <stdio.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/options.h"
45 /*! \brief Device state strings for printing */
46 static const char *devstatestring[] = {
47 /* 0 AST_DEVICE_UNKNOWN */ "Unknown", /*!< Valid, but unknown state */
48 /* 1 AST_DEVICE_NOT_INUSE */ "Not in use", /*!< Not used */
49 /* 2 AST_DEVICE IN USE */ "In use", /*!< In use */
50 /* 3 AST_DEVICE_BUSY */ "Busy", /*!< Busy */
51 /* 4 AST_DEVICE_INVALID */ "Invalid", /*!< Invalid - not known to Asterisk */
52 /* 5 AST_DEVICE_UNAVAILABLE */ "Unavailable", /*!< Unavailable (not registred) */
53 /* 6 AST_DEVICE_RINGING */ "Ringing" /*!< Ring, ring, ring */
56 /*! \brief A device state watcher (callback) */
57 struct devstate_cb {
58 void *data;
59 ast_devstate_cb_type callback;
60 AST_LIST_ENTRY(devstate_cb) list;
63 static AST_LIST_HEAD_STATIC(devstate_cbs, devstate_cb);
65 struct state_change {
66 AST_LIST_ENTRY(state_change) list;
67 char device[1];
70 static AST_LIST_HEAD_STATIC(state_changes, state_change);
72 static pthread_t change_thread = AST_PTHREADT_NULL;
73 static ast_cond_t change_pending;
75 /*! \brief Find devicestate as text message for output */
76 const char *devstate2str(int devstate)
78 return devstatestring[devstate];
81 /*! \brief Find out if device is active in a call or not
82 \note find channels with the device's name in it
83 This function is only used for channels that does not implement
84 devicestate natively
86 int ast_parse_device_state(const char *device)
88 struct ast_channel *chan;
89 char match[AST_CHANNEL_NAME];
90 int res;
92 ast_copy_string(match, device, sizeof(match)-1);
93 strcat(match, "-");
94 chan = ast_get_channel_by_name_prefix_locked(match, strlen(match));
96 if (!chan)
97 return AST_DEVICE_UNKNOWN;
99 if (chan->_state == AST_STATE_RINGING)
100 res = AST_DEVICE_RINGING;
101 else
102 res = AST_DEVICE_INUSE;
104 ast_channel_unlock(chan);
106 return res;
109 /*! \brief Check device state through channel specific function or generic function */
110 int ast_device_state(const char *device)
112 char *buf;
113 char *tech;
114 char *number;
115 const struct ast_channel_tech *chan_tech;
116 int res = 0;
118 buf = ast_strdupa(device);
119 tech = strsep(&buf, "/");
120 number = buf;
121 if (!number)
122 return AST_DEVICE_INVALID;
124 chan_tech = ast_get_channel_tech(tech);
125 if (!chan_tech)
126 return AST_DEVICE_INVALID;
128 if (!chan_tech->devicestate) /* Does the channel driver support device state notification? */
129 return ast_parse_device_state(device); /* No, try the generic function */
130 else {
131 res = chan_tech->devicestate(number); /* Ask the channel driver for device state */
132 if (res == AST_DEVICE_UNKNOWN) {
133 res = ast_parse_device_state(device);
134 /* at this point we know the device exists, but the channel driver
135 could not give us a state; if there is no channel state available,
136 it must be 'not in use'
138 if (res == AST_DEVICE_UNKNOWN)
139 res = AST_DEVICE_NOT_INUSE;
140 return res;
141 } else
142 return res;
146 /*! \brief Add device state watcher */
147 int ast_devstate_add(ast_devstate_cb_type callback, void *data)
149 struct devstate_cb *devcb;
151 if (!callback || !(devcb = ast_calloc(1, sizeof(*devcb))))
152 return -1;
154 devcb->data = data;
155 devcb->callback = callback;
157 AST_LIST_LOCK(&devstate_cbs);
158 AST_LIST_INSERT_HEAD(&devstate_cbs, devcb, list);
159 AST_LIST_UNLOCK(&devstate_cbs);
161 return 0;
164 /*! \brief Remove device state watcher */
165 void ast_devstate_del(ast_devstate_cb_type callback, void *data)
167 struct devstate_cb *devcb;
169 AST_LIST_LOCK(&devstate_cbs);
170 AST_LIST_TRAVERSE_SAFE_BEGIN(&devstate_cbs, devcb, list) {
171 if ((devcb->callback == callback) && (devcb->data == data)) {
172 AST_LIST_REMOVE_CURRENT(&devstate_cbs, list);
173 free(devcb);
174 break;
177 AST_LIST_TRAVERSE_SAFE_END;
178 AST_LIST_UNLOCK(&devstate_cbs);
181 /*! \brief Notify callback watchers of change, and notify PBX core for hint updates */
182 static void do_state_change(const char *device)
184 int state;
185 struct devstate_cb *devcb;
187 state = ast_device_state(device);
188 if (option_debug > 2)
189 ast_log(LOG_DEBUG, "Changing state for %s - state %d (%s)\n", device, state, devstate2str(state));
191 AST_LIST_LOCK(&devstate_cbs);
192 AST_LIST_TRAVERSE(&devstate_cbs, devcb, list)
193 devcb->callback(device, state, devcb->data);
194 AST_LIST_UNLOCK(&devstate_cbs);
196 ast_hint_state_changed(device);
199 static int __ast_device_state_changed_literal(char *buf)
201 char *device, *tmp;
202 struct state_change *change;
204 device = buf;
205 if ((tmp = strrchr(device, '-')))
206 *tmp = '\0';
208 if (change_thread == AST_PTHREADT_NULL || !(change = ast_calloc(1, sizeof(*change) + strlen(device)))) {
209 /* we could not allocate a change struct, or */
210 /* there is no background thread, so process the change now */
211 do_state_change(device);
212 } else {
213 /* queue the change */
214 strcpy(change->device, device);
215 AST_LIST_LOCK(&state_changes);
216 AST_LIST_INSERT_TAIL(&state_changes, change, list);
217 if (AST_LIST_FIRST(&state_changes) == change)
218 /* the list was empty, signal the thread */
219 ast_cond_signal(&change_pending);
220 AST_LIST_UNLOCK(&state_changes);
223 return 1;
226 int ast_device_state_changed_literal(const char *dev)
228 char *buf;
229 buf = ast_strdupa(dev);
230 return __ast_device_state_changed_literal(buf);
233 /*! \brief Accept change notification, add it to change queue */
234 int ast_device_state_changed(const char *fmt, ...)
236 char buf[AST_MAX_EXTENSION];
237 va_list ap;
239 va_start(ap, fmt);
240 vsnprintf(buf, sizeof(buf), fmt, ap);
241 va_end(ap);
242 return __ast_device_state_changed_literal(buf);
245 /*! \brief Go through the dev state change queue and update changes in the dev state thread */
246 static void *do_devstate_changes(void *data)
248 struct state_change *cur;
250 AST_LIST_LOCK(&state_changes);
251 for(;;) {
252 /* the list lock will _always_ be held at this point in the loop */
253 cur = AST_LIST_REMOVE_HEAD(&state_changes, list);
254 if (cur) {
255 /* we got an entry, so unlock the list while we process it */
256 AST_LIST_UNLOCK(&state_changes);
257 do_state_change(cur->device);
258 free(cur);
259 AST_LIST_LOCK(&state_changes);
260 } else {
261 /* there was no entry, so atomically unlock the list and wait for
262 the condition to be signalled (returns with the lock held) */
263 ast_cond_wait(&change_pending, &state_changes.lock);
267 return NULL;
270 /*! \brief Initialize the device state engine in separate thread */
271 int ast_device_state_engine_init(void)
273 ast_cond_init(&change_pending, NULL);
274 if (ast_pthread_create(&change_thread, NULL, do_devstate_changes, NULL) < 0) {
275 ast_log(LOG_ERROR, "Unable to start device state change thread.\n");
276 return -1;
279 return 0;