Index: ccss.c =================================================================== --- ccss.c (revision 307673) +++ ccss.c (working copy) @@ -2,6 +2,8 @@ * Asterisk -- An open source telephony toolkit. * * Copyright (C) 1999 - 2010, Digium, Inc. + * Copyright (C) 2010 for DEV_STATE Additions Philippe Lindheimer + * * * Mark Michelson * @@ -33,6 +35,7 @@ #include "asterisk/utils.h" #include "asterisk/taskprocessor.h" #include "asterisk/event.h" +#include "asterisk/devicestate.h" #include "asterisk/module.h" #include "asterisk/app.h" #include "asterisk/cli.h" @@ -501,6 +504,114 @@ return 0; } +/* default values mapping from cc_state to ast_dev_state */ + +#define CC_AVAILABLE_DEVSTATE_DEFAULT AST_DEVICE_NOT_INUSE +#define CC_CALLER_OFFERED_DEVSTATE_DEFAULT AST_DEVICE_NOT_INUSE +#define CC_CALLER_REQUESTED_DEVSTATE_DEFAULT AST_DEVICE_NOT_INUSE +#define CC_ACTIVE_DEVSTATE_DEFAULT AST_DEVICE_INUSE +#define CC_CALLEE_READY_DEVSTATE_DEFAULT AST_DEVICE_RINGING +#define CC_CALLER_BUSY_DEVSTATE_DEFAULT AST_DEVICE_RINGINUSE +#define CC_RECALLING_DEVSTATE_DEFAULT AST_DEVICE_RINGING +#define CC_COMPLETE_DEVSTATE_DEFAULT AST_DEVICE_NOT_INUSE +#define CC_FAILED_DEVSTATE_DEFAULT AST_DEVICE_NOT_INUSE + +/*! + * \internal + * \brief structure to map CC_STATE to DEVICE_STATES + * + * Structure to map cc_state to an ast_device_state. Initialized here but + * configured through ccss.conf in iniitalize_cc_devstate_map() function. + */ +struct cc_state_2_devstate { + enum cc_state state; + enum ast_device_state devstate; +}; + +/*! + * \internal + * \brief initialization of defaults for CC_STATE to DEVICE_STATE map + */ +static struct cc_state_2_devstate cc_state_to_devstate_map[] = { + {CC_AVAILABLE, CC_AVAILABLE_DEVSTATE_DEFAULT}, + {CC_CALLER_OFFERED, CC_CALLER_OFFERED_DEVSTATE_DEFAULT}, + {CC_CALLER_REQUESTED, CC_CALLER_REQUESTED_DEVSTATE_DEFAULT}, + {CC_ACTIVE, CC_ACTIVE_DEVSTATE_DEFAULT}, + {CC_CALLEE_READY, CC_CALLEE_READY_DEVSTATE_DEFAULT}, + {CC_CALLER_BUSY, CC_CALLER_BUSY_DEVSTATE_DEFAULT}, + {CC_RECALLING, CC_RECALLING_DEVSTATE_DEFAULT}, + {CC_COMPLETE, CC_COMPLETE_DEVSTATE_DEFAULT}, + {CC_FAILED, CC_FAILED_DEVSTATE_DEFAULT}, +}; + +/*! + * \intenral + * \brief lookup the ast_device_state mapped to cc_state + * + * Returns the correponding DEVICE STATE from the cc_state_to_devstate_map + * when passed an internal. + */ +static enum ast_device_state cc_state_to_devstate(enum cc_state state) +{ + return cc_state_to_devstate_map[state].devstate; +} + +/*! + * \internal + * \brief Callback for devicestate providers + * + * initialize with ast_devstate_prov_add() and returns the corresponding + * DEVICE STATE based on the current CC_STATE statemachine if the requested + * device is found and is a generic device. Returns the equivalent of + * CC_FAIL which defaults to NOT_INUSE if no device is found which would + * indicate that there is no presence of and pending call back. + */ +static enum ast_device_state ccss_device_state(const char *device_name) +{ + struct cc_core_instance *core_instance; + unsigned long match_flags; + + match_flags = MATCH_NO_REQUEST; + if (!(core_instance = ao2_t_callback_data(cc_core_instances, 0, match_agent, (char *) device_name, &match_flags, "Find Core Instance for ccss_device_state reqeust."))) { + ast_debug(4, "Couldn't find a core instance for caller %s\n", device_name); + return cc_state_to_devstate(CC_FAILED); + } + + ast_debug(4, "Core %d: Found core_instance for caller %s in state %s\n", + core_instance->core_id, device_name, cc_state_to_string(core_instance->current_state)); + + /* TODO: at this point do I care if it is generic or not, it's still valid to return the state right? + * I would still have to make sure to call cc_unref() + */ + if (strcmp(core_instance->agent->callbacks->type, "generic")) { + ast_debug(4, "Core %d: Device State is only for generic agent types.\n", + core_instance->core_id); + cc_unref(core_instance, "Unref core_instance since ccss_device_state was called with native agent"); + return cc_state_to_devstate(CC_FAILED); + } + return cc_state_to_devstate(core_instance->current_state); +} + +/*! + * \internal + * \brief Notify Device State Changes from CC STATE MACHINE + * + * Any time a state is changed, we call this function to notify the DEVICE STATE + * subsystem of the change so that subscribed phones to any corresponding hints that + * are using that state are updated + */ +static void ccss_notify_device_state_change(const char *device, enum cc_state state) +{ + enum ast_device_state devstate; + + devstate = cc_state_to_devstate(state); + + ast_debug(4, "Notification of CCSS state change to '%s', device state '%s' for device '%s'", + cc_state_to_string(state), ast_devstate2str(devstate), device); + + ast_devstate_changed(devstate, "ccss:%s", device); +} + #define CC_OFFER_TIMER_DEFAULT 20 /* Seconds */ #define CCNR_AVAILABLE_TIMER_DEFAULT 7200 /* Seconds */ #define CCBS_AVAILABLE_TIMER_DEFAULT 4800 /* Seconds */ @@ -2967,6 +3078,11 @@ core_instance->current_state = args->state; res = state_change_funcs[core_instance->current_state](core_instance, args, previous_state); + /* If state change successful then notify any device state watchers of the change */ + if (!res && !strcmp(core_instance->agent->callbacks->type, "generic")) { + ccss_notify_device_state_change(core_instance->agent->device_name, core_instance->current_state); + } + ast_free(args); cc_unref(core_instance, "Unref since state change has completed"); /* From ao2_find */ return res; @@ -3905,7 +4021,9 @@ match_flags = MATCH_NO_REQUEST; if (!(core_instance = ao2_t_callback_data(cc_core_instances, 0, match_agent, device_name, &match_flags, "Find core instance for CallCompletionRequest"))) { ast_log_dynamic_level(cc_logger_level, "Couldn't find a core instance for caller %s\n", device_name); - return -1; + pbx_builtin_setvar_helper(chan, "CC_REQUEST_RESULT", "FAIL"); + pbx_builtin_setvar_helper(chan, "CC_REQUEST_REASON", "NO_CORE_INSTANCE"); + return 0; } ast_log_dynamic_level(cc_logger_level, "Core %d: Found core_instance for caller %s\n", @@ -3915,6 +4033,7 @@ ast_log_dynamic_level(cc_logger_level, "Core %d: CallCompletionRequest is only for generic agent types.\n", core_instance->core_id); pbx_builtin_setvar_helper(chan, "CC_REQUEST_RESULT", "FAIL"); + pbx_builtin_setvar_helper(chan, "CC_REQUEST_REASON", "AGENT_NOT_GENERIC"); cc_unref(core_instance, "Unref core_instance since CallCompletionRequest was called with native agent"); return 0; } @@ -3924,14 +4043,19 @@ core_instance->core_id); ast_cc_failed(core_instance->core_id, "Too many CC requests\n"); pbx_builtin_setvar_helper(chan, "CC_REQUEST_RESULT", "FAIL"); + pbx_builtin_setvar_helper(chan, "CC_REQUEST_REASON", "TO_MANY_REQUESTS"); cc_unref(core_instance, "Unref core_instance since too many CC requests"); return 0; } res = ast_cc_agent_accept_request(core_instance->core_id, "CallCompletionRequest called by caller %s for core_id %d", device_name, core_instance->core_id); pbx_builtin_setvar_helper(chan, "CC_REQUEST_RESULT", res ? "FAIL" : "SUCCESS"); + if (res) { + pbx_builtin_setvar_helper(chan, "CC_REQUEST_REASON", "REQUEST_NOT_ACCEPTED"); + } + cc_unref(core_instance, "Done with CallCompletionRequest"); - return res; + return 0; } static const char *cccancel_app = "CallCompletionCancel"; @@ -3948,18 +4072,26 @@ match_flags = MATCH_REQUEST; if (!(core_instance = ao2_t_callback_data(cc_core_instances, 0, match_agent, device_name, &match_flags, "Find core instance for CallCompletionCancel"))) { ast_log(LOG_WARNING, "Cannot find CC transaction to cancel for caller %s\n", device_name); - return -1; + pbx_builtin_setvar_helper(chan, "CC_CANCEL_RESULT", "FAIL"); + pbx_builtin_setvar_helper(chan, "CC_CANCEL_REASON", "NOTHING_TO_CANCEL"); + return 0; } if (strcmp(core_instance->agent->callbacks->type, "generic")) { ast_log(LOG_WARNING, "CallCompletionCancel may only be used for calles with a generic agent\n"); cc_unref(core_instance, "Unref core instance found during CallCompletionCancel"); - return -1; + pbx_builtin_setvar_helper(chan, "CC_CANCEL_RESULT", "FAIL"); + pbx_builtin_setvar_helper(chan, "CC_CANCEL_REASON", "NOT_GENERIC"); + return 0; } res = ast_cc_failed(core_instance->core_id, "Call completion request Cancelled for core ID %d by caller %s", core_instance->core_id, device_name); cc_unref(core_instance, "Unref core instance found during CallCompletionCancel"); - return res; + pbx_builtin_setvar_helper(chan, "CC_CANCEL_RESULT", res ? "FAIL" : "SUCCESS"); + if (res) { + pbx_builtin_setvar_helper(chan, "CC_CANCEL_REASON", "CANCEL_FAILED"); + } + return 0; } struct count_monitors_cb_data { @@ -4028,6 +4160,100 @@ return; } +/*! + * \brief initializes cc_state_to_devstate_map from ccss.conf + * + * The cc_state_to_devstate_map[] is already initialized with all the + * default values. This will update that structure with any changes + * from the ccss.conf file. The configuration parameters in ccss.conf + * should use any valid device state form that is recognized by + * ast_devstate_val() function + */ +static void initialize_cc_devstate_map(void) +{ + struct ast_config *cc_config; + struct ast_flags config_flags = {0,}; + const char *cc_devstate_str; + enum ast_device_state this_devstate; + + /* don't need to set these, they are initialized in the struct: + * + cc_state_to_devstate_map[CC_AVAILABLE].devstate = CC_AVAILABLE_DEVSTATE_DEFAULT; + cc_state_to_devstate_map[CC_CALLER_OFFERED].devstate = CC_CALLER_OFFERED_DEVSTATE_DEFAULT; + cc_state_to_devstate_map[CC_CALLER_REQUESTED].devstate = CC_CALLER_REQUESTED_DEVSTATE_DEFAULT; + cc_state_to_devstate_map[CC_ACTIVE].devstate = CC_ACTIVE_DEVSTATE_DEFAULT; + cc_state_to_devstate_map[CC_CALLEE_READY].devstate = CC_CALLEE_READY_DEVSTATE_DEFAULT; + cc_state_to_devstate_map[CC_CALLER_BUSY].devstate = CC_CALLER_BUSY_DEVSTATE_DEFAULT; + cc_state_to_devstate_map[CC_RECALLING].devstate = CC_RECALLING_DEVSTATE_DEFAULT; + cc_state_to_devstate_map[CC_COMPLETE].devstate = CC_COMPLETE_DEVSTATE_DEFAULT; + cc_state_to_devstate_map[CC_FAILED].devstate = CC_FAILED_DEVSTATE_DEFAULT; + */ + + cc_config = ast_config_load2("ccss.conf", "ccss", config_flags); + if (!cc_config || cc_config == CONFIG_STATUS_FILEINVALID) { + ast_log(LOG_WARNING, "Could not find valid ccss.conf file. Using cc_[state]_devstate defaults\n"); + return; + } + + if ((cc_devstate_str = ast_variable_retrieve(cc_config, "general", "cc_available_devstate"))) { + this_devstate = ast_devstate_val(cc_devstate_str); + if (this_devstate != AST_DEVICE_UNKNOWN) { + cc_state_to_devstate_map[CC_AVAILABLE].devstate = this_devstate; + } + } + if ((cc_devstate_str = ast_variable_retrieve(cc_config, "general", "cc_caller_offered_devstate"))) { + this_devstate = ast_devstate_val(cc_devstate_str); + if (this_devstate != AST_DEVICE_UNKNOWN) { + cc_state_to_devstate_map[CC_CALLER_OFFERED].devstate = this_devstate; + } + } + if ((cc_devstate_str = ast_variable_retrieve(cc_config, "general", "cc_caller_requested_devstate"))) { + this_devstate = ast_devstate_val(cc_devstate_str); + if (this_devstate != AST_DEVICE_UNKNOWN) { + cc_state_to_devstate_map[CC_CALLER_REQUESTED].devstate = this_devstate; + } + } + if ((cc_devstate_str = ast_variable_retrieve(cc_config, "general", "cc_active_devstate"))) { + this_devstate = ast_devstate_val(cc_devstate_str); + if (this_devstate != AST_DEVICE_UNKNOWN) { + cc_state_to_devstate_map[CC_ACTIVE].devstate = this_devstate; + } + } + if ((cc_devstate_str = ast_variable_retrieve(cc_config, "general", "cc_callee_ready_devstate"))) { + this_devstate = ast_devstate_val(cc_devstate_str); + if (this_devstate != AST_DEVICE_UNKNOWN) { + cc_state_to_devstate_map[CC_CALLEE_READY].devstate = this_devstate; + } + } + if ((cc_devstate_str = ast_variable_retrieve(cc_config, "general", "cc_caller_busy_devstate"))) { + this_devstate = ast_devstate_val(cc_devstate_str); + if (this_devstate != AST_DEVICE_UNKNOWN) { + cc_state_to_devstate_map[CC_CALLER_BUSY].devstate = this_devstate; + } + } + if ((cc_devstate_str = ast_variable_retrieve(cc_config, "general", "cc_recalling_devstate"))) { + this_devstate = ast_devstate_val(cc_devstate_str); + if (this_devstate != AST_DEVICE_UNKNOWN) { + cc_state_to_devstate_map[CC_RECALLING].devstate = this_devstate; + } + } + if ((cc_devstate_str = ast_variable_retrieve(cc_config, "general", "cc_complete_devstate"))) { + this_devstate = ast_devstate_val(cc_devstate_str); + if (this_devstate != AST_DEVICE_UNKNOWN) { + cc_state_to_devstate_map[CC_COMPLETE].devstate = this_devstate; + } + } + if ((cc_devstate_str = ast_variable_retrieve(cc_config, "general", "cc_failed_devstate"))) { + this_devstate = ast_devstate_val(cc_devstate_str); + if (this_devstate != AST_DEVICE_UNKNOWN) { + cc_state_to_devstate_map[CC_FAILED].devstate = this_devstate; + } + } + + ast_config_destroy(cc_config); + return; +} + static void cc_cli_print_monitor_stats(struct ast_cc_monitor *monitor, int fd, int parent_id) { struct ast_cc_monitor *child_monitor_iter = monitor; @@ -4220,9 +4446,14 @@ res |= ast_register_application2(cccancel_app, cccancel_exec, NULL, NULL, NULL); res |= ast_cc_monitor_register(&generic_monitor_cbs); res |= ast_cc_agent_register(&generic_agent_callbacks); + + /* Register the device state callback for generic agents */ + res |= ast_devstate_prov_add("ccss", ccss_device_state); + ast_cli_register_multiple(cc_cli, ARRAY_LEN(cc_cli)); cc_logger_level = ast_logger_register_level(CC_LOGGER_LEVEL_NAME); dialed_cc_interface_counter = 1; initialize_cc_max_requests(); + initialize_cc_devstate_map(); return res; }