METERMAIDS:
authorOlle Johansson <oej@edvina.net>
Mon, 26 Jun 2006 16:43:21 +0000 (16:43 +0000)
committerOlle Johansson <oej@edvina.net>
Mon, 26 Jun 2006 16:43:21 +0000 (16:43 +0000)
-----------
- Adding devicestate providers, a new architecture to add non-channel related
  device state information, like parking lots, queues, meetmes, vending machines
  and Windows 98 reboots (lots of blinking on those lights)
- Adding provider for parking lots, so you can subscribe to the status of a
  parking lot
- Adding provider for meetme, so you can have a blinking lamp for a meetme
  ( Example: exten => edvina,hint,meetme:1234 )
- Adding support for directed parking - set the PARKINGEXTEN before you manually
  call Park() and you will be parked on that space. If it's occupied, dialplan
  execution will continue.

This work was sponsored by Voop A/S - www.voop.com

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

apps/app_meetme.c
configs/extensions.conf.sample
devicestate.c
include/asterisk/devicestate.h
pbx.c
res/res_features.c

index 16cf7f1..e162c56 100644 (file)
@@ -58,6 +58,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/utils.h"
 #include "asterisk/translate.h"
 #include "asterisk/ulaw.h"
+#include "asterisk/devicestate.h"
 
 #include "enter.h"
 #include "leave.h"
