2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2013, Digium, Inc.
6 * Kevin Harwell <kharwell@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.
20 <depend type="module">res_stasis</depend>
21 <support_level>core</support_level>
26 ASTERISK_REGISTER_FILE()
28 #include "asterisk/astdb.h"
29 #include "asterisk/astobj2.h"
30 #include "asterisk/module.h"
31 #include "asterisk/stasis_app_impl.h"
32 #include "asterisk/stasis_app_device_state.h"
34 #define DEVICE_STATE_SIZE 64
35 /*! astdb family name */
36 #define DEVICE_STATE_FAMILY "StasisDeviceState"
37 /*! Stasis device state provider */
38 #define DEVICE_STATE_PROVIDER_STASIS "Stasis"
39 /*! Scheme for custom device states */
40 #define DEVICE_STATE_SCHEME_STASIS "Stasis:"
41 /*! Scheme for device state subscriptions */
42 #define DEVICE_STATE_SCHEME_SUB "deviceState:"
44 /*! Number of hash buckets for device state subscriptions */
45 #define DEVICE_STATE_BUCKETS 37
47 /*! The key used for tracking a subscription to all device states */
48 #define DEVICE_STATE_ALL "__AST_DEVICE_STATE_ALL_TOPIC"
50 /*! Container for subscribed device states */
51 static struct ao2_container *device_state_subscriptions;
54 * \brief Device state subscription object.
56 struct device_state_subscription {
57 AST_DECLARE_STRING_FIELDS(
58 AST_STRING_FIELD(app_name);
59 AST_STRING_FIELD(device_name);
61 /*! The subscription object */
62 struct stasis_subscription *sub;
65 static int device_state_subscriptions_hash(const void *obj, const int flags)
67 const struct device_state_subscription *object;
69 switch (flags & OBJ_SEARCH_MASK) {
70 case OBJ_SEARCH_OBJECT:
72 return ast_str_hash(object->device_name);
75 /* Hash can only work on something with a full key. */
81 static int device_state_subscriptions_cmp(void *obj, void *arg, int flags)
83 const struct device_state_subscription *object_left = obj;
84 const struct device_state_subscription *object_right = arg;
87 switch (flags & OBJ_SEARCH_MASK) {
88 case OBJ_SEARCH_OBJECT:
89 /* find objects matching both device and app names */
90 if (strcmp(object_left->device_name,
91 object_right->device_name)) {
94 cmp = strcmp(object_left->app_name, object_right->app_name);
97 case OBJ_SEARCH_PARTIAL_KEY:
98 ast_assert(0); /* not supported by container */
105 return cmp ? 0 : CMP_MATCH | CMP_STOP;
108 static void device_state_subscription_destroy(void *obj)
110 struct device_state_subscription *sub = obj;
111 sub->sub = stasis_unsubscribe_and_join(sub->sub);
112 ast_string_field_free_memory(sub);
115 static struct device_state_subscription *device_state_subscription_create(
116 const struct stasis_app *app, const char *device_name)
118 struct device_state_subscription *sub;
119 const char *app_name = stasis_app_name(app);
122 if (ast_strlen_zero(device_name)) {
123 device_name = DEVICE_STATE_ALL;
126 size = strlen(device_name) + strlen(app_name) + 2;
128 sub = ao2_alloc(sizeof(*sub), device_state_subscription_destroy);
133 if (ast_string_field_init(sub, size)) {
138 ast_string_field_set(sub, app_name, app_name);
139 ast_string_field_set(sub, device_name, device_name);
143 static struct device_state_subscription *find_device_state_subscription(
144 struct stasis_app *app, const char *name)
146 struct device_state_subscription dummy_sub = {
147 .app_name = stasis_app_name(app),
151 return ao2_find(device_state_subscriptions, &dummy_sub, OBJ_SEARCH_OBJECT);
154 static void remove_device_state_subscription(
155 struct device_state_subscription *sub)
157 ao2_unlink(device_state_subscriptions, sub);
160 struct ast_json *stasis_app_device_state_to_json(
161 const char *name, enum ast_device_state state)
163 return ast_json_pack("{s: s, s: s}",
165 "state", ast_devstate_str(state));
168 struct ast_json *stasis_app_device_states_to_json(void)
170 struct ast_json *array = ast_json_array_create();
171 RAII_VAR(struct ast_db_entry *, tree,
172 ast_db_gettree(DEVICE_STATE_FAMILY, NULL), ast_db_freetree);
173 struct ast_db_entry *entry;
175 for (entry = tree; entry; entry = entry->next) {
176 const char *name = strrchr(entry->key, '/');
177 if (!ast_strlen_zero(name)) {
178 struct ast_str *device = ast_str_alloca(DEVICE_STATE_SIZE);
179 ast_str_set(&device, 0, "%s%s",
180 DEVICE_STATE_SCHEME_STASIS, ++name);
181 ast_json_array_append(
182 array, stasis_app_device_state_to_json(
183 ast_str_buffer(device),
184 ast_device_state(ast_str_buffer(device))));
191 static void send_device_state(struct device_state_subscription *sub,
192 const char *name, enum ast_device_state state)
194 RAII_VAR(struct ast_json *, json, NULL, ast_json_unref);
196 json = ast_json_pack("{s:s, s:s, s:o, s:o}",
197 "type", "DeviceStateChanged",
198 "application", sub->app_name,
199 "timestamp", ast_json_timeval(ast_tvnow(), NULL),
200 "device_state", stasis_app_device_state_to_json(
204 ast_log(LOG_ERROR, "Unable to create device state json object\n");
208 stasis_app_send(sub->app_name, json);
211 enum stasis_device_state_result stasis_app_device_state_update(
212 const char *name, const char *value)
214 size_t size = strlen(DEVICE_STATE_SCHEME_STASIS);
215 enum ast_device_state state;
217 ast_debug(3, "Updating device name = %s, value = %s", name, value);
219 if (strncasecmp(name, DEVICE_STATE_SCHEME_STASIS, size)) {
220 ast_log(LOG_ERROR, "Update can only be used to set "
221 "'%s' device state!\n", DEVICE_STATE_SCHEME_STASIS);
222 return STASIS_DEVICE_STATE_NOT_CONTROLLED;
226 if (ast_strlen_zero(name)) {
227 ast_log(LOG_ERROR, "Update requires custom device name!\n");
228 return STASIS_DEVICE_STATE_MISSING;
231 if (!value || (state = ast_devstate_val(value)) == AST_DEVICE_UNKNOWN) {
232 ast_log(LOG_ERROR, "Unknown device state "
233 "value '%s'\n", value);
234 return STASIS_DEVICE_STATE_UNKNOWN;
237 ast_db_put(DEVICE_STATE_FAMILY, name, value);
238 ast_devstate_changed(state, AST_DEVSTATE_CACHABLE, "%s%s",
239 DEVICE_STATE_SCHEME_STASIS, name);
241 return STASIS_DEVICE_STATE_OK;
244 enum stasis_device_state_result stasis_app_device_state_delete(const char *name)
246 const char *full_name = name;
247 size_t size = strlen(DEVICE_STATE_SCHEME_STASIS);
249 if (strncasecmp(name, DEVICE_STATE_SCHEME_STASIS, size)) {
250 ast_log(LOG_ERROR, "Can only delete '%s' device states!\n",
251 DEVICE_STATE_SCHEME_STASIS);
252 return STASIS_DEVICE_STATE_NOT_CONTROLLED;
256 if (ast_strlen_zero(name)) {
257 ast_log(LOG_ERROR, "Delete requires a device name!\n");
258 return STASIS_DEVICE_STATE_MISSING;
261 if (ast_device_state_clear_cache(full_name)) {
262 return STASIS_DEVICE_STATE_UNKNOWN;
265 ast_db_del(DEVICE_STATE_FAMILY, name);
267 /* send state change for delete */
268 ast_devstate_changed(
269 AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "%s%s",
270 DEVICE_STATE_SCHEME_STASIS, name);
272 return STASIS_DEVICE_STATE_OK;
275 static void populate_cache(void)
277 RAII_VAR(struct ast_db_entry *, tree,
278 ast_db_gettree(DEVICE_STATE_FAMILY, NULL), ast_db_freetree);
279 struct ast_db_entry *entry;
281 for (entry = tree; entry; entry = entry->next) {
282 const char *name = strrchr(entry->key, '/');
283 if (!ast_strlen_zero(name)) {
284 ast_devstate_changed(
285 ast_devstate_val(entry->data),
286 AST_DEVSTATE_CACHABLE, "%s%s\n",
287 DEVICE_STATE_SCHEME_STASIS, name + 1);
292 static enum ast_device_state stasis_device_state_cb(const char *data)
294 char buf[DEVICE_STATE_SIZE] = "";
296 ast_db_get(DEVICE_STATE_FAMILY, data, buf, sizeof(buf));
298 return ast_devstate_val(buf);
301 static void device_state_cb(void *data, struct stasis_subscription *sub,
302 struct stasis_message *msg)
304 struct ast_device_state_message *device_state;
306 if (stasis_subscription_final_message(sub, msg)) {
307 /* Remove stasis subscription's reference to device_state_subscription */
312 if (ast_device_state_message_type() != stasis_message_type(msg)) {
316 device_state = stasis_message_data(msg);
317 if (device_state->eid) {
318 /* ignore non-aggregate states */
322 send_device_state(data, device_state->device, device_state->state);
325 static void *find_device_state(const struct stasis_app *app, const char *name)
327 return device_state_subscription_create(app, name);
330 static int is_subscribed_device_state(struct stasis_app *app, const char *name)
332 struct device_state_subscription *sub;
334 sub = find_device_state_subscription(app, DEVICE_STATE_ALL);
340 sub = find_device_state_subscription(app, name);
349 static int subscribe_device_state(struct stasis_app *app, void *obj)
351 struct device_state_subscription *sub = obj;
352 struct stasis_topic *topic;
355 sub = device_state_subscription_create(app, NULL);
361 if (strcmp(sub->device_name, DEVICE_STATE_ALL)) {
362 topic = ast_device_state_topic(sub->device_name);
364 topic = ast_device_state_topic_all();
367 if (is_subscribed_device_state(app, sub->device_name)) {
368 ast_debug(3, "App %s is already subscribed to %s\n", stasis_app_name(app), sub->device_name);
372 ast_debug(3, "Subscribing to device %s\n", sub->device_name);
374 sub->sub = stasis_subscribe_pool(topic, device_state_cb, ao2_bump(sub));
376 ast_log(LOG_ERROR, "Unable to subscribe to device %s\n",
378 /* Reference we added when attempting to stasis_subscribe_pool */
383 ao2_link(device_state_subscriptions, sub);
387 static int unsubscribe_device_state(struct stasis_app *app, const char *name)
389 RAII_VAR(struct device_state_subscription *, sub,
390 find_device_state_subscription(app, name), ao2_cleanup);
391 remove_device_state_subscription(sub);
395 static int device_to_json_cb(void *obj, void *arg, void *data, int flags)
397 struct device_state_subscription *sub = obj;
398 const char *app_name = arg;
399 struct ast_json *array = data;
401 if (strcmp(sub->app_name, app_name)) {
405 ast_json_array_append(
406 array, ast_json_string_create(sub->device_name));
411 static void devices_to_json(const struct stasis_app *app, struct ast_json *json)
413 struct ast_json *array = ast_json_array_create();
414 ao2_callback_data(device_state_subscriptions, OBJ_NODATA,
415 device_to_json_cb, (void *)stasis_app_name(app), array);
416 ast_json_object_set(json, "device_names", array);
419 struct stasis_app_event_source device_state_event_source = {
420 .scheme = DEVICE_STATE_SCHEME_SUB,
421 .find = find_device_state,
422 .subscribe = subscribe_device_state,
423 .unsubscribe = unsubscribe_device_state,
424 .is_subscribed = is_subscribed_device_state,
425 .to_json = devices_to_json
428 static int load_module(void)
431 if (ast_devstate_prov_add(DEVICE_STATE_PROVIDER_STASIS,
432 stasis_device_state_cb)) {
433 return AST_MODULE_LOAD_FAILURE;
436 if (!(device_state_subscriptions = ao2_container_alloc(
437 DEVICE_STATE_BUCKETS, device_state_subscriptions_hash,
438 device_state_subscriptions_cmp))) {
439 return AST_MODULE_LOAD_FAILURE;
442 stasis_app_register_event_source(&device_state_event_source);
443 return AST_MODULE_LOAD_SUCCESS;
446 static int unload_module(void)
448 ast_devstate_prov_del(DEVICE_STATE_PROVIDER_STASIS);
449 stasis_app_unregister_event_source(&device_state_event_source);
450 ao2_cleanup(device_state_subscriptions);
451 device_state_subscriptions = NULL;
455 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Stasis application device state support",
456 .support_level = AST_MODULE_SUPPORT_CORE,
458 .unload = unload_module,
459 .nonoptreq = "res_stasis"