Add Device State Information CCSS for Generic Devices.
authorRichard Mudgett <rmudgett@digium.com>
Thu, 14 Apr 2011 18:22:35 +0000 (18:22 +0000)
committerRichard Mudgett <rmudgett@digium.com>
Thu, 14 Apr 2011 18:22:35 +0000 (18:22 +0000)
Add Asterisk Device State information and callbacks to the Call Completion
Supplemental Services for generic agents.

There are currently not many devices that have native support for CCSS.
Even as the devices become available there may be other reasons why one
may choose to not take advantage of the native abilities and stick with
the generic implementation.  The generic implementation is quite capable
and could be greatly enhanced by adding device state capabilities.  A
phone could then subscribe to the device state with a BLF key in
conjunction with Asterisk hints.

The advantages of the device state information would allow a single button
to: request CCSS, cancel a CCSS request, and display the current state of
a CCSS request.

For example, you may have a single button that when not lit, there is no
active CCSS request.  When you press that button, the dialplan can query
the DEVICE_STATE() associated with that caller to determine whether they
should be calling CallCompletionRequest() or CallCompletionCancel().  If
there is currently a pending request, then the dialplan would cancel it.
This also has the advantage of showing the true state of a request, which
is an asynchronous call, even when CallCompletionRequest() thinks it was
successful.  The actual request could ultimately fail.  Once lit, further
feedback can be provided to the caller about the current state of their
request since it will be updated by the CCSS State Machine as appropriate.

The DEVICE_STATE mapping is configurable since the BLF being used on a
given phone type may vary.  The idea is to allow some level of
customization as to the phone's behavior.

As an example, you may want the BLF key to go solid once you have
requested a callback.  You may then want the LED to blink (typically
ringing) when either the callback is in process, which is a visual
indication that the incoming call is the desired callback.  You may want
it to blink when the callee is ready but you are busy, giving you a visual
indication that the target is available as you may want to get off the
line so that the callback can be successful.

Device state information is sent back via the ast_devstate_prov_add()
callback for any generic CCSS device as it traverses through the state
machine.  You simply provide a map between CC_STATE values and the
corresponding AST_DEVICE state values.

You could then generate hints against these states similar to what is
possible today with Custom Devstates or MeetMe states.  For example, you
may have an extension 3000 that is currently associated with device
SIP/3000.  You could then create a feature code for that extension that
may look something like:

exten => *823000,hint,ccss:sip/3000

You would then subscribe a BLF button to *823000 which would point to the
dialplan that handled CCSS requests/cancels using the available
DEVICE_STATE() information about ccss:sip/3000 to make the decision about
what to do.