@@ -274,7 +275,7 @@ struct ast_conference {
        int markedusers;                        /*!< Number of marked users */
        time_t start;                           /*!< Start time (s) */
        int refcount;                           /*!< reference count of usage */
-       enum recording_state recording:2;               /*!< recording status */
+       enum recording_state recording:2;       /*!< recording status */
        unsigned int isdynamic:1;               /*!< Created on the fly? */
        unsigned int locked:1;                  /*!< Is the conference locked? */
        pthread_t recordthread;                 /*!< thread for recording */
@@ -967,6 +968,10 @@ static int conf_run(struct ast_channel *chan, struct ast_conference *conf, int c
        snprintf(members, sizeof(members), "%d", conf->users);
        ast_update_realtime("meetme", "confno", conf->confno, "members", members , NULL);
 
+       /* This device changed state now - if this is the first user */
+       if (conf->users == 1)
+               ast_device_state_changed("meetme:%s", conf->confno);
+
        ast_mutex_unlock(&conf->playlock);
 
        if (confflags & CONFFLAG_EXIT_CONTEXT) {
@@ -1742,6 +1747,10 @@ bailoutandtrynormal:
                /* Return the number of seconds the user was in the conf */
                snprintf(meetmesecs, sizeof(meetmesecs), "%d", (int) (time(NULL) - user->jointime));
                pbx_builtin_setvar_helper(chan, "MEETMESECS", meetmesecs);
+
+               /* This device changed state now */
+               if (!conf->users)       /* If there are no more members */
+                       ast_device_state_changed("meetme:%s", conf->confno);
        }
        free(user);
        AST_LIST_UNLOCK(&confs);
@@ -2510,6 +2519,29 @@ static void *recordthread(void *args)
        pthread_exit(0);
 }
 
+/*! \brief Callback for devicestate providers */
+static int meetmestate(const char *data)
+{
+       struct ast_conference *conf;
+
+       /* Find conference */
+       AST_LIST_LOCK(&confs);
+       AST_LIST_TRAVERSE(&confs, conf, list) {
+               if (!strcmp(data, conf->confno))
+                       break;
+       }
+       AST_LIST_UNLOCK(&confs);
+       if (!conf)
+               return AST_DEVICE_INVALID;
+
+
+       /* SKREP to fill */
+       if (!conf->users)
+               return AST_DEVICE_NOT_INUSE;
+
+       return AST_DEVICE_INUSE;
+}
+
 static void load_config(void)
 {
        struct ast_config *cfg;
@@ -2547,6 +2579,7 @@ static int unload_module(void *mod)
        res |= ast_unregister_application(app2);
        res |= ast_unregister_application(app);
 
+       ast_devstate_prov_del("Meetme");
        STANDARD_HANGUP_LOCALUSERS;
 
        return res;
@@ -2565,6 +2598,7 @@ static int load_module(void *mod)
        res |= ast_register_application(app2, count_exec, synopsis2, descrip2);
        res |= ast_register_application(app, conf_exec, synopsis, descrip);
 
+       res |= ast_devstate_prov_add("Meetme", meetmestate);
        return res;
 }
 
index f74f350..e9dda61 100644 (file)
@@ -278,11 +278,14 @@ include => trunkld
 ;
 ignorepat => 9
 include => default
-include => parkedcalls
 include => trunklocal
 include => iaxtel700
 include => trunktollfree
 include => iaxprovider
+
+;Include parkedcalls (or the context you define in features conf)
+;to enable call parking.
+include => parkedcalls
 ;
 ; You can use an alternative switch type as well, to resolve
 ; extensions that are not known here, for example with remote 
@@ -511,6 +514,12 @@ include => demo
 ;exten => mark,1,Goto(6275|1)                  ; alias mark to 6275
 ;exten => 6536,1,Macro(stdexten,6236,${WIL})   ; Ditto for wil
 ;exten => wil,1,Goto(6236|1)
+
+;If you want to subscribe to the status of a parking space, this is
+;how you do it. Subscribe to extension 6600 in sip, and you will see
+;the status of the first parking lot with this extensions' help
+;exten => 6600,hint,park:701@parkedcalls
+;exten => 6600,1,noop
 ;
 ; Some other handy things are an extension for checking voicemail via
 ; voicemailmain
index 4121cfb..b0e5d5a 100644 (file)
@@ -40,6 +40,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/logger.h"
 #include "asterisk/devicestate.h"
 #include "asterisk/pbx.h"
+#include "asterisk/app.h"
 #include "asterisk/options.h"
 
 /*! \brief Device state strings for printing */
@@ -53,6 +54,16 @@ static const char *devstatestring[] = {
        /* 6 AST_DEVICE_RINGING */      "Ringing"       /*!< Ring, ring, ring */
 };
 
+/*! \brief  A device state provider (not a channel) */
+struct devstate_prov {
+       char label[40];
+       ast_devstate_prov_cb_type callback;
+       AST_LIST_ENTRY(devstate_prov) list;
+};
+
+/*! \brief A list of providers */
+static AST_LIST_HEAD_STATIC(devstate_provs, devstate_prov);
+
 /*! \brief  A device state watcher (callback) */
 struct devstate_cb {
        void *data;
@@ -60,6 +71,7 @@ struct devstate_cb {
        AST_LIST_ENTRY(devstate_cb) list;
 };
 
+/*! \brief A device state watcher list */
 static AST_LIST_HEAD_STATIC(devstate_cbs, devstate_cb);
 
 struct state_change {
@@ -67,11 +79,19 @@ struct state_change {
        char device[1];
 };
 
+/*! \brief The state change queue. State changes are queued
+       for processing by a separate thread */
 static AST_LIST_HEAD_STATIC(state_changes, state_change);
 
+/*! \brief The device state change notification thread */
 static pthread_t change_thread = AST_PTHREADT_NULL;
+
+/*! \brief Flag for the queue */
 static ast_cond_t change_pending;
 
+/* Forward declarations */
+static int getproviderstate(const char *provider, const char *address);
+
 /*! \brief Find devicestate as text message for output */
 const char *devstate2str(int devstate) 
 {
@@ -79,9 +99,9 @@ const char *devstate2str(int devstate)
 }
 
 /*! \brief Find out if device is active in a call or not 
-\note find channels with the device's name in it
-This function is only used for channels that does not implement 
-devicestate natively
+       \note find channels with the device's name in it
+       This function is only used for channels that does not implement 
+       devicestate natively
 */
 int ast_parse_device_state(const char *device)
 {
@@ -110,17 +130,34 @@ int ast_parse_device_state(const char *device)
 int ast_device_state(const char *device)
 {
        char *buf;
-       char *tech;
        char *number;
        const struct ast_channel_tech *chan_tech;
        int res = 0;
+       /*! \brief Channel driver that provides device state */
+       char *tech;
+       /*! \brief Another provider of device state */
+       char *provider = NULL;
        
        buf = ast_strdupa(device);
        tech = strsep(&buf, "/");
        number = buf;
-       if (!number)
-               return AST_DEVICE_INVALID;
-               
+       if (!number) {
+               provider = strsep(&tech, ":");
+               if (!provider)
+                       return AST_DEVICE_INVALID;
+               /* We have a provider */
+               number = tech;
+               tech = NULL;
+       }
+
+       if (provider)  {
+               if(option_debug > 2)
+                       ast_log(LOG_DEBUG, "Checking if I can find provider for \"%s\" - number: %s\n", provider, number);
+               return getproviderstate(provider, number);
+       }
+       if (option_debug > 3)
+               ast_log(LOG_DEBUG, "No provider found, checking channel drivers for %s - %s\n", tech, number);
+
        chan_tech = ast_get_channel_tech(tech);
        if (!chan_tech)
                return AST_DEVICE_INVALID;
@@ -143,6 +180,63 @@ int ast_device_state(const char *device)
        }
 }
 
+/*! \brief Add device state provider */
+int ast_devstate_prov_add(const char *label, ast_devstate_prov_cb_type callback)
+{
+       struct devstate_prov *devprov;
+
+       if (!callback || !(devprov = ast_calloc(1, sizeof(*devprov))))
+               return -1;
+
+       devprov->callback = callback;
+       ast_copy_string(devprov->label, label, sizeof(devprov->label));
+
+       AST_LIST_LOCK(&devstate_provs);
+       AST_LIST_INSERT_HEAD(&devstate_provs, devprov, list);
+       AST_LIST_UNLOCK(&devstate_provs);
+
+       return 0;
+}
+
+/*! \brief Remove device state provider */
+void ast_devstate_prov_del(const char *label)
+{
+       struct devstate_prov *devcb;
+
+       AST_LIST_LOCK(&devstate_provs);
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&devstate_provs, devcb, list) {
+               if (!strcasecmp(devcb->label, label)) {
+                       AST_LIST_REMOVE_CURRENT(&devstate_provs, list);
+                       free(devcb);
+                       break;
+               }
+       }
+       AST_LIST_TRAVERSE_SAFE_END;
+       AST_LIST_UNLOCK(&devstate_provs);
+}
+
+/*! \brief Get provider device state */
+static int getproviderstate(const char *provider, const char *address)
+{
+       struct devstate_prov *devprov;
+       int res = AST_DEVICE_INVALID;
+
+
+       AST_LIST_LOCK(&devstate_provs);
+       AST_LIST_TRAVERSE_SAFE_BEGIN(&devstate_provs, devprov, list) {
+               if(option_debug > 4)
+                       ast_log(LOG_DEBUG, "Checking provider %s with %s\n", devprov->label, provider);
+
+               if (!strcasecmp(devprov->label, provider)) {
+                       res = devprov->callback(address);
+                       break;
+               }
+       }
+       AST_LIST_TRAVERSE_SAFE_END;
+       AST_LIST_UNLOCK(&devstate_provs);
+       return res;
+}
+
 /*! \brief Add device state watcher */
 int ast_devstate_add(ast_devstate_cb_type callback, void *data)
 {
@@ -178,7 +272,9 @@ void ast_devstate_del(ast_devstate_cb_type callback, void *data)
        AST_LIST_UNLOCK(&devstate_cbs);
 }
 
-/*! \brief Notify callback watchers of change, and notify PBX core for hint updates */
+/*! \brief Notify callback watchers of change, and notify PBX core for hint updates
+       Normally executed within a separate thread
+*/
 static void do_state_change(const char *device)
 {
        int state;
@@ -201,10 +297,15 @@ static int __ast_device_state_changed_literal(char *buf)
        char *device, *tmp;
        struct state_change *change;
 
+       if (option_debug > 2)
+               ast_log(LOG_DEBUG, "Notification of state change to be queued on device/channel %s\n", buf);
+
        device = buf;
        if ((tmp = strrchr(device, '-')))
                *tmp = '\0';
 
+       
+
        if (change_thread == AST_PTHREADT_NULL || !(change = ast_calloc(1, sizeof(*change) + strlen(device)))) {
                /* we could not allocate a change struct, or */
                /* there is no background thread, so process the change now */
index 3c7f5b4..d93d6d9 100644 (file)
@@ -44,8 +44,12 @@ extern "C" {
 /*! Device is ringing *and* in use */
 #define AST_DEVICE_RINGINUSE   7
 
+/*! \brief Devicestate watcher call back */
 typedef int (*ast_devstate_cb_type)(const char *dev, int state, void *data);
 
+/*!  \brief Devicestate provider call back */
+typedef int (*ast_devstate_prov_cb_type)(const char *data);
+
 /*! \brief Convert device state to text string for output 
  * \param devstate Current device state 
  */
@@ -95,8 +99,28 @@ int ast_device_state_changed_literal(const char *device);
  * Return -1 on failure, ID on success
  */ 
 int ast_devstate_add(ast_devstate_cb_type callback, void *data);
+
+/*! \brief Unregisters a device state change callback 
+ * \param callback Callback
+ * \param data to pass to callback
+ * The callback is called if the state for extension is changed
+ * Return -1 on failure, ID on success
+ */ 
 void ast_devstate_del(ast_devstate_cb_type callback, void *data);
 
+/*! \brief Add device state provider 
+ * \param label to use in hint, like label:object
+ * \param callback Callback
+ * Return -1 on failure, ID on success
+ */ 
+int ast_devstate_prov_add(const char *label, ast_devstate_prov_cb_type callback);
+
+/*! \brief Remove device state provider 
+ * \param label to use in hint, like label:object
+ * Return -1 on failure, ID on success
+ */ 
+void ast_devstate_prov_del(const char *label);
+
 int ast_device_state_engine_init(void);
 
 #if defined(__cplusplus) || defined(c_plusplus)
diff --git a/pbx.c b/pbx.c
index 7bbe3bc..560a932 100644 (file)
--- a/pbx.c
+++ b/pbx.c
@@ -188,7 +188,7 @@ struct ast_state_cb {
 
 /*! \brief Structure for dial plan hints
 
-  Hints are pointers from an extension in the dialplan to one or
+  \note Hints are pointers from an extension in the dialplan to one or
   more devices (tech/name) */
 struct ast_hint {
        struct ast_exten *exten;        /*!< Extension */
index 43586ce..588229e 100644 (file)
@@ -56,6 +56,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/manager.h"
 #include "asterisk/utils.h"
 #include "asterisk/adsi.h"
+#include "asterisk/devicestate.h"
 #include "asterisk/monitor.h"
 
 #ifdef __AST_DEBUG_MALLOC
@@ -76,6 +77,7 @@ static void FREE(void *ptr)
 
 static char *parkedcall = "ParkedCall";
 
+static int parkaddhints = 0;                               /*!< Add parking hints automatically */
 static int parkingtime = DEFAULT_PARK_TIME;                /*!< No more than 45 seconds parked before you do something with them */
 static char parking_con[AST_MAX_EXTENSION];                /*!< Context for which parking is made accessible */
 static char parking_con_dial[AST_MAX_EXTENSION];           /*!< Context for dialback for parking (KLUDGE) */
@@ -119,7 +121,11 @@ static char *descrip2 = "Park():"
 "transfer to know the parking space). This application is always\n"
 "registered internally and does not need to be explicitly added\n"
 "into the dialplan, although you should include the 'parkedcalls'\n"
-"context.\n";
+"context (or the context specified in features.conf).\n\n"
+"If you set the PARKINGEXTEN variable to an extension in your\n"
+"parking context, park() will park the call on that extension, unless\n"
+"it already exists. In that case, execution will continue at next\n"
+"priority.\n" ;
 
 static struct ast_app *monitor_app = NULL;
 static int monitor_ok = 1;
@@ -128,6 +134,7 @@ struct parkeduser {
        struct ast_channel *chan;                   /*!< Parking channel */
        struct timeval start;                       /*!< Time the parking started */
        int parkingnum;                             /*!< Parking lot */
+       char parkingexten[AST_MAX_EXTENSION];       /*!< If set beforehand, parking extension used for this call */
        char context[AST_MAX_CONTEXT];              /*!< Where to go if our parking time expires */
        char exten[AST_MAX_EXTENSION];
        int priority;
@@ -248,14 +255,14 @@ static void ast_bridge_call_thread_launch(void *data)
        pthread_setschedparam(thread, SCHED_RR, &sched);
 }
 
-static int adsi_announce_park(struct ast_channel *chan, int parkingnum)
+static int adsi_announce_park(struct ast_channel *chan, char *parkingexten)
 {
        int res;
        int justify[5] = {ADSI_JUST_CENT, ADSI_JUST_CENT, ADSI_JUST_CENT, ADSI_JUST_CENT};
        char tmp[256];
        char *message[5] = {NULL, NULL, NULL, NULL, NULL};
 
-       snprintf(tmp, sizeof(tmp), "Parked on %d", parkingnum);
+       snprintf(tmp, sizeof(tmp), "Parked on %s", parkingexten);
        message[0] = tmp;
        res = adsi_load_session(chan, NULL, 0, 1);
        if (res == -1)
@@ -263,46 +270,95 @@ static int adsi_announce_park(struct ast_channel *chan, int parkingnum)
        return adsi_print(chan, message, justify, 1);
 }
 
+/*! \brief Notify metermaids that we've changed an extension */
+static void notify_metermaids(char *exten, char *context)
+{
+       if (option_debug > 3)
+               ast_log(LOG_DEBUG, "Notification of state change to metermaids %s@%s\n", exten, context);
+
+       /* Send notification to devicestate subsystem */
+       ast_device_state_changed("park:%s@%s", exten, context);
+       return;
+}
+
+/*! \brief metermaids callback from devicestate.c */
+static int metermaidstate(const char *data)
+{
+       int res = AST_DEVICE_INVALID;
+       char *context = ast_strdupa(data);
+       char *exten;
+
+       exten = strsep(&context, "@");
+       if (!context)
+               return res;
+       
+       if (option_debug > 3)
+               ast_log(LOG_DEBUG, "Checking state of exten %s in context %s\n", exten, context);
+
+       res = ast_exists_extension(NULL, context, exten, 1, NULL);
+
+       if (!res)
+               return AST_DEVICE_NOT_INUSE;
+       else
+               return AST_DEVICE_INUSE;
+}
+
 /*! \brief Park a call 
        \note We put the user in the parking list, then wake up the parking thread to be sure it looks
        after these channels too */
 int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeout, int *extout)
 {
        struct parkeduser *pu, *cur;
-       int i,x,parking_range;
-       char exten[AST_MAX_EXTENSION];
+       int i, x = -1, parking_range;
        struct ast_context *con;
+       const char *parkingexten;
        
+       /* Allocate memory for parking data */
        if (!(pu = ast_calloc(1, sizeof(*pu)))) 
                return -1;
 
+       /* Lock parking lot */
        ast_mutex_lock(&parking_lock);
-       parking_range = parking_stop - parking_start+1;
-       for (i = 0; i < parking_range; i++) {
-               x = (i + parking_offset) % parking_range + parking_start;
-               cur = parkinglot;
-               while(cur) {
-                       if (cur->parkingnum == x) 
+       /* Check for channel variable PARKINGEXTEN */
+       parkingexten = pbx_builtin_getvar_helper(chan, "PARKINGEXTEN");
+       if (!ast_strlen_zero(parkingexten)) {
+               if (ast_exists_extension(NULL, parking_con, parkingexten, 1, NULL)) {
+                       ast_log(LOG_WARNING, "Requested parking extension already exists: %s@%s\n", parkingexten, parking_con);
+                       return 0;       /* Continue execution if possible */
+               }
+               ast_copy_string(pu->parkingexten, parkingexten, sizeof(pu->parkingexten));
+       } else {
+               /* Select parking space within range */
+               parking_range = parking_stop - parking_start+1;
+               for (i = 0; i < parking_range; i++) {
+                       x = (i + parking_offset) % parking_range + parking_start;
+                       cur = parkinglot;
+                       while(cur) {
+                               if (cur->parkingnum == x) 
+                                       break;
+                               cur = cur->next;
+                       }
+                       if (!cur)
                                break;
-                       cur = cur->next;
                }
-               if (!cur)
-                       break;
-       }
 
-       if (!(i < parking_range)) {
-               ast_log(LOG_WARNING, "No more parking spaces\n");
-               free(pu);
-               ast_mutex_unlock(&parking_lock);
-               return -1;
+               if (!(i < parking_range)) {
+                       ast_log(LOG_WARNING, "No more parking spaces\n");
+                       free(pu);
+                       ast_mutex_unlock(&parking_lock);
+                       return -1;
+               }
+               /* Set pointer for next parking */
+               if (parkfindnext) 
+                       parking_offset = x - parking_start + 1;
        }
-       if (parkfindnext) 
-               parking_offset = x - parking_start + 1;
+       
        chan->appl = "Parked Call";
        chan->data = NULL; 
 
        pu->chan = chan;
-       /* Start music on hold */
+       
+       /* Start music on hold if we have two different channels */
        if (chan != peer) {
                ast_indicate(pu->chan, AST_CONTROL_HOLD);       /* Indicate to peer that we're on hold */
                ast_moh_start(pu->chan, NULL);
@@ -312,6 +368,7 @@ int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeou
        pu->parkingtime = (timeout > 0) ? timeout : parkingtime;
        if (extout)
                *extout = x;
+
        if (peer) 
                ast_copy_string(pu->peername, peer->name, sizeof(pu->peername));
 
@@ -322,6 +379,7 @@ int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeou
        pu->priority = chan->macropriority ? chan->macropriority : chan->priority;
        pu->next = parkinglot;
        parkinglot = pu;
+
        /* If parking a channel directly, don't quiet yet get parking running on it */
        if (peer == chan) 
                pu->notquiteyet = 1;
@@ -329,23 +387,25 @@ int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeou
        /* Wake up the (presumably select()ing) thread */
        pthread_kill(parking_thread, SIGURG);
        if (option_verbose > 1) 
-               ast_verbose(VERBOSE_PREFIX_2 "Parked %s on %d. Will timeout back to extension [%s] %s, %d in %d seconds\n", pu->chan->name, pu->parkingnum, pu->context, pu->exten, pu->priority, (pu->parkingtime/1000));
+               ast_verbose(VERBOSE_PREFIX_2 "Parked %s on %d@%s. Will timeout back to extension [%s] %s, %d in %d seconds\n", pu->chan->name, pu->parkingnum, parking_con, pu->context, pu->exten, pu->priority, (pu->parkingtime/1000));
 
+       if (pu->parkingnum != -1)
+               snprintf(pu->parkingexten, sizeof(pu->parkingexten), "%d", x);
        manager_event(EVENT_FLAG_CALL, "ParkedCall",
-               "Exten: %d\r\n"
+               "Exten: %s\r\n"
                "Channel: %s\r\n"
                "From: %s\r\n"
                "Timeout: %ld\r\n"
                "CallerID: %s\r\n"
                "CallerIDName: %s\r\n",
-               pu->parkingnum, pu->chan->name, peer ? peer->name : "",
+               pu->parkingexten, pu->chan->name, peer ? peer->name : "",
                (long)pu->start.tv_sec + (long)(pu->parkingtime/1000) - (long)time(NULL),
                S_OR(pu->chan->cid.cid_num, "<unknown>"),
                S_OR(pu->chan->cid.cid_name, "<unknown>")
                );
 
        if (peer && adsipark && adsi_available(peer)) {
-               adsi_announce_park(peer, pu->parkingnum);
+               adsi_announce_park(peer, pu->parkingexten);     /* Only supports parking numbers */
                adsi_unload_session(peer);
        }
 
@@ -355,11 +415,11 @@ int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeou
        if (!con)       /* Still no context? Bad */
                ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con);
        else {          /* Add extension to context */
-               snprintf(exten, sizeof(exten), "%d", x);
-               ast_add_extension2(con, 1, exten, 1, NULL, NULL, parkedcall, strdup(exten), FREE, registrar);
+               if (!ast_add_extension2(con, 1, pu->parkingexten, 1, NULL, NULL, parkedcall, strdup(pu->parkingexten), FREE, registrar))
+                       notify_metermaids(pu->parkingexten, parking_con);
        }
        /* Tell the peer channel the number of the parking space */
-       if (peer) 
+       if (peer && !pu->parkingnum == -1) /* Only say number if it's a number */
                ast_say_digits(peer, pu->parkingnum, "", peer->language);
        if (pu->notquiteyet) {
                /* Wake up parking thread if we're really done */
@@ -412,7 +472,7 @@ int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, int
 #define FEATURE_SENSE_CHAN     (1 << 0)
 #define FEATURE_SENSE_PEER     (1 << 1)
 
-/*
+/*! \brief
  * set caller and callee according to the direction
  */
 static void set_peers(struct ast_channel **caller, struct ast_channel **callee,
@@ -427,6 +487,7 @@ static void set_peers(struct ast_channel **caller, struct ast_channel **callee,
        }
 }
 
+/*! \brief support routing for one touch call parking */
 static int builtin_parkcall(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
 {
        struct ast_channel *parker;
@@ -471,7 +532,7 @@ static int builtin_automonitor(struct ast_channel *chan, struct ast_channel *pee
        }
 
        if (!monitor_app && !(monitor_app = pbx_findapp("Monitor"))) {
-               monitor_ok=0;
+               monitor_ok = 0;
                ast_log(LOG_ERROR,"Cannot record the call. The monitor application is disabled.\n");
                return -1;
        }
@@ -559,6 +620,7 @@ static int finishup(struct ast_channel *chan)
         return res;
 }
 
+/*! \brief Find the context for the transfer */
 static const char *real_ctx(struct ast_channel *transferer, struct ast_channel *transferee)
 {
         const char *s = pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT");
@@ -845,6 +907,7 @@ void ast_unregister_feature(struct ast_call_feature *feature)
        free(feature);
 }
 
+/*! \brief Remove all features in the list */
 static void ast_unregister_features(void)
 {
        struct ast_call_feature *feature;
@@ -1412,14 +1475,15 @@ int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast
        return res;
 }
 
-static void post_manager_event(const char *s, int num, struct ast_channel *chan)
+static void post_manager_event(const char *s, char *parkingexten, struct ast_channel *chan)
 {
        manager_event(EVENT_FLAG_CALL, s,
-               "Exten: %d\r\n"
+               "Exten: %s\r\n"
                "Channel: %s\r\n"
                "CallerID: %s\r\n"
                "CallerIDName: %s\r\n\r\n",
-               num, chan->name,
+               parkingexten, 
+               chan->name,
                S_OR(chan->cid.cid_num, "<unknown>"),
                S_OR(chan->cid.cid_name, "<unknown>")
                );
@@ -1444,7 +1508,7 @@ static void *do_parking_thread(void *ignore)
                pl = NULL;
                pu = parkinglot;
                /* navigate the list with prev-cur pointers to support removals */
-               while(pu) {
+               while (pu) {
                        struct ast_channel *chan = pu->chan;    /* shorthand */
                        int tms;        /* timeout for this item */
                        int x;          /* fd index in channel */
@@ -1469,9 +1533,8 @@ static void *do_parking_thread(void *ignore)
                                        con = ast_context_find(parking_con_dial);
                                        if (!con) {
                                                con = ast_context_create(NULL, parking_con_dial, registrar);
-                                               if (!con) {
+                                               if (!con)
                                                        ast_log(LOG_ERROR, "Parking dial context '%s' does not exist and unable to create\n", parking_con_dial);
-                                               }
                                        }
                                        if (con) {
                                                char returnexten[AST_MAX_EXTENSION];
@@ -1485,7 +1548,7 @@ static void *do_parking_thread(void *ignore)
                                        set_c_e_p(chan, pu->context, pu->exten, pu->priority);
                                }
 
-                               post_manager_event("ParkedCallTimeOut", pu->parkingnum, chan);
+                               post_manager_event("ParkedCallTimeOut", pu->parkingexten, chan);
 
                                if (option_verbose > 1) 
                                        ast_verbose(VERBOSE_PREFIX_2 "Timeout for %s parked on %d. Returning to %s,%s,%d\n", chan->name, pu->parkingnum, chan->context, chan->exten, chan->priority);
@@ -1503,10 +1566,10 @@ static void *do_parking_thread(void *ignore)
                                pu = pu->next;
                                con = ast_context_find(parking_con);
                                if (con) {
-                                       char exten[AST_MAX_EXTENSION];
-                                       snprintf(exten, sizeof(exten), "%d", pt->parkingnum);
-                                       if (ast_context_remove_extension2(con, exten, 1, NULL))
+                                       if (ast_context_remove_extension2(con, pt->parkingexten, 1, NULL))
                                                ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
+                                       else
+                                               notify_metermaids(pu->parkingexten, parking_con);
                                } else
                                        ast_log(LOG_WARNING, "Whoa, no parking context?\n");
                                free(pt);
@@ -1522,12 +1585,13 @@ static void *do_parking_thread(void *ignore)
                                        else
                                                ast_clear_flag(chan, AST_FLAG_EXCEPTION);
                                        chan->fdno = x;
+
                                        /* See if they need servicing */
                                        f = ast_read(chan);
                                        if (!f || (f->frametype == AST_FRAME_CONTROL && f->subclass ==  AST_CONTROL_HANGUP)) {
                                                if (f)
                                                        ast_frfree(f);
-                                               post_manager_event("ParkedCallGiveUp", pu->parkingnum, chan);
+                                               post_manager_event("ParkedCallGiveUp", pu->parkingexten, chan);
 
                                                /* There's a problem, hang them up*/
                                                if (option_verbose > 1) 
@@ -1542,10 +1606,10 @@ static void *do_parking_thread(void *ignore)
                                                pu = pu->next;
                                                con = ast_context_find(parking_con);
                                                if (con) {
-                                                       char exten[AST_MAX_EXTENSION];
-                                                       snprintf(exten, sizeof(exten), "%d", pt->parkingnum);
-                                                       if (ast_context_remove_extension2(con, exten, 1, NULL))
+                                                       if (ast_context_remove_extension2(con, pt->parkingexten, 1, NULL))
                                                                ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
+                                               else
+                                                       notify_metermaids(pt->parkingexten, parking_con);
                                                } else
                                                        ast_log(LOG_WARNING, "Whoa, no parking context?\n");
                                                free(pt);
@@ -1593,6 +1657,7 @@ std:                                      for (x=0; x<AST_MAX_FDS; x++) { /* mark fds for next round */
        return NULL;    /* Never reached */
 }
 
+/*! \brief Park a call */
 static int park_call_exec(struct ast_channel *chan, void *data)
 {
        /* Data is unused at the moment but could contain a parking
@@ -1604,10 +1669,13 @@ static int park_call_exec(struct ast_channel *chan, void *data)
           where this call should return */
        strcpy(chan->exten, "s");
        chan->priority = 1;
+       /* Answer if call is not up */
        if (chan->_state != AST_STATE_UP)
                res = ast_answer(chan);
+       /* Sleep to allow VoIP streams to settle down */
        if (!res)
                res = ast_safe_sleep(chan, 1000);
+       /* Park the call */
        if (!res)
                res = ast_park_call(chan, chan, 0, NULL);
        LOCAL_USER_REMOVE(u);
@@ -1616,6 +1684,7 @@ static int park_call_exec(struct ast_channel *chan, void *data)
        return res;
 }
 
+/*! \brief Pickup parked call */
 static int park_exec(struct ast_channel *chan, void *data)
 {
        int res=0;
@@ -1627,7 +1696,7 @@ static int park_exec(struct ast_channel *chan, void *data)
        struct ast_bridge_config config;
 
        if (!data) {
-               ast_log(LOG_WARNING, "Park requires an argument (extension number)\n");
+               ast_log(LOG_WARNING, "Parkedcall requires an argument (extension number)\n");
                return -1;
        }
        LOCAL_USER_ADD(u);
@@ -1650,20 +1719,20 @@ static int park_exec(struct ast_channel *chan, void *data)
                peer = pu->chan;
                con = ast_context_find(parking_con);
                if (con) {
-                       char exten[AST_MAX_EXTENSION];
-                       snprintf(exten, sizeof(exten), "%d", pu->parkingnum);
-                       if (ast_context_remove_extension2(con, exten, 1, NULL))
+                       if (ast_context_remove_extension2(con, pu->parkingexten, 1, NULL))
                                ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
+                       else
+                               notify_metermaids(pu->parkingexten, parking_con);
                } else
                        ast_log(LOG_WARNING, "Whoa, no parking context?\n");
 
                manager_event(EVENT_FLAG_CALL, "UnParkedCall",
-                       "Exten: %d\r\n"
+                       "Exten: %s\r\n"
                        "Channel: %s\r\n"
                        "From: %s\r\n"
                        "CallerID: %s\r\n"
                        "CallerIDName: %s\r\n",
-                       pu->parkingnum, pu->chan->name, chan->name,
+                       pu->parkingexten, pu->chan->name, chan->name,
                        S_OR(pu->chan->cid.cid_num, "<unknown>"),
                        S_OR(pu->chan->cid.cid_name, "<unknown>")
                        );
@@ -1801,8 +1870,8 @@ static int handle_parkedcalls(int fd, int argc, char *argv[])
        ast_mutex_lock(&parking_lock);
 
        for (cur = parkinglot; cur; cur = cur->next) {
-               ast_cli(fd, "%4d %25s (%-15s %-12s %-4d) %6lds\n"
-                       ,cur->parkingnum, cur->chan->name, cur->context, cur->exten
+               ast_cli(fd, "%-10.10s %25s (%-15s %-12s %-4d) %6lds\n"
+                       ,cur->parkingexten, cur->chan->name, cur->context, cur->exten
                        ,cur->priority, cur->start.tv_sec + (cur->parkingtime/1000) - time(NULL));
 
                numparked++;
@@ -1829,7 +1898,7 @@ static int manager_parking_status( struct mansession *s, struct message *m )
        char idText[256] = "";
 
        if (!ast_strlen_zero(id))
-               snprintf(idText,256,"ActionID: %s\r\n",id);
+               snprintf(idText, 256, "ActionID: %s\r\n", id);
 
        astman_send_ack(s, m, "Parked calls will follow");
 
@@ -1959,9 +2028,25 @@ int ast_pickup_call(struct ast_channel *chan)
        return res;
 }
 
+/*! \brief Add parking hints for all defined parking lots */
+static void park_add_hints(char *context, int start, int stop)
+{
+       int numext;
+       char device[AST_MAX_EXTENSION];
+       char exten[10];
+
+       for (numext = start; numext <= stop; numext++) {
+               snprintf(exten, sizeof(exten), "%d", numext);
+               snprintf(device, sizeof(device), "park:%s@%s", exten, context);
+               ast_add_extension(context, 1, exten, PRIORITY_HINT, NULL, NULL, device, NULL, NULL, registrar);
+       }
+}
+
+
 static int load_config(void) 
 {
        int start = 0, end = 0;
+       int res;
        struct ast_context *con = NULL;
        struct ast_config *cfg = NULL;
        struct ast_variable *var = NULL;
@@ -1985,6 +2070,7 @@ static int load_config(void)
        parking_stop = 750;
        parkfindnext = 0;
        adsipark = 0;
+       parkaddhints = 0;
 
        transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
        featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
@@ -2012,6 +2098,8 @@ static int load_config(void)
                                }
                        } else if (!strcasecmp(var->name, "findslot")) {
                                parkfindnext = (!strcasecmp(var->value, "next"));
+                       } else if (!strcasecmp(var->name, "parkinghints")) {
+                               parkaddhints = ast_true(var->value);
                        } else if (!strcasecmp(var->name, "adsipark")) {
                                adsipark = ast_true(var->value);
                        } else if (!strcasecmp(var->name, "transferdigittimeout")) {
@@ -2131,7 +2219,8 @@ static int load_config(void)
 
        /* Remove the old parking extension */
        if (!ast_strlen_zero(old_parking_con) && (con = ast_context_find(old_parking_con)))     {
-               ast_context_remove_extension2(con, old_parking_ext, 1, registrar);
+               if(ast_context_remove_extension2(con, old_parking_ext, 1, registrar))
+                               notify_metermaids(old_parking_ext, old_parking_con);
                if (option_debug)
                        ast_log(LOG_DEBUG, "Removed old parking extension %s@%s\n", old_parking_ext, old_parking_con);
        }
@@ -2140,7 +2229,13 @@ static int load_config(void)
                ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con);
                return -1;
        }
-       return ast_add_extension2(con, 1, ast_parking_ext(), 1, NULL, NULL, parkcall, strdup(""), FREE, registrar);
+       res = ast_add_extension2(con, 1, ast_parking_ext(), 1, NULL, NULL, parkcall, strdup(""), FREE, registrar);
+       if (parkaddhints)
+               park_add_hints(parking_con, parking_start, parking_stop);
+       if (!res)
+               notify_metermaids(ast_parking_ext(), parking_con);
+       return res;
+
 }
 
 static int reload(void *mod)
@@ -2169,6 +2264,9 @@ static int load_module(void *mod)
                ast_manager_register2("Park", EVENT_FLAG_CALL, manager_park,
                        "Park a channel", mandescr_park); 
        }
+
+       res |= ast_devstate_prov_add("Park", metermaidstate);
+
        return res;
 }
 
@@ -2182,6 +2280,7 @@ static int unload_module(void *mod)
        ast_cli_unregister(&showfeatures);
        ast_cli_unregister(&showparked);
        ast_unregister_application(parkcall);
+       ast_devstate_prov_del("Park");
        return ast_unregister_application(parkedcall);
 }