(closes issue #18788)
Reported by: p_lindheimer
Patches:
      ccss.trunk.18788.patch uploaded by p lindheimer (license 558)
      Modified with final reviewboard comments.
Tested by: p_lindheimer, loloski

Review: https://reviewboard.asterisk.org/r/1105/

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@313744 65c4cc65-6c06-0410-ace0-fbb531ad65f3

CREDITS
configs/ccss.conf.sample
main/ccss.c

diff --git a/CREDITS b/CREDITS
index 6c8f7ff..a9e9687 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -222,6 +222,8 @@ ClearIT AB, Sweden - res_mutestream, queue_exists and various other patches (dev
 Despegar.com, Argentina - AstData API implementation, also sponsored by Google as part of the
        gsoc/2009 program (developed by Eliel)
 
 Despegar.com, Argentina - AstData API implementation, also sponsored by Google as part of the
        gsoc/2009 program (developed by Eliel)
 
+Philippe Lindheimer - DEV_STATE additions to CCSS
+
 === OTHER CONTRIBUTIONS ===
 John Todd - Monkey sounds and associated teletorture prompt
 Michael Jerris - bug marshaling
 === OTHER CONTRIBUTIONS ===
 John Todd - Monkey sounds and associated teletorture prompt
 Michael Jerris - bug marshaling
index bb78cad..2636f7e 100644 (file)
@@ -6,12 +6,54 @@
 ;
 
 [general]
 ;
 
 [general]
-; There is only a single option that may be defined in this file.
 ; The cc_max_requests option is a global limit on the number of
 ; CC requests that may be in the Asterisk system at any time.
 ;
 ;cc_max_requests = 20
 ;
 ; The cc_max_requests option is a global limit on the number of
 ; CC requests that may be in the Asterisk system at any time.
 ;
 ;cc_max_requests = 20
 ;
+; The cc_STATE_devstate variables listed below can be used to change the
+; default mapping of the internal state machine tracking the state of
+; call completion to an Asterisk Device State value. The acceptable values
+; that can be provided are as follows, with a description of what the
+; equivalent device BLF that this maps to:
+;
+;      UNKNOWN      ; Device is valid but channel didn't know state
+;      NOT_INUSE    ; Device is not used
+;      INUSE        ; Device is in use
+;      BUSY         ; Device is busy
+;      INVALID      ; Device is invalid
+;      UNAVAILABLE  ; Device is unavailable
+;      RINGING      ; Device is ringing
+;      RINGINUSE    ; Device is ringing *and* in use
+;      ONHOLD       ; Device is on hold
+;
+; These states are used to generate DEVICE_STATE information that can be
+; included with Asterisk hints for phones to subscribe to the state information
+; or dialplan to check the state using the EXTENSION_STATE() function or
+; the DEVICE_STATE() function.
+;
+; The states are in the format of: "ccss:TECH/ID" so an example of device
+; SIP/3000 making a CallCompletionRequest() could be checked  by looking at
+; DEVICE_STATE(ccss:SIP/3000) or an Asterisk Hint could be generated such as
+;
+; [hint-context]
+; exten => *843000,hint,ccss:SIP/3000
+;
+; and then accessed with EXTENSION_STATE(*843000@hint-context)
+; or subscribed to with a BLF button on a phone.
+;
+; The available state mapping and default values are:
+;
+; cc_available_devstate = NOT_INUSE
+; cc_offered_devstate = NOT_INUSE
+; cc_caller_requested_devstate = NOT_INUSE
+; cc_active_devstate = INUSE
+; cc_callee_ready_devstate = INUSE
+; cc_caller_busy_devstate = ONHOLD
+; cc_recalling_devstate = RINGING
+; cc_complete_devstate = NOT_INUSE
+; cc_failed_devstate = NOT_INUSE
+
 ;
 ;============================================
 ;           PLEASE READ THIS!!!
 ;
 ;============================================
 ;           PLEASE READ THIS!!!
index cdd0be7..e440b34 100644 (file)
@@ -33,6 +33,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/utils.h"
 #include "asterisk/taskprocessor.h"
 #include "asterisk/event.h"
 #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"
 #include "asterisk/module.h"
 #include "asterisk/app.h"
 #include "asterisk/cli.h"
@@ -530,6 +531,111 @@ static int count_agents_cb(void *obj, void *arg, void *data, int flags)
        return 0;
 }
 
        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_ONHOLD
+#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 initialization of defaults for CC_STATE to DEVICE_STATE map
+ */
+static enum ast_device_state 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
+ *
+ * \return the correponding DEVICE STATE from the cc_state_to_devstate_map
+ * when passed an internal state.
+ */
+static enum ast_device_state cc_state_to_devstate(enum cc_state state)
+{
+       return cc_state_to_devstate_map[state];
+}
+
+/*!
+ * \internal
+ * \brief Callback for devicestate providers
+ *
+ * \details
+ * Initialize with ast_devstate_prov_add() and returns the corresponding
+ * DEVICE STATE based on the current CC_STATE state machine if the requested
+ * device is found and is a generic device. Returns the equivalent of
+ * CC_FAILED, which defaults to NOT_INUSE, if no device is found.  NOT_INUSE would
+ * indicate that there is no presence of any 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;
+       enum ast_device_state cc_current_state;
+
+       match_flags = MATCH_NO_REQUEST;
+       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.");
+       if (!core_instance) {
+               ast_log_dynamic_level(cc_logger_level,
+                       "Couldn't find a core instance for caller %s\n", device_name);
+               return cc_state_to_devstate(CC_FAILED);
+       }
+
+       ast_log_dynamic_level(cc_logger_level,
+               "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));
+
+       if (strcmp(core_instance->agent->callbacks->type, "generic")) {
+               ast_log_dynamic_level(cc_logger_level,
+                       "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);
+       }
+       cc_current_state = cc_state_to_devstate(core_instance->current_state);
+       cc_unref(core_instance, "Unref core_instance done with ccss_device_state");
+       return cc_current_state;
+}
+
+/*!
+ * \internal
+ * \brief Notify Device State Changes from CC STATE MACHINE
+ *
+ * \details
+ * 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_log_dynamic_level(cc_logger_level,
+               "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 */
 #define CC_OFFER_TIMER_DEFAULT                 20              /* Seconds */
 #define CCNR_AVAILABLE_TIMER_DEFAULT   7200    /* Seconds */
 #define CCBS_AVAILABLE_TIMER_DEFAULT   4800    /* Seconds */
@@ -3014,6 +3120,11 @@ static int cc_do_state_change(void *datap)
        core_instance->current_state = args->state;
        res = state_change_funcs[core_instance->current_state](core_instance, args, previous_state);
 
        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;
        ast_free(args);
        cc_unref(core_instance, "Unref since state change has completed"); /* From ao2_find */
        return res;
@@ -4102,6 +4213,59 @@ static void initialize_cc_max_requests(void)
        return;
 }
 
        return;
 }
 
+/*!
+ * \internal
+ * \brief helper function to parse and configure each devstate map
+ */
+static void initialize_cc_devstate_map_helper(struct ast_config *cc_config, enum cc_state state, const char *cc_setting)
+{
+       const char *cc_devstate_str;
+       enum ast_device_state this_devstate;
+
+       if ((cc_devstate_str = ast_variable_retrieve(cc_config, "general", cc_setting))) {
+               this_devstate = ast_devstate_val(cc_devstate_str);
+               if (this_devstate != AST_DEVICE_UNKNOWN) {
+                       cc_state_to_devstate_map[state] = this_devstate;
+               }
+       }
+}
+
+/*!
+ * \internal
+ * \brief initializes cc_state_to_devstate_map from ccss.conf
+ *
+ * \details
+ * 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, };
+
+       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;
+       }
+
+       initialize_cc_devstate_map_helper(cc_config, CC_AVAILABLE, "cc_available_devstate");
+       initialize_cc_devstate_map_helper(cc_config, CC_CALLER_OFFERED, "cc_caller_offered_devstate");
+       initialize_cc_devstate_map_helper(cc_config, CC_CALLER_REQUESTED, "cc_caller_requested_devstate");
+       initialize_cc_devstate_map_helper(cc_config, CC_ACTIVE, "cc_active_devstate");
+       initialize_cc_devstate_map_helper(cc_config, CC_CALLEE_READY, "cc_callee_ready_devstate");
+       initialize_cc_devstate_map_helper(cc_config, CC_CALLER_BUSY, "cc_caller_busy_devstate");
+       initialize_cc_devstate_map_helper(cc_config, CC_RECALLING, "cc_recalling_devstate");
+       initialize_cc_devstate_map_helper(cc_config, CC_COMPLETE, "cc_complete_devstate");
+       initialize_cc_devstate_map_helper(cc_config, CC_FAILED, "cc_failed_devstate");
+
+       ast_config_destroy(cc_config);
+}
+
 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;
 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;
@@ -4297,9 +4461,15 @@ int ast_cc_init(void)
        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);
        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);
+
        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();
        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();
+
+       /* Read the map and register the device state callback for generic agents */
+       initialize_cc_devstate_map();
+       res |= ast_devstate_prov_add("ccss", ccss_device_state);
+
        return res;
 }
        return res;
 }