· KLDP.org · KLDP.net · KLDP Wiki · KLDP BBS ·
Asterisk Source/App Queue

queue_exec


/*!\brief The starting point for all queue calls
 *
 * The process involved here is to 
 * 1. Parse the options specified in the call to Queue()
 * 2. Join the queue
 * 3. Wait in a loop until it is our turn to try calling a queue member
 * 4. Attempt to call a queue member
 * 5. If 4. did not result in a bridged call, then check for between
 *    call options such as periodic announcements etc.
 * 6. Try 4 again uless some condition (such as an expiration time) causes us to 
 *    exit the queue.
 */

* static int queue_exec(struct ast_channel *chan, void *data)
  • Parse options and set qe.expire, ringing
  • Get variable and set prio, max_penalty
  • set queue_ent structure qe
  • if (!join_queue(args.queuename, &qe, &reason)) {
    • int makeannouncement = 0;
    • check_turns:
    • res = wait_our_turn(&qe, ringing, &reason);
    • if (res)
      • goto stop;
    • for (;;) {
      • IFabandoned(QUEUE_TIMEOUT)
      • if (makeannouncement) {
        • if (qe.parent->announcefrequency && !ringing)
          • if ((res = say_position(&qe)))
            • goto stop;
      • }
      • makeannouncement = 1;
      • IFabandoned(QUEUE_TIMEOUT)
      • if (qe.parent->periodicannouncefrequency && !ringing)
        • if ((res = say_periodic_announcement(&qe)))
          • goto stop;
      • IFabandoned(QUEUE_TIMEOUT)
      • res = try_calling(&qe, args.options, args.announceoverride, args.url, &tries, &noption, args.agi);
      • if (res)
        • goto stop;
      • stat = get_member_status(qe.parent, qe.max_penalty);
      • if (noption && tries >= qe.parent->membercount) {
        • abandoned(QUEUE_TIMEOUT)
      • }
      • if (qe.parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) {
        • abandoned(QUEUE_LEAVEEMPTY);
      • }
      • if ((qe.parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) {
        • abandoned(QUEUE_LEAVEUNAVAIL);
      • }
      • IFabandoned(QUEUE_TIMEOUT)
      • update_realtime_members(qe.parent);
      • res = wait_a_bit(&qe);
      • if (res)
        • goto stop;
      • if (!is_our_turn(&qe)) {
        • goto check_turns;
      • }
    • }
    • stop:
    • if (res) {
      • if (res < 0) {
        • if (!qe.handled) {
          • record_abandoned(&qe);
        • }
        • res = -1;
      • } else if (qe.valid_digits) {
      • }
    • }
    • if (res >= 0) {
      • res = 0;
      • ast_stopstream(chan);
    • }
    • leave_queue(&qe);
    • if (reason != QUEUE_UNKNOWN)
      • set_queue_result(chan, reason);
  • } else {
  • }
  • return res;

* QUEUESTATUS
  • TIMEOUT, FULL, JOINEMPTY, LEAVEEMPTY, JOINUNAVAIL, LEAVEUNAVAIL

* abandoned(QUEUESTATUS)
  • record_abandoned(&qe);
  • reason = QUEUESTATUS;
  • res = 0;
  • break;

* IFabandoned(QUEUE_TIMEOUT)
  • if (qe.expire && (time(NULL) >= qe.expire)) {
    • abandoned(QUEUE_TIMEOUT)
  • }

queue_exec 1


* queue_exec
  • queue 에 join 을 못한 경우
        AST_DECLARE_APP_ARGS(args,
                AST_APP_ARG(queuename);
                AST_APP_ARG(options);
                AST_APP_ARG(url);
                AST_APP_ARG(announceoverride);
                AST_APP_ARG(queuetimeoutstr);
                AST_APP_ARG(agi);
        );
  • if (ast_strlen_zero(data)) {
    • return -1;
  • }
  • parse = ast_strdupa(data);
  • AST_STANDARD_APP_ARGS(args, parse);
  • qe.start = time(NULL);
  • if (!ast_strlen_zero(args.queuetimeoutstr))
    • qe.expire = qe.start + atoi(args.queuetimeoutstr);
  • else
    • qe.expire = 0;
  • user_priority = pbx_builtin_getvar_helper(chan, "QUEUE_PRIO");
  • if (user_priority) {
    • if (sscanf(user_priority, "%d", &prio) == 1) {
    • } else {
      • prio = 0;
    • }
  • } else {
    • prio = 0;
  • }
  • if ((max_penalty_str = pbx_builtin_getvar_helper(chan, "QUEUE_MAX_PENALTY"))) {
    • if (sscanf(max_penalty_str, "%d", &max_penalty) == 1) {
    • } else {
      • max_penalty = 0;
    • }
  • } else {
    • max_penalty = 0;
  • }
  • if (args.options && (strchr(args.options, 'r')))
    • ringing = 1;
        qe.chan = chan;
        qe.prio = prio;
        qe.max_penalty = max_penalty;
        qe.last_pos_said = 0;
        qe.last_pos = 0;
        qe.last_periodic_announce_time = time(NULL);
        qe.last_periodic_announce_sound = 0;
        qe.valid_digits = 0;
  • if (!join_queue(args.queuename, &qe, &reason)) {
  • } else {
    • set_queue_result(chan, reason);
    • res = 0;
  • }
  • return res;

* join_queue return -1
  • int res = -1;
  • if (!(q = load_realtime_queue(queuename)))
    • return res;
  • stat = get_member_status(q, qe->max_penalty);
  • if (!q->joinempty && (stat == QUEUE_NO_MEMBERS))
    • *reason = QUEUE_JOINEMPTY;
  • else if ((q->joinempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_MEMBERS))
    • *reason = QUEUE_JOINUNAVAIL;
  • else if (q->maxlen && (q->count >= q->maxlen))
    • *reason = QUEUE_FULL;
  • else {
  • }
  • return res;

  • load_realtime_queue(queuename) error;
  • stat = get_member_status(q, qe->max_penalty);
  • (!q->joinempty && (stat == QUEUE_NO_MEMBERS))
    • joinempty=true, yes, t, y, on, 1 , strict 면, QUEUE_NO_MEMBERS 인 경우 join 못함
  • ((q->joinempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_MEMBERS))
    • joinempty 가 strict 면 QUEUE_NO_REACHABLE_MEMBERS 일때도 join 못함. ( || 는 필요 없는 듯함)
  • (q->maxlen && (q->count >= q->maxlen))
    • queue 가 다 찬 경우

* join_queue return 0
  • queue_ent list 에 추가

queue_exec 2


* queue_exec
  • queue 에 join 하고, wait_our_turn 에서 0 이 아닌 값을 return 한 경우
  • ....
  • if (!join_queue(args.queuename, &qe, &reason)) {
    • int makeannouncement = 0;
    • res = wait_our_turn(&qe, ringing, &reason);
    • if (res)
      • goto stop;
    • for (;;) {
    • }
    • stop:
    • if (res) {
      • if (res < 0) {
        • if (!qe.handled) {
          • record_abandoned(&qe);
          • wait_our_turn 수행중 ast_waitfordigit 에서 -1 을 return 한 경우
        • }
        • res = -1;
      • } else if (qe.valid_digits) {
        • wait_our_turn 수행중 say_position, say_periodic_announcement, ast_waitfordigit 에서 the ascii value of the digit 을 받은 경우
      • }
    • }
    • if (res >= 0) {
      • wait_our_turn 수행중 say_position, say_periodic_announcement, ast_waitfordigit 에서 the ascii value of the digit 을 받은 경우
      • res = 0;
      • ast_stopstream(chan);
    • }
    • leave_queue(&qe);
    • if (reason != QUEUE_UNKNOWN)
      • set_queue_result(chan, reason);
  • } else {
  • }
  • return res;

* wait_our_turn 이 0 을 return 하는 경우
  • our_turn 인 경우
  • QUEUE_TIMEOUT
  • QUEUE_LEAVEEMPTY, QUEUE_LEAVEUNAVAIL
    • leavequeue(qe) 를 함.

* wait_our_turn 이 0 이 아닌 값을 return 하는 경우
  • if (qe->parent->announcefrequency && !ringing && (res = say_position(qe)))
    • the ascii value of the digit
  • if (qe->parent->periodicannouncefrequency && !ringing && (res = say_periodic_announcement(qe)))
    • the ascii value of the digit
  • if ((res = ast_waitfordigit(qe->chan, RECHECK * 1000))) {
    • if (res > 0 && !valid_exit(qe, res))
    • else
      • the ascii value of the digit or -1
    • }

queue_exec 3


* queue_exec
  • queue 에 join 하고, wait_our_turn 에서 0 을 return
  • ....
  • if (!join_queue(args.queuename, &qe, &reason)) {
    • int makeannouncement = 0;
    • check_turns:
    • res = wait_our_turn(&qe, ringing, &reason);
    • if (res)
      • goto stop;
    • for (;;) {
    • wait_our_turn 에서 our_turn 인 경우
    • wait_our_turn 중 QUEUE_TIMEOUT 인 경우
    • wait_our_turn 중 QUEUE_LEAVEEMPTY, QUEUE_LEAVEUNAVAIL 인 경우: leavequeue(qe) 를 하였음.
      • IFabandoned(QUEUE_TIMEOUT)
      • if (makeannouncement) {
        • if (qe.parent->announcefrequency && !ringing)
          • if ((res = say_position(&qe)))
            • goto stop;
      • }
      • makeannouncement = 1;
      • IFabandoned(QUEUE_TIMEOUT)
      • if (qe.parent->periodicannouncefrequency && !ringing)
        • if ((res = say_periodic_announcement(&qe)))
          • goto stop;
      • IFabandoned(QUEUE_TIMEOUT)
      • res = try_calling(&qe, args.options, args.announceoverride, args.url, &tries, &noption, args.agi);
      • if (res)
        • goto stop;
      • stat = get_member_status(qe.parent, qe.max_penalty);
      • if (noption && tries >= qe.parent->membercount) {
        • abandoned(QUEUE_TIMEOUT)
      • }
      • if (qe.parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) {
        • abandoned(QUEUE_LEAVEEMPTY);
      • }
      • if ((qe.parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) {
        • abandoned(QUEUE_LEAVEUNAVAIL);
      • }
      • IFabandoned(QUEUE_TIMEOUT)
      • update_realtime_members(qe.parent);
      • res = wait_a_bit(&qe);
      • if (res)
        • goto stop;
      • if (!is_our_turn(&qe)) {
        • our_turn 이 아직 안 되었으면
        • goto check_turns;
      • }
      • our turn 이면 for loop
    • }
    • stop:
    • if (res) {
      • if (res < 0) {
        • wait_a_bit 에서 오류가 발생한 경우
        • if (!qe.handled) {
          • record_abandoned(&qe);
        • }
        • res = -1;
      • } else if (qe.valid_digits) {
        • for loop 중 say_position, say_periodic_announcement 에서 the ascii value of the digit 을 받은 경우
        • for loop 중 wait_a_bit 에서 the ascii value of the digit 을 받은 경우
      • }
    • }
    • if (res >= 0) {
      • res 가 0 인 경우
        • IFabandoned(QUEUE_TIMEOUT), abandoned(QUEUE_TIMEOUT), abandoned(QUEUE_LEAVEEMPTY), abandoned(QUEUE_LEAVEUNAVAIL)
      • for loop 중 say_position, say_periodic_announcement 에서 the ascii value of the digit 을 받은 경우
      • for loop 중 wait_a_bit 에서 the ascii value of the digit 을 받은 경우
      • res = 0;
      • ast_stopstream(chan);
    • }
    • leave_queue(&qe);
    • if (reason != QUEUE_UNKNOWN)
      • set_queue_result(chan, reason);
  • } else {
  • }
  • return res;

* static int wait_a_bit(struct queue_ent *qe)
  • int retrywait = qe->parent->retry * 1000;
  • int res = ast_waitfordigit(qe->chan, retrywait);
  • if (res > 0 && !valid_exit(qe, res))
    • valid 하지 않은 key 가 press 된 경우
    • res = 0;
  • return res;
  • retry 시간이 입력없이 지난 경우: 0
  • valid key 가 입력된 경우: the ascii value of the digit
  • 오류가 발생한 경우: -1

queue_exec 4


* queue_exec
  • try_calling 수행: return 0 과 return nonezero 인 경우
  • queue 에 join 하고, wait_for_answer 에서 0 을 return
  • ....
  • if (!join_queue(args.queuename, &qe, &reason)) {
    • int makeannouncement = 0;
    • check_turns:
    • res = wait_our_turn(&qe, ringing, &reason);
    • if (res)
      • goto stop;
    • for (;;) {
      • IFabandoned(QUEUE_TIMEOUT)
      • if (makeannouncement) {
        • if (qe.parent->announcefrequency && !ringing)
          • if ((res = say_position(&qe)))
            • goto stop;
      • }
      • makeannouncement = 1;
      • IFabandoned(QUEUE_TIMEOUT)
      • if (qe.parent->periodicannouncefrequency && !ringing)
        • if ((res = say_periodic_announcement(&qe)))
          • goto stop;
      • IFabandoned(QUEUE_TIMEOUT)
      • res = try_calling(&qe, args.options, args.announceoverride, args.url, &tries, &noption, args.agi);
      • if (res)
        • goto stop;
      • stat = get_member_status(qe.parent, qe.max_penalty);
      • if (noption && tries >= qe.parent->membercount) {
        • abandoned(QUEUE_TIMEOUT)
      • }
      • if (qe.parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) {
        • abandoned(QUEUE_LEAVEEMPTY);
      • }
      • if ((qe.parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) {
        • abandoned(QUEUE_LEAVEUNAVAIL);
      • }
      • IFabandoned(QUEUE_TIMEOUT)
      • update_realtime_members(qe.parent);
      • res = wait_a_bit(&qe);
      • if (res)
        • goto stop;
      • if (!is_our_turn(&qe)) {
        • goto check_turns;
      • }
    • }
    • stop:
    • if (res) {
      • if (res < 0) {
        • if (!qe.handled) {
          • record_abandoned(&qe);
        • }
        • res = -1;
      • } else if (qe.valid_digits) {
      • }
    • }
    • if (res >= 0) {
      • res = 0;
      • ast_stopstream(chan);
    • }
    • leave_queue(&qe);
    • if (reason != QUEUE_UNKNOWN)
      • set_queue_result(chan, reason);
  • } else {
  • }
  • return res;

queue CDR


* mysql 을 사용하기 위하여
  • cdr 의 방식을 따르는 경우
    • 확장성도 좋고, 디자인 측면에서도 좋다.
    • queue 모듈에 대한 dependency 를 점검할 방법이 있는가?
    • 실제로 손을 보아야 할 것이 많으므로, 오류가 발생할 확률이 높다.
  • logger.c 를 수정하는 방법
    • 간단한 방법이기는 하나, 확장성이 없음
    • 디자인 측면에서 문제가 있다.
    • ast_queue_log 사용

* cdr 의 방법을 따를 경우
  • main 디렉토리 밑의 cdr.c 의 ast_cdr_register 를 어디에 넣을 것인가?
    • apps/app_queue.c
    • main/logger.c
    • 별도의 파일을 만든다

* cdr.c 와 cdr.h 에 필요한 데이타와 함수 설정
  • 이름은 qcdr 로 함.
  • queue 별로 db table 을 설정하는 것이 가능한가?
  • queue.conf 에 qcdr 사용
  • qe.parent->qcdr 로 확인
  • queue 에 enter 한 이후에 사용가능

* qcdr structure
status
qe.start
join_queue
  queue_exec 중 join_queue 바로 밑에서 설정 
wait_our_turn
  wait_our_turn 바로 앞에서 setting
is_our_turn
  res=1 인 경우에 setting
try_calling
  try_calling 의 time(&now) 사용
start_call
really_call (여러개임)
wait_for_answer
  wait_for_answer 의 starttime 사용 
callcompleted
leave_queue
connected
  callstart talk 시작
talk end
  TRANSFER
  COMPLETECALLER
  COMPLETEAGENT

* AgentCalled, AgentConnect, AgentComplete

agent 와 통화가 되고 정상종료인 경우

* case 1:
  • int ringing=0;
  • option 에 r 이 있으면
    • ringing = 1;
  • join_queue 가 0 을 return
    • int makeannouncement = 0;
    • wait_our_turn 이 0 을 return
    • for (;;) {
      • expire 가 설정되어 있지 않거나, 아직 expire time 이 되지 않은 경우
      • makeannouncement 가 설정되어 있으면,
        • announcefrequency 가 설정되어 있고, ringing 이 0 이면,
          • say_position(&qe) 이 0 을 return
      • makeannouncement = 1;
      • expire 가 설정되어 있지 않거나, 아직 expire time 이 되지 않은 경우
      • periodicannouncefrequency 가 설정되어 있고, ringing 이 0 이면,
        • say_periodic_announcement(&qe) 이 0 을 return
      • try_calling 이 0 보다 큰 값을 return

* case 2:
  • int ringing=0;
  • option 에 r 이 있으면
    • ringing = 1;
  • join_queue 가 0 을 return
    • int makeannouncement = 0;
    • check_turns:
    • wait_our_turn 이 0 을 return
    • for (;;) {
      • expire 가 설정되어 있지 않거나, 아직 expire time 이 되지 않은 경우
      • makeannouncement 가 설정되어 있으면,
        • announcefrequency 가 설정되어 있고, ringing 이 0 이면,
          • say_position(&qe) 이 0 을 return
      • makeannouncement = 1;
      • expire 가 설정되어 있지 않거나, 아직 expire time 이 되지 않은 경우
      • periodicannouncefrequency 가 설정되어 있고, ringing 이 0 이면,
        • say_periodic_announcement(&qe) 이 0 을 return
      • try_calling 이 0 을 return
      • noption 이 설정되어 있지 않거나, tries 가 membercount 보다 작은 경우
      • leavewhenempty 설정되어 있지 않거나, stat 가 QUEUE_NO_MEMBERS 가 아닌 경우
      • leavewhenempty 가 QUEUE_EMPTY_STRICT 가 아니거나, stat 이 QUEUE_NO_REACHABLE_MEMBERS 이 아닌 경우
      • expire 가 설정되어 있지 않거나, 아직 expire time 이 되지 않은 경우
      • wait_a_bit(&qe) 이 0 을 return
      • is_our_turn(&qe) 이 0 이 아닌 값을 return
        • for loop
      • is_our_turn(&qe) 이 0 을 return
        • check_turns 부터 다시 시작

* join_queue 가 0 을 return 하는 경우
  • load_realtime_queue(queuename) 이 0 이 아닌 값을 return
  • joinempty 가 0 이 아니거나, stat 이 QUEUE_NO_MEMBERS 가 아니고,
    • joinempty 가 QUEUE_EMPTY_STRICT 가 아니거나, stat 가 QUEUE_NO_REACHABLE_MEMBERS 도 아니고, QUEUE_NO_MEMBERS 도 아니고
      • maxlen 가 0 이거나, count 가 maxlen 보다 작으면

* try_calling 이 0 이 아닌 값을 return
  • expire 가 설정되어 있지 않거나, 아직 expire time 이 되지 않은 경우
  • outgoing 생성
  • ring_one(qe, outgoing, &numbusies);
  • wait_for_answer 가 peer 를 return
  • ast_channel_make_compatible 이 0 이나 0 보다 큰 값을 return
  • bridge = ast_bridge_call(qe->chan,peer, &bridge_config) 수행

통계관련


* asterisk 와 통화가 안되는 경우

* asterisk 와 통화가 된 경우
  • queue 에 못 들어간 경우
  • queue 에 들어간 경우
    • wait_our_turn 이 0이 아닌 값을 retun 한 경우
    • wait_our_turn 이 0을 return 한 경우
      • for loop
        • try_calling 전
          • break 하는 경우
            • time out
          • goto stop 인 경우
            • say_position 이 0 이 아닌 값을 return 한 경우
            • say_periodic_announcement 가 0 이 아닌 값을 return 한 경우
        • try_calling
        • try_calling 후
          • goto stop 인 경우
            • try_calling 이 0 이 아닌 값을 return 한 경우
          • break 하는 경우
            • noption && tries >= qe.parent->membercount
            • 가능한 member 가 없는 경우
            • time out
        • wait_a_bit
          • goto stop 인 경우
            • wait_a_bit 이 0 이 아닌 값을 return 한 경우
          • goto check_turns 인 경우
            • is_our_turn 이 0 을 return 한 경우

* queue 에 못 들어간 경우
  • 이유
    • realtime queue 를 load 하지 못한 경우에
      • 가능한 member 가 없는 경우
      • queue 가 full 인 경우
  • 결과
    • log 를 남긴다
    • dialplan 의 variable 설정
    • Queue 정상종료

* wait_our_turn 이 0을 return 한 경우
  • 이유
    • autofill 이고 첫번째이거나, 현재 순서까지 할당할 member 수가 충분한 경우
    • time out
    • 할당할 member 가 없는 경우 (leave_queue )
  • 결과
    • 다음 프로그램 수행

* wait_our_turn 이 0이 아닌 값을 retun 한 경우
  • 이유
    • say_position 이 0 이 아닌 값을 return 한 경우
    • say_periodic_announcement 이 0 이 아닌 값을 return 한 경우
    • ast_waitfordigit 가 0 이 아닌 값을 return 하고, return 값이 음수거나 valid_exit 이 0 이 아닌 값을 return 한 경우
  • 결과
    • res 가 음수면 log ABANDON
    • res 가 양수고 valid_digits 면 log EXITWITHKEY
    • res 가 양수면 res 를 0 으로 설정
    • leave_queue(&qe)
    • QUEUE_UNKNOWN 이 아니면 dialplan 의 variable 설정

* try_calling 을 수행하는 경우
  • join_queue return 0
  • wait_our_turn return 0
  • for loop
    • not time out
    • from second loop: say_position(&qe) return 0
    • say_periodic_announcement(&qe) return 0
    • try_calling return 0
    • not noption && tries >= qe.parent->membercount
    • member available
    • wait_a_bit return 0
    • is_out_turn return non zero

* try_calling
  • outgoing 은 callattempt 의 list 임
* outgoing 을 만드는 과정까지
  • try_calling 이 0 이 아닌 값을 return 하는 경우
    • 없음.
  • try_calling 이 0 을 return 하는 경우
    • time out
    • callattempt 를 위한 memory alloc 을 실패한 경우
    • channel 에 같은 type 의 datastore 가 없고
      • datastore 를 위한 memory alloc 을 실패한 경우
      • dialed_interfaces 를 위한 memory alloc 을 실패한 경우
    • member 의 interface 가 Local 이 아니고
      • di 를 위한 memory alloc 을 실패한 경우
* outgoing 을 만든후
  • wait_for_answer 의 return 값이 0 인 경우
    • try_calling 이 0 이 아닌 값을 return 하는 경우
      • to 가 0 이 아니면 return -1
      • to 가 0 이면 return digit
    • try_calling 이 0 을 return 하는 경우
      • 없음
  • wait_for_answer 의 return 값이 0 이 아닌 경우
    • announce, reportholdtime, memberdelay 중 하나라도 0 이 아닌 경우
    • ast_channel_make_compatible 이 음수를 return 한 경우
    • monfmt 가 0 이 아닌 경우
    • leave_queue(qe);
    • bridge = ast_bridge_call(qe->chan,peer, &bridge_config);
    • attended_transfer_occurred(qe->chan) 이 0 을 return 한 경우
    • res = bridge ? bridge : 1;

* announce, reportholdtime, memberdelay 중 하나라도 0 이 아닌 경우
  • try_calling 이 0 이 아닌 값을 return 하는 경우
    • peer->_softhangup 이 0 이고 res2 가 0 이 아니면 return -1
  • try_calling 이 0 을 return 하는 경우
    • peer->_softhangup 이 0 이 아닌 경우

* ast_channel_make_compatible 이 음수를 return 한 경우
  • ry_calling 이 0 이 아닌 값을 return 하는 경우
    • return -1
  • try_calling 이 0 을 return 하는 경우
    • 없음.

* monfmt 가 0 이 아닌 경우
  • try_calling 이 0 이 아닌 값을 return 하는 경우
    • 없음
  • try_calling 이 0 을 return 하는 경우
    • 없음.

* attended_transfer_occurred(qe->chan) 이 0 을 return 한 경우
  • try_calling 이 0 이 아닌 값을 return 하는 경우
    • 없음
  • try_calling 이 0 을 return 하는 경우
    • 없음.

time 관련

* static int say_position(struct queue_ent *qe)
  • time(&now);
* static int say_periodic_announcement(struct queue_ent *qe)
  • time(&now);
* static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callattempt *outgoing, int *to, char *digit, int prebusies, int caller_disconnect, int forwardsallowed)
  • starttime = (long) time(NULL);
  • endtime = (long)time(NULL);
* static int update_queue(struct call_queue *q, struct member *member, int callcompletedinsl)
  • time(&member->lastcall);
* static int try_calling(struct queue_ent *qe, const char *options, char *announceoverride, const char *url, int *tries, int *noption, const char *agi)
  • time_t now = time(NULL);
  • time(&now);
  • time(&callstart);
* static int queue_exec(struct ast_channel *chan, void *data)
  • qe.start = time(NULL);
  • qe.last_periodic_announce_time = time(NULL);
* static int __queues_show(struct mansession *s, int manager, int fd, int argc, char **argv)
  • time(&now);
* static int manager_queues_status(struct mansession *s, const struct message *m)
  • time(&now);

* ABANDON(position|origposition|waittime)
  • The caller abandoned
  • try_calling
    • wait_for_answer 이 0 이 아닌 값을 return 한 경우
      • if (announce || qe->parent->reportholdtime || qe->parent->memberdelay) {
        • peer->_softhangup 이 0 이고 res2 가 0 이 아니면
          • ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "ABANDON", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start);
      • }

  • queue_exec
    • if (!join_queue(args.queuename, &qe, &reason)) {
      • for (;;) {
      • stop:
        • if (res) {
          • if (res < 0) {
            • if (!qe.handled) {
              • ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ABANDON", "%d|%d|%ld", qe.pos, qe.opos, (long) time(NULL) - qe.start);
            • }
          • } else if (qe.valid_digits) {
          • }
        • }
      • }
    • }

* AGENTLOGOFF(channel|logintime)

* AGENTCALLBACKLOGOFF(exten@context|logintime|reason)

* COMPLETEAGENT(holdtime|calltime|origposition)
  • The caller was connected to an agent, and the call was terminated normally by the agent.

  • try_calling
    • wait_for_answer 의 return 값이 0 이 아닌 경우
      • announce, reportholdtime, memberdelay 중 하나라도 0 이 아닌 경우
      • ast_channel_make_compatible 이 음수를 return 한 경우
      • monfmt 가 0 이 아닌 경우
      • leave_queue(qe);
      • bridge = ast_bridge_call(qe->chan,peer, &bridge_config);
      • attended_transfer_occurred(qe->chan) 이 0 을 return 한 경우
        • if (strcasecmp(oldcontext, qe->chan->context) || strcasecmp(oldexten, qe->chan->exten)) {
        • } else if (qe->chan->_softhangup) {
        • } else {
          • ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETEAGENT", "%ld|%ld|%d", (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos);
        • }

* COMPLETECALLER(holdtime|calltime|origposition)
  • The caller was connected to an agent, and the call was terminated normally by the caller.

  • try_calling
    • wait_for_answer 의 return 값이 0 이 아닌 경우
      • announce, reportholdtime, memberdelay 중 하나라도 0 이 아닌 경우
      • ast_channel_make_compatible 이 음수를 return 한 경우
      • monfmt 가 0 이 아닌 경우
      • leave_queue(qe);
      • bridge = ast_bridge_call(qe->chan,peer, &bridge_config);
      • attended_transfer_occurred(qe->chan) 이 0 을 return 한 경우
        • if (strcasecmp(oldcontext, qe->chan->context) || strcasecmp(oldexten, qe->chan->exten)) {
        • } else if (qe->chan->_softhangup) {
          • ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETECALLER", "%ld|%ld|%d", (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos);
        • } else {
        • }

* CONNECT(holdtime|bridgedchanneluniqueid)
  • The caller was connected to an agent.

  • try_calling
    • wait_for_answer 의 return 값이 0 이 아닌 경우
      • announce, reportholdtime, memberdelay 중 하나라도 0 이 아닌 경우
      • ast_channel_make_compatible 이 음수를 return 한 경우
      • monfmt 가 0 이 아닌 경우
      • leave_queue(qe);
      • ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "CONNECT", "%ld|%s", (long)time(NULL) - qe->start, peer->uniqueid);

* EXITEMPTY(position|origposition|waittime)
  • The caller was exited from the queue forcefully because the queue had no reachable members and it's configured to do that to callers when there are no reachable members.

  • wait_our_turn
    • for (;;) {
      • stat = get_member_status(qe->parent, qe->max_penalty);
      • member 가 없을 때
        • ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start);
    • }

  • queue_exec
    • try_calling 이 0 을 return 한 경우
    • stat = get_member_status(qe.parent, qe.max_penalty);
    • member 가 없을 때
      • ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start));

* RINGNOANSWER(ringtime)
  • After trying for ringtime ms to connect to the available queue member, the attempt ended without the member picking up the call.

  • rna
    • ast_queue_log(qe->parent->name, qe->chan->uniqueid, membername, "RINGNOANSWER", "%d", rnatime);

  • wait_for_answer
  • while (*to && !peer) {
    • for (retry = 0; retry < 2; retry++) {
    • }
    • if (pos == 1 /* not found */) {
    • }
    • winner = ast_waitfor_n(watchers, pos, to);
    • for (o = start; o; o = o->call_next) {
      • if (o->stillgoing && (o->chan) && (o->chan->_state == AST_STATE_UP)) {
      • } else if (o->chan && (o->chan == winner)) {
        • if (!ast_strlen_zero(o->chan->call_forward) && !forwardsallowed) {
        • } else if (!ast_strlen_zero(o->chan->call_forward)) {
        • }
        • f = ast_read(winner);
        • if (f) {
          • if (f->frametype == AST_FRAME_CONTROL) {
            • switch (f->subclass) {
            • case AST_CONTROL_BUSY:
              • endtime = (long)time(NULL);
              • endtime -= starttime;
              • rna(endtime * 1000, qe, on, membername, 0);
            • case AST_CONTROL_CONGESTION:
              • endtime = (long)time(NULL);
              • endtime -= starttime;
              • rna(endtime * 1000, qe, on, membername, 0);
          • }
        • } else {
          • endtime = (long) time(NULL) - starttime;
          • rna(endtime * 1000, qe, on, membername, 1);
        • }
      • }
    • }
    • if (winner == in) {
    • }
    • if (!*to) {
      • for (o = start; o; o = o->call_next)
        • rna(orig, qe, o->interface, o->member->membername, 1);
    • }
  • }
* TRANSFER(extension|context|holdtime|calltime)
  • Caller was transferred to a different extension.

  • try_calling
    • wait_for_answer 의 return 값이 0 이 아닌 경우
      • announce, reportholdtime, memberdelay 중 하나라도 0 이 아닌 경우
      • ast_channel_make_compatible 이 음수를 return 한 경우
      • monfmt 가 0 이 아닌 경우
      • leave_queue(qe);
      • bridge = ast_bridge_call(qe->chan,peer, &bridge_config);
      • attended_transfer_occurred(qe->chan) 이 0 을 return 한 경우
        • if (strcasecmp(oldcontext, qe->chan->context) || strcasecmp(oldexten, qe->chan->exten)) {
          • ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "TRANSFER", "%s|%s|%ld|%ld", qe->chan->exten, qe->chan->context, (long) (callstart - qe->start), (long) (time(NULL) - callstart));
        • } else if (qe->chan->_softhangup) {
        • } else {
        • }

* OURTURN(position|origposition|waittime)

* ast_queue_log(args.queuename, chan->uniqueid, "NONE", "OURTURN", "%d|%d|%ld", qe.pos, qe.opos, (long) time(NULL) - qe.start);
  • is_our_turn 에서 res=1 인 경우에 위의 것을 추가하면 됨.

Version

* asterisk-0.2.0

* asterisk-0.3.0
  • manager_queues_show

ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "COMPLETEXXXXX", "%ld|%ld", (long)(callstart - qe->start), (long)(time(NULL) - callstart)); 

ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "TRANSFER", "%ld|%ld|%d|%s", (long)(callstart - qe->start), (long)(time(NULL) - callstart), qe->opos, qe->chan->exten);

* time variable
  • callstart
  • qe->start

* QUEUE_NO_REACHABLE_MEMBERS

* AST_DEVICE_UNAVAILABLE

* realtime queue members


/*! \brief A large function which calls members, updates statistics, and bridges the caller and a member
 * 
 * Here is the process of this function
 * 1. Process any options passed to the Queue() application. Options here mean the third argument to Queue()
 * 2. Iterate trough the members of the queue, creating a callattempt corresponding to each member. During this
 *    iteration, we also check the dialed_interfaces datastore to see if we have already attempted calling this
 *    member. If we have, we do not create a callattempt. This is in place to prevent call forwarding loops. Also
 *    during each iteration, we call calc_metric to determine which members should be rung when.
 * 3. Call ring_one to place a call to the appropriate member(s)
 * 4. Call wait_for_answer to wait for an answer. If no one answers, return.
 * 5. Take care of any holdtime announcements, member delays, or other options which occur after a call has been answered.
 * 6. Start the monitor or mixmonitor if the option is set
 * 7. Remove the caller from the queue to allow other callers to advance
 * 8. Bridge the call.
 * 9. Do any post processing after the call has disconnected.
 *
 * \param[in] qe the queue_ent structure which corresponds to the caller attempting to reach members
 * \param[in] options the options passed as the third parameter to the Queue() application
 * \param[in] url the url passed as the fourth parameter to the Queue() application
 * \param[in,out] tries the number of times we have tried calling queue members
 * \param[out] noption set if the call to Queue() has the 'n' option set.
 * \param[in] agi the agi passed as the fifth parameter to the Queue() application
 */


get_member_status


* referenced
  • join_queue
        if (!q->joinempty && (stat == QUEUE_NO_MEMBERS))
                *reason = QUEUE_JOINEMPTY;
        else if ((q->joinempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_MEMBERS))
                *reason = QUEUE_JOINUNAVAIL;
  • wait_our_turn
                if (qe->parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) {
                        *reason = QUEUE_LEAVEEMPTY;
                        ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start);
                        leave_queue(qe);
                        break;
                }

                /* leave the queue if no reachable agents, if enabled */
                if ((qe->parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) {
                        *reason = QUEUE_LEAVEUNAVAIL;
                        ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start);
                        leave_queue(qe);
                        break;
                }
  • queue_exec
    • try_calling 이 0 을 return 한 경우
                        if (qe.parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) {
                                record_abandoned(&qe);
                                reason = QUEUE_LEAVEEMPTY;
                                ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start));
                                res = 0;
                                break;
                        }

                        /* leave the queue if no reachable agents, if enabled */
                        if ((qe.parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) {
                                record_abandoned(&qe);
                                reason = QUEUE_LEAVEUNAVAIL;
                                ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start));
                                res = 0;
                                break;
                        }
* static enum queue_member_status get_member_status(struct call_queue *q, int max_penalty)
  • member 들을 하나씩 점검한다.
    • max_penalty 가 주어진 경우, 이보다 member->penalty 가 큰 것은 통과
    • paused member 도 통과
    • switch
      • status 가 AST_DEVICE_INVALID 면 break
      • status 가 AST_DEVICE_UNAVAILABLE
        • result = QUEUE_NO_REACHABLE_MEMBERS;
        • break
      • 이외의 경우는 return QUEUE_NORMAL
  • penalty 보다 작고, paused 가 아니고, status 가 AST_DEVICE_INVALID 가 아니고, status 가 AST_DEVICE_UNAVAILABLE 이 아닌 member 가 존재하면 return QUEUE_NORMAL
  • 위와 같은 member 가 하나도 없는 경우, status 가 AST_DEVICE_UNAVAILABLE 인 member 가 있으면 return QUEUE_NO_REACHABLE_MEMBERS
  • 위와 같은 member 가 없으면, return QUEUE_NO_MEMBERS

* static struct member *create_queue_member(const char *interface, const char *membername, int penalty, int paused)
  • struct member *cur;
  • if ((cur = ao2_alloc(sizeof(*cur), NULL))) {
    • cur->status = ast_device_state(interface);
  • }
  • return cur;

* int ast_device_state(const char *device) main/devicestate.c
  • buf = ast_strdupa(device);
  • tech = strsep(&buf, "/");
  • number = buf;
  • if (!number) {
    • provider = strsep(&tech, ":");
      • if (!provider)
        • return AST_DEVICE_INVALID;
      • number = tech;
      • tech = NULL;
  • }
  • if (provider) {
    • return getproviderstate(provider, number);
  • }
  • chan_tech = ast_get_channel_tech(tech);
  • if (!chan_tech)
    • return AST_DEVICE_INVALID;
  • if (!chan_tech->devicestate)
    • return ast_parse_device_state(device);
  • else {
    • res = chan_tech->devicestate(number);
    • if (res == AST_DEVICE_UNKNOWN) {
      • res = ast_parse_device_state(device);
      • if (res == AST_DEVICE_UNKNOWN)
        • res = AST_DEVICE_NOT_INUSE;
      • return res;
    • } else
      • return res;
  • }

static const char *devstatestring[] = {
	/* 0 AST_DEVICE_UNKNOWN */	"Unknown",	/* Valid, but unknown state */
	/* 1 AST_DEVICE_NOT_INUSE */	"Not in use",	/* Not used */
	/* 2 AST_DEVICE IN USE */	"In use",	/* In use */
	/* 3 AST_DEVICE_BUSY */		"Busy",		/* Busy */
	/* 4 AST_DEVICE_INVALID */	"Invalid",	/* Invalid - not known to Asterisk */
	/* 5 AST_DEVICE_UNAVAILABLE */	"Unavailable",	/* Unavailable (not registred) */
	/* 6 AST_DEVICE_RINGING */	"Ringing"	/* Ring, ring, ring */
};
case AST_DEVICE_UNAVAILABLE:
    result = QUEUE_NO_REACHABLE_MEMBERS;
chan_agent.c:	res = AST_DEVICE_UNAVAILABLE;
chan_iax2.c:	res = AST_DEVICE_UNAVAILABLE;
chan_sip.c:	        res = AST_DEVICE_UNAVAILABLE;

The queue system (\ref app_queue.c) treats a member as "active"
  if devicestate is != AST_DEVICE_UNAVAILBALE && != AST_DEVICE_INVALID

When placing a call to the queue member, queue system sets a member to busy if
        != AST_DEVICE_NOT_INUSE and != AST_DEVICE_UNKNOWN

* static void set_queue_result(struct ast_channel *chan, enum queue_result res)
  • pbx_builtin_setvar_helper(chan, "QUEUESTATUS", queue_resultsi.text);
    • sets the QUEUESTATUS channel variable
The Queue application sets the QUEUESTATUS channel variable upon
completion. The status of the call can be : TIMEOUT, FULL, JOINEMPTY,
LEAVEEMPTY, JOINUNAVAIL or LEAVEUNAVAIL.

Here an example....

...
exten => 3,5,Queue(scopserv-test|tH|||30)
exten => 3,6,GotoIf($["${QUEUESTATUS}" = "JOINEMPTY"]?1000)
exten => 3,7,GotoIf($["${QUEUESTATUS}" = "JOINUNAVAIL"]?1000)
exten => 3,8,GotoIf($["${QUEUESTATUS}" = "FULL"]?1000)
exten => 3,9,NoOp(Normal Queue exist)
exten => 3,10,Hangup

exten => 3,1000,Voicemail(b1000@scopserv) 

* leave
  • wait_our_turn
    • is_our_turn 이 0 을 return 하고,
    • timeout 이 안되고,
    • leavewhenempty 가 설정되어 있고, member status 가 QUEUE_NO_MEMBERS 인 경우
    • leavewhenempty == QUEUE_EMPTY_STRICT 이고 member status 가 QUEUE_NO_REACHABLE_MEMBERS 인 경우
    • leave_queue(qe);
    • return 0
  • try_calling
    • answered peer 이 있는 경우
  • queue_exec
    • queue 에 join 하면
    • 무조건 leave_queue 를 수행함.

wait our turn

* return 0 or the ascii value of the digit or -1
  • ast_waitfordigit 에서 -1 을 return 할 수 있음.

* wait_our_turn
  • queue_exec
    • if (!join_queue(args.queuename, &qe, &reason)) {
      • res = wait_our_turn(&qe, ringing, &reason);
      • if (res)
        • goto stop;
      • for (;;) {
      • }
      • stop:
      • if (res) {
        • if (res < 0) {
          • if (!qe.handled) {
          • ast_waitfordigit 에서 -1 을 return 한 경우
          • }
          • res = -1;
        • } else if (qe.valid_digits) {
        • say_position, say_periodic_announcement, ast_waitfordigit 에서 the ascii value of the digit 을 받은 경우
        • }
      • }
      • if (res >= 0) {
      • wait our turn 에서 온 경우는 res > 0 만임.
      • }
    • } else {
    • }

* static int wait_our_turn(struct queue_ent *qe, int ringing, enum queue_result *reason)
  • int res = 0;
  • for (;;) {
    • if (is_our_turn(qe))
      • break;
    • IFreason(QUEUE_TIMEOUT)
    • stat = get_member_status(qe->parent, qe->max_penalty);
    • if (qe->parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) {
      • leave_queue(qe);
      • reason(QUEUE_LEAVEEMPTY)
    • }
    • if ((qe->parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) {
      • leave_queue(qe);
      • reason(QUEUE_LEAVEUNAVAIL);
    • }
    • if (qe->parent->announcefrequency && !ringing && (res = say_position(qe)))
      • break;
        • announcefrequency 가 0 이면 for loop
        • ringing 가 0 이 아니면 for loop
        • say_position 이 0 를 return 하면 for loop
    • IFreason(QUEUE_TIMEOUT)
    • if (qe->parent->periodicannouncefrequency && !ringing && (res = say_periodic_announcement(qe)))
      • break;
        • periodicannouncefrequency 가 0 이면 for loop
        • ringing 가 0 이 아니면 for loop
        • say_periodic_announcement 이 0 를 return 하면 for loop
    • IFreason(QUEUE_TIMEOUT)
    • if ((res = ast_waitfordigit(qe->chan, RECHECK * 1000))) {
      • if (res > 0 && !valid_exit(qe, res))
        • res = 0;
          • for loop
      • else
        • break;
          • qe->valid_digits 을 1로 설정
    • }
    • IFreason(QUEUE_TIMEOUT)
  • }
  • return res;

* reason(QUEUESTATUS)
  • *reason = QUEUESTATUS;
  • break;

* IFreason(QUEUE_TIMEOUT)
  • if (qe->expire && (time(NULL) >= qe->expire)) {
    • reason(QUEUE_TIMEOUT);
  • }

* 0 을 return 하는 경우
  • our_turn 인 경우
  • time out
  • QUEUE_NO_MEMBERS
  • QUEUE_NO_REACHABLE_MEMBERS

* for loop
  • time out 인 경우는 for loop 의 첫번째에서 다시 time out 을 check 하여 빠져 나옴.
  • say_position(&qe)
  • say_periodic_announcement(&qe)
  • res = try_calling(&qe, args.options, args.announceoverride, args.url, &tries, &noption, args.agi);
  • if (res)
    • goto stop;


* leavewhenempty 의 경우 이곳에서 leave_queue 를 하는 이유가 무엇인가?

* 0 이 아닌 값을 return 하는 경우
  • if (qe->parent->announcefrequency && !ringing && (res = say_position(qe)))
    • break;
  • if (qe->parent->periodicannouncefrequency && !ringing && (res = say_periodic_announcement(qe)))
    • break;
  • if ((res = ast_waitfordigit(qe->chan, RECHECK * 1000))) {
    • if (res > 0 && !valid_exit(qe, res))
    • else
      • break;
  • }

* static int say_position(struct queue_ent *qe)
  • play_file
    • ast_streamfile
      • res = ast_waitstream(chan, AST_DIGIT_ANY);
  • playout:
  • if ((res > 0 && !valid_exit(qe, res)) || res < 0)
    • res = 0;
  • return res;
  • return 0 or the ascii value of the digit

* static int say_periodic_announcement(struct queue_ent *qe)
  • play_file
    • ast_streamfile
      • res = ast_waitstream(chan, AST_DIGIT_ANY);
  • if ((res > 0 && !valid_exit(qe, res)) || res < 0)
    • res = 0;
  • return res;
  • return 0 or the ascii value of the digit

* int ast_waitfordigit(struct ast_channel *c, int ms) main/channel.c
  • return ast_waitfordigit_full(c, ms, -1, -1);
  • Wait for a digit, no more than ms milliseconds total
    • return 1, -1, f->subclass, 0
    • hangup 의 경우 -1

* play_file
  • return 0 or the ascii value of the digit

file.h:#define AST_DIGIT_ANY "0123456789#*ABCD"
file.h:#define AST_DIGIT_ANYNUM "0123456789"
* int ast_waitstream(struct ast_channel *c, const char *breakon)
  • res holds the dtmf code, invalid or valid.
  • breakon is the string with codes ast_waitstream should break on, could be AST_DIGIT_ANY or could be "" in case you do not want to break on anything.
  • So res has the ascii value of the digit you pressed and strchr checks if that value is in the breakon string, if it is, it stops the playback (as the user has started to input dtmf values).

valid_exit


* static int valid_exit(struct queue_ent *qe, char digit)
  • 현재 qe->digits 에 assign 된 string 의 크기가 sizeof(qe->digits) - 2 보다 작으면 digit 를 string 끝에 append 한다.
  • 넘으면 qe->digits 를 reset
  • 해당되는 context 가 있는가 점검
  • 해당되는 context 에 해당되는 extension 이 있는가 점검.
  • 해당되는 extension 이 있으면 ast_goto_if_exists
    • qe->valid_digits = 1;
    • return 1;

* ast_goto_if_exists
  • ast_explicit_goto
  • chan->exten 에 exten 을 넣고 리턴함.

* called
  • say_position
  • say_periodic_announcement
  • wait_for_answer
  • wait_our_turn
  • wait_a_bit

queue status

TIMEOUT &#8211; the max time specified in the queue command elapsed, only checked
between retries so may not be 100% accurate.

FULL &#8211; the number of callers in the queues would exceed the maxlen= value
defined in queues.conf if another caller was added

JOINEMPTY &#8211; a call was sent to the queue but the queue had no members, does
not apply when using agentcallbacklogin since there could be unavailable
members defined but not available.

LEAVEEMPTY &#8211; the last agent was removed form the queue before alls calls we
handled, remain callers exit with this status, also acts differently when
there are only queue members that are unavaialbe

JOINUNAVAIL/LEAVEUNAVAIL &#8211; same as JOINEMPTY/LEAVEEMPTY, except that there
were still queue members, but all were status unavailable (logged out)

So if a queue is made up of only callback agents (agentcallbacklogin) then
the queuestatus will never be joinempty or leaveempty

If the maxlen=0 then there will never be a queuestatus of full

If there is no timeout in the queue command thee wll never be a questatus of
timeout

If there are no callback or static agents joinunavail/leaveunavail will
never apply.


 I don't believe that the logic you're describing here with "if a
queue is made up of only callback agents then the queuestatus will
never be joinempty or leavempty" is right. If a queue is made up of
only callback agents and none of those callback agents are presently
logged in, the status could certainly be joinempty or leavempty.

 The rest of it looks pretty good. Thanks for taking the time to
better document this!


1.4.24.1

Queue commands


queue add member - Add a channel to a specified queue
queue remove member - Removes a channel from a specified queue
queue show - Show status of a specified queue
rtcp debug ip - Enable RTCP debugging on IP
rtcp debug - Enable RTCP debugging
rtcp debug off - Disable RTCP debugging
rtcp stats - Enable RTCP stats
rtcp stats off - Disable RTCP stats
rtp debug ip - Enable RTP debugging on IP
rtp debug - Enable RTP debugging
rtp debug off - Disable RTP debugging
say load - Set/show the say mode
show parkedcalls - Lists parked calls
show queue - Show information for target queue
show queues - Show the queues
9) Queue and ACD management
01. Queue - thanks to this application you can attach a call in a queue.
02. AddQueueMember - with this application you can add an agent(member) in your queue. 
03. AgentCallbackLogin - the application allows you to log in an agent, into a queue, with callback.
04. AgentLogin - the application allows you to log in an agent into a queue.
05. RemoveQueueMember - this application allows you to remove, dynamically, an agents or members, from a queue.
06. PauseQueueMember - this application allows you to stop, temporary, the answering of calls in the queue, from a specific member.
07. UnpauseQueueMember - the usage of this application will cancel the effect of the PauseQueueMember application.
08. AgentMonitorOutgoing - this application allows you to figure out the Caller ID of the agent, who is making an outgoing call.
09. ParkedCall - this application answers parked calls 
10. Park - this application allows you to park yourself.
11. ParkAndAnnounce
* ast_register_application
  • queue_exec Queue(queuename[|optionsURLannounceoverridetimeout)
  • aqm_exec AddQueueMember(queuenameinterfacepenalty)
  • rqm_exec RemoveQueueMember(queuenamemember)
  • pqm_exec PauseQueueMember(queuenamemember)
  • upqm_exec UnpauseQueueMember(queuenamemember)
  • ql_exec
* ast_manager_register
  • Queues", 0, manager_queues_show, "Queues
  • QueueStatus", 0, manager_queues_status, "Queue Status
  • QueueAdd", EVENT_FLAG_AGENT, manager_add_queue_member, "Add interface to queue
  • QueueRemove", EVENT_FLAG_AGENT, manager_remove_queue_member, "Remove interface from queue
  • QueuePause", EVENT_FLAG_AGENT, manager_pause_queue_member, "Makes a queue member temporarily unavailable
* ast_custom_function_register
  • queueagentcount_function
  • queuemembercount_function
  • queuememberlist_function
  • queuewaitingcount_function
* ast_devstate_add
  • statechange_queue

try_calling

/*! \brief A large function which calls members, updates statistics, and bridges the caller and a member
 * 
 * Here is the process of this function
 * 1. Process any options passed to the Queue() application. Options here mean the third argument to Queue()
 * 2. Iterate trough the members of the queue, creating a callattempt corresponding to each member. During this
 *    iteration, we also check the dialed_interfaces datastore to see if we have already attempted calling this
 *    member. If we have, we do not create a callattempt. This is in place to prevent call forwarding loops. Also
 *    during each iteration, we call calc_metric to determine which members should be rung when.
 * 3. Call ring_one to place a call to the appropriate member(s)
 * 4. Call wait_for_answer to wait for an answer. If no one answers, return.
 * 5. Take care of any holdtime announcements, member delays, or other options which occur after a call has been answered.
 * 6. Start the monitor or mixmonitor if the option is set
 * 7. Remove the caller from the queue to allow other callers to advance
 * 8. Bridge the call.
 * 9. Do any post processing after the call has disconnected.
 *
 * \param[in] qe the queue_ent structure which corresponds to the caller attempting to reach members
 * \param[in] options the options passed as the third parameter to the Queue() application
 * \param[in] url the url passed as the fourth parameter to the Queue() application
 * \param[in,out] tries the number of times we have tried calling queue members
 * \param[out] noption set if the call to Queue() has the 'n' option set.
 * \param[in] agi the agi passed as the fifth parameter to the Queue() application
 */

* try_calling
  • queue_exec
    • if (!join_queue(args.queuename, &qe, &reason)) {
      • check_turns:
      • res = wait_our_turn(&qe, ringing, &reason);
      • if (res)
        • goto stop;
      • for (;;) {
        • break and goto stop
        • ...
        • res = try_calling(&qe, args.options, args.announceoverride, args.url, &tries, &noption, args.agi);
        • if (res)
          • goto stop;
        • ....
        • res = wait_a_bit(&qe);
        • if (res)
          • goto stop;
        • if (!is_our_turn(&qe)) {
          • goto check_turns;
        • }
        • stop:
        • if (res) {
        • }
        • if (res >= 0) {
        • }
      • }
    • } else {
    • }
    • return res;

* try_calling return non-zero
  • while ((cur = ao2_iterator_next(&memi))) {
  • }
  • lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies, ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT), forwardsallowed);
  • peer = lpeer ? lpeer->chan : NULL;
  • if (!peer) {
    • if (to) {
      • res = -1;
    • } else {
      • res = digit;
    • }
  • } else {
    • if (announce || qe->parent->reportholdtime || qe->parent->memberdelay) {
      • int res2;
      • res2 = ast_autoservice_start(qe->chan);
      • if (!res2) {
        • if (qe->parent->memberdelay) {
          • res2 |= ast_safe_sleep(peer, qe->parent->memberdelay * 1000);
        • }
      • }
      • res2 |= ast_autoservice_stop(qe->chan);
      • if (peer->_softhangup) {
      • } else if (res2) {
        • return -1;
      • }
    • }
    • res = ast_channel_make_compatible(qe->chan, peer);
    • if (res < 0) {
      • return -1;
    • }
    • bridge = ast_bridge_call(qe->chan,peer, &bridge_config);
    • res = bridge ? bridge : 1;
  • }
  • out:
  • hangupcalls(outgoing, NULL);
  • return res;

* try_calling return zero
  • int res = 0;
  • ...
  • time(&now);
  • if (qe->expire && now >= qe->expire) {
    • res = 0;
    • goto out;
  • }
  • for (; options && *options; options++)
  • }
  • memi = ao2_iterator_init(qe->parent->members, 0);
  • while ((cur = ao2_iterator_next(&memi))) {
    • struct callattempt *tmp = ast_calloc(1, sizeof(*tmp));
    • if (!tmp) {
      • goto out;
    • }
    • if (!datastore) {
      • if (!(datastore = ast_channel_datastore_alloc(&dialed_interface_info, NULL))) {
        • free(tmp);
        • goto out;
      • }
      • if (!(dialed_interfaces = ast_calloc(1, sizeof(*dialed_interfaces)))) {
        • free(tmp);
        • goto out;
      • }
    • } else
    • AST_LIST_TRAVERSE(dialed_interfaces, di, list) {
      • if (!strcasecmp(cur->interface, di->interface)) {
        • break;
      • }
    • }
    • if (di) {
      • free(tmp);
      • continue;
    • }
    • if (strncasecmp(cur->interface, "Local/", 6)) {
      • if (!(di = ast_calloc(1, sizeof(*di) + strlen(cur->interface)))) {
        • free(tmp);
        • goto out;
      • }
    • }
    • if (!calc_metric(qe->parent, cur, x++, qe, tmp)) {
    • } else {
    • }
  • }
  • ring_one(qe, outgoing, &numbusies);
  • lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies, ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT), forwardsallowed);
  • peer = lpeer ? lpeer->chan : NULL;
  • if (!peer) {
  • } else {
    • if (announce || qe->parent->reportholdtime || qe->parent->memberdelay) {
      • int res2;
      • res2 = ast_autoservice_start(qe->chan);
      • if (!res2) {
        • if (qe->parent->memberdelay) {
          • res2 |= ast_safe_sleep(peer, qe->parent->memberdelay * 1000);
        • }
      • }
      • res2 |= ast_autoservice_stop(qe->chan);
      • if (peer->_softhangup) {
        • ast_hangup(peer);
        • goto out;
      • } else if (res2) {
        • return -1;
      • }
    • }
    • res = ast_channel_make_compatible(qe->chan, peer);
    • if (res < 0) {
      • return -1;
    • }
    • bridge = ast_bridge_call(qe->chan,peer, &bridge_config);
    • res = bridge ? bridge : 1;
  • }
  • out:
  • hangupcalls(outgoing, NULL);
  • return res;

try_calling 1


* channel.h
struct ast_datastore_info {
        const char *type;               /*!< Type of data store */
        void *(*duplicate)(void *data); /*!< Duplicate item data (used for inheritance) */
        void (*destroy)(void *data);    /*!< Destroy function */
        /*!
         * \brief Fix up channel references
         *
         * \arg data The datastore data
         * \arg old_chan The old channel owning the datastore
         * \arg new_chan The new channel owning the datastore
         *
         * This is exactly like the fixup callback of the channel technology interface.
         * It allows a datastore to fix any pointers it saved to the owning channel
         * in case that the owning channel has changed.  Generally, this would happen
         * when the datastore is set to be inherited, and a masquerade occurs.
         *
         * \return nothing.
         */
        void (*chan_fixup)(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan);
};

struct ast_datastore {
        char *uid;              /*!< Unique data store identifier */
        void *data;             /*!< Contained data */
        const struct ast_datastore_info *info;  /*!< Data store type information */
        unsigned int inheritance;       /*!Number of levels this item will continue to be inherited */
        AST_LIST_ENTRY(ast_datastore) entry; /*!< Used for easy linking */
};

struct member {
        char interface[80];                 /*!< Technology/Location */
        char membername[80];                /*!< Member name to use in queue logs */
        int penalty;                        /*!< Are we a last resort? */
        int calls;                          /*!< Number of calls serviced by this member */
        int dynamic;                        /*!< Are we dynamically added? */
        int realtime;                       /*!< Is this member realtime? */
        int status;                         /*!< Status of queue member */
        int paused;                         /*!< Are we paused (not accepting calls)? */
        time_t lastcall;                    /*!< When last successful call was hungup */
        unsigned int dead:1;                /*!< Used to detect members deleted in realtime */
        unsigned int delme:1;               /*!< Flag to delete entry on reload */
};
* main/global_datastores.c
const struct ast_datastore_info dialed_interface_info = {
        .type = "dialed-interface",
        .destroy = dialed_interface_destroy,
        .duplicate = dialed_interface_duplicate,
};

struct ast_dialed_interface {
        AST_LIST_ENTRY(ast_dialed_interface) list;
        char interface[1];
};



* datastore 를 만들어 channel 에 assign 한다.
  • datastore = ast_channel_datastore_alloc(&dialed_interface_info, NULL)
  • datastore->data = dialed_interfaces;
  • ast_channel_datastore_add(qe->chan, datastore);

* member 의 interface 가 이미 datastore 에 있는지 점검한다.
  • datastore = ast_channel_datastore_find(qe->chan, &dialed_interface_info, NULL);
  • dialed_interfaces = datastore->data;
  • AST_LIST_TRAVERSE(dialed_interfaces, di, list) {
    • if (!strcasecmp(cur->interface, di->interface)) {
      • already been dialed
    • }
  • }

* member 의 interface 가 이미 datastore 에 없는 경우
  • strcpy(di->interface, cur->interface);
  • AST_LIST_INSERT_TAIL(dialed_interfaces, di, list);

* try_calling
  • outgoing 을 만들때는 member status 를 점검하지 않는다.
  • struct ast_datastore *datastore, *transfer_ds;
  • datastore = ast_channel_datastore_find(qe->chan, &dialed_interface_info, NULL);
    • channel 에 연결된 ast_datastore 에서 ast_datastore_info 를 dialed_interface_info 와 비교하여 type 이 같은 것이 있으면, ast_datastore 를 return 한다.
  • if (qe->expire && now >= qe->expire) {
  • }
  • for (; options && *options; options++)
  • }
  • memi = ao2_iterator_init(qe->parent->members, 0);
  • while ((cur = ao2_iterator_next(&memi))) {
    • struct callattempt *tmp = ast_calloc(1, sizeof(*tmp));
    • if (!tmp) {
      • goto out;
    • }
    • if (!datastore) {
      • 같은 type 의 datastore 가 없으면
      • if (!(datastore = ast_channel_datastore_alloc(&dialed_interface_info, NULL))) {
        • datastore 를 위한 memory 를 alloc 을 못하면
        • goto out;
      • }
      • datastore->inheritance = DATASTORE_INHERIT_FOREVER;
      • if (!(dialed_interfaces = ast_calloc(1, sizeof(*dialed_interfaces)))) {
        • dialed_interfaces 를 위한 memory 를 alloc 을 못하면
        • goto out;
      • }
      • datastore->data = dialed_interfaces;
      • ast_channel_datastore_add(qe->chan, datastore);
        • qe->chan 에 datastore 를 추가한다.
    • } else
      • dialed_interfaces = datastore->data;
        • 같은 type 의 datastore 가 있으면, datastore의 data 를 dialed_interfaces 에 assign 한다.
    • AST_LIST_TRAVERSE(dialed_interfaces, di, list) {
      • if (!strcasecmp(cur->interface, di->interface)) {
        • break;
        • member 의 interface 와 dialed_interfaces 의 interface 가 같은 것이면 di 는 null 이 아님.
      • }
    • }
    • if (di) {
      • continue;
      • dialed_interfaces 에 member 의 interface 와 같은 것이 이미 있으면 다음 member 에 대해 같은 일을 수행한다.
      • 즉 outgoing 에 넣지를 않는다.
    • }
    • if (strncasecmp(cur->interface, "Local/", 6)) {
      • member 의 interface 가 Local 이 아니면.
      • if (!(di = ast_calloc(1, sizeof(*di) + strlen(cur->interface)))) {
        • member 의 interface 만큼의 memory 를 더 alloc 하지 못하면
        • goto out;
        • member 의 interface 만큼의 memory 를 더 alloc 한다.
        • dialed_interfaces structure 에 interface 가 하나의 character 로 정의 되어 있으므로.
        • strcpy(di->interface, cur->interface);
        • di->interface 에 member 의 interface 를 복사하고
      • }
      • AST_LIST_INSERT_TAIL(dialed_interfaces, di, list);
      • di 를 dialed_interfaces 의 끝에 추가한다.
    • }
    • tmp->stillgoing = -1;
    • if (!calc_metric(qe->parent, cur, x++, qe, tmp)) {
      • tmp->q_next = outgoing;
      • outgoing = tmp;
      • if (outgoing->chan && (outgoing->chan->_state == AST_STATE_UP))
        • break;
        • member 가 수화기를 든 경우.
          • ring 전에 수화기를 들 수 있는가?
          • ring 이나 대화중이 아닐 경우 항상 AST_STATE_UP 인 기기가 있나?
    • } else {
      • free(tmp);
    • }
  • }
  • if (qe->expire && (!qe->parent->timeout || (qe->expire - now) <= qe->parent->timeout))
    • to = (qe->expire - now) * 1000;
  • else
    • to = (qe->parent->timeout) ? qe->parent->timeout * 1000 : -1;
  • ++qe->pending;
  • ring_one(qe, outgoing, &numbusies);
  • lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies, ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT), forwardsallowed);
  • peer = lpeer ? lpeer->chan : NULL;
  • if (!peer) {
  • } else {
    • if (announce || qe->parent->reportholdtime || qe->parent->memberdelay) {
    • }
    • res = ast_channel_make_compatible(qe->chan, peer);
    • if (res < 0) {
    • }
    • if (qe->parent->monfmt && *qe->parent->monfmt) {
    • }
    • leave_queue(qe);
    • bridge = ast_bridge_call(qe->chan,peer, &bridge_config);
    • if (!attended_transfer_occurred(qe->chan)) {
    • }
    • ast_hangup(peer);
    • res = bridge ? bridge : 1;
  • }
  • out:
  • hangupcalls(outgoing, NULL);
  • return res;

try_calling 2


  • datastore = ast_channel_datastore_find(qe->chan, &dialed_interface_info, NULL);
  • if (qe->expire && now >= qe->expire) {
  • }
  • for (; options && *options; options++)
  • }
  • memi = ao2_iterator_init(qe->parent->members, 0);
  • while ((cur = ao2_iterator_next(&memi))) {
  • }
  • if (qe->expire && (!qe->parent->timeout || (qe->expire - now) <= qe->parent->timeout))
    • to = (qe->expire - now) * 1000;
  • else
    • to = (qe->parent->timeout) ? qe->parent->timeout * 1000 : -1;
  • ++qe->pending;
  • ring_one(qe, outgoing, &numbusies);
  • lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies, ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT), forwardsallowed);
  • peer = lpeer ? lpeer->chan : NULL;
  • if (!peer) {
    • qe->pending = 0
    • if (to) {
      • wait_for_n 에서 member 들 보다 caller 의 응답이 먼저이고, 응답이 hangup 인 경우
      • res = -1
    • } else {
      • ring_one 에서 ast_call 에 응답한 member 가 없는 경우
      • wait_for_n 에서 member 들 보다 caller 의 응답이 먼저이고, 응답이 * 인 경우
      • wait_for_n 에서 member 들 보다 caller 의 응답이 먼저이고, 응답이 valid digit 인 경우
      • res = digit
      • 응답이 valid digit 인 경우만 digit 가 0 이 아님
    • }
  • } else {
    • ring_one 에서 ast_call 에 응답하고, channel 의 state 가 AST_STATE_UP 이면
    • member 가 응답을 하고, f->subclass 가 AST_CONTROL_ANSWER 여야 함.
    • time(&now);
    • recalc_holdtime(qe, (now - qe->start));
    • callcompletedinsl = ((now - qe->start) <= qe->parent->servicelevel);
    • member = lpeer->member;
    • hangupcalls(outgoing, peer);
    • outgoing = NULL;
    • if (announce || qe->parent->reportholdtime || qe->parent->memberdelay) {
      • res2 = ast_autoservice_start(qe->chan);
      • if (!res2) {
      • }
      • res2 |= ast_autoservice_stop(qe->chan);
      • if (peer->_softhangup) {
        • ast_hangup(peer);
        • goto out;
      • } else if (res2) {
        • ast_hangup(peer);
        • return -1;
      • }
    • }
    • ast_moh_stop(qe->chan);
    • res = ast_channel_make_compatible(qe->chan, peer);
    • if (res < 0) {
      • ast_hangup(peer);
      • return -1;
    • }
    • if (qe->parent->monfmt && *qe->parent->monfmt) {
    • }
    • leave_queue(qe);
    • if (!ast_strlen_zero(url) && ast_channel_supports_html(peer)) {
      • ast_channel_sendurl(peer, url);
    • }
    • if (!ast_strlen_zero(agi)) {
    • }
    • qe->handled++;
    • time(&callstart);
    • transfer_ds = setup_transfer_datastore(qe, member, callstart, callcompletedinsl);
    • bridge = ast_bridge_call(qe->chan,peer, &bridge_config);
    • if (!attended_transfer_occurred(qe->chan)) {
      • if (strcasecmp(oldcontext, qe->chan->context) || strcasecmp(oldexten, qe->chan->exten)) {
      • } else if (qe->chan->_softhangup) {
      • } else {
      • }
      • update_queue(qe->parent, member, callcompletedinsl);
    • }
    • ast_hangup(peer);
    • res = bridge ? bridge : 1;
  • }
  • out:
  • hangupcalls(outgoing, NULL)

try_calling 3


* try_calling
  • datastore = ast_channel_datastore_find(qe->chan, &dialed_interface_info, NULL);
  • if (qe->expire && now >= qe->expire) {
  • }
  • for (; options && *options; options++)
  • }
  • memi = ao2_iterator_init(qe->parent->members, 0);
  • while ((cur = ao2_iterator_next(&memi))) {
  • }
  • if (qe->expire && (!qe->parent->timeout || (qe->expire - now) <= qe->parent->timeout))
    • to = (qe->expire - now) * 1000;
  • else
    • to = (qe->parent->timeout) ? qe->parent->timeout * 1000 : -1;
  • ++qe->pending;
  • ring_one(qe, outgoing, &numbusies);
  • lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies, ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT), forwardsallowed);
  • peer = lpeer ? lpeer->chan : NULL;
  • if (!peer) {
  • } else {
    • if (announce || qe->parent->reportholdtime || qe->parent->memberdelay) {
    • }
    • res = ast_channel_make_compatible(qe->chan, peer);
    • if (res < 0) {
      • record_abandoned(qe);
      • ast_hangup(peer);
      • return -1;
    • }
    • if (qe->parent->monfmt && *qe->parent->monfmt) {
      • return 하지 않음.
    • }
    • leave_queue(qe);
    • if (!ast_strlen_zero(url) && ast_channel_supports_html(peer)) {
      • ast_channel_sendurl(peer, url);
      • 'URL' allows you to specify a URL that will be sent to the called party if the channel supports it.
    • }
    • if (!ast_strlen_zero(agi)) {
      • app = pbx_findapp("agi");
      • if (app) {
        • agiexec = ast_strdupa(agi);
        • ret = pbx_exec(qe->chan, app, agiexec);
      • }
      • AGI parameter will setup an AGI script to be executed on the calling party's channel once they are connected to a queue member.
    • }
    • qe->handled++;
    • transfer_ds = setup_transfer_datastore(qe, member, callstart, callcompletedinsl);
    • bridge = ast_bridge_call(qe->chan,peer, &bridge_config);
    • if (!attended_transfer_occurred(qe->chan)) {
      • if (strcasecmp(oldcontext, qe->chan->context) || strcasecmp(oldexten, qe->chan->exten)) {
        • TRANSFER
      • } else if (qe->chan->_softhangup) {
        • COMPLETECALLER
      • } else {
        • COMPLETEAGENT
      • }
      • if ((tds = ast_channel_datastore_find(qe->chan, &queue_transfer_info, NULL))) {
        • ast_channel_datastore_remove(qe->chan, tds);
      • }
      • update_queue(qe->parent, member, callcompletedinsl);
    • }
    • if (transfer_ds) {
    • }
    • ast_hangup(peer);
    • res = bridge ? bridge : 1;
  • }
  • out:
  • hangupcalls(outgoing, NULL);
  • return res;

* transfer
  • char oldextenAST_MAX_EXTENSION="";
  • char oldcontextAST_MAX_CONTEXT="";
  • ...
  • ast_copy_string(oldcontext, qe->chan->context, sizeof(oldcontext));
  • ast_copy_string(oldexten, qe->chan->exten, sizeof(oldexten));
  • time(&callstart);
  • transfer_ds = setup_transfer_datastore(qe, member, callstart, callcompletedinsl);
  • bridge = ast_bridge_call(qe->chan,peer, &bridge_config);
  • if (!attended_transfer_occurred(qe->chan)) {
    • if (strcasecmp(oldcontext, qe->chan->context) || strcasecmp(oldexten, qe->chan->exten)) {
      • TRANSFER
      • bridge 된 후에 channel 의 context 나 exten 이 변경되는 경우
      • 즉 통화중 transfer 를 한 경우
    • } else if (qe->chan->_softhangup) {
      • caller 가 hangup 한 경우
    • } else {
      • agent 가 hangup 한 경우
    • }
    • if ((tds = ast_channel_datastore_find(qe->chan, &queue_transfer_info, NULL))) {
      • ast_channel_datastore_remove(qe->chan, tds);
    • }
  • }
  • queue_transfer_info 에 해당되는 datastore 가 없어지는 경우가 있는가?
  • if (transfer_ds) {
    • ast_channel_datastore_free(transfer_ds);
  • }

* static struct ast_datastore *setup_transfer_datastore(struct queue_ent *qe, struct member *member, time_t starttime, int callcompletedinsl)
  • struct queue_transfer_ds *qtds = ast_calloc(1, sizeof(*qtds));
  • if (!qtds) {
    • return NULL;
  • }
  • if (!(ds = ast_channel_datastore_alloc(&queue_transfer_info, NULL))) {
    • return NULL;
  • }
  • ds->data = qtds;
  • ast_channel_datastore_add(qe->chan, ds);
  • return ds;

* static int attended_transfer_occurred(struct ast_channel *chan)
  • return ast_channel_datastore_find(chan, &queue_transfer_info, NULL) ? 0 : 1;

/*! \brief mechanism to tell if a queue caller was atxferred by a queue member.
 *
 * When a caller is atxferred, then the queue_transfer_info datastore
 * is removed from the channel. If it's still there after the bridge is
 * broken, then the caller was not atxferred.
 *
 * \note Only call this with chan locked
 */
static int attended_transfer_occurred(struct ast_channel *chan)
{
        return ast_channel_datastore_find(chan, &queue_transfer_info, NULL) ? 0 : 1;
}


ring_one


* Place a call to a queue member.
  • 1 if a member was called successfully
    • ring_entry 가 1 을 return 한 경우
  • 0 find_best 가 null 을 return 한 경우
    • 모든 member 의 stillgoing 가 0 인 경우
    • stillgoing 이 -1 인 member 가 있으면, 그 member 의 chan 은 null 이 아니어야 함.

* referenced
  • try_calling
  • wait_for_answer

Once metrics have been calculated for each member, this function is used to place a call to the appropriate member (or members). The low-level channel-handling and error detection is handled in ring_entry

* static int ring_one(struct queue_ent *qe, struct callattempt *outgoing, int *busies)
  • int ret = 0;
  • while (ret == 0) {
    • struct callattempt *best = find_best(outgoing);
    • if (!best) {
      • break;
      • stillgoing 이 -1 이고 chan 이 설정되지 않은 callattempt 가 없는 경우
    • }
    • if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) {
      • for (cur = outgoing; cur; cur = cur->q_next) {
        • if (cur->stillgoing && !cur->chan && cur->metric <= best->metric) {
          • ret |= ring_entry(qe, cur, busies);
          • best 의 metric 과 metric 이 같은 callattempt 들에 대해 ringentry
        • }
      • }
    • } else {
      • ret = ring_entry(qe, best, busies);
      • best callattempt 에 대해 ring_entry
    • }
  • }
  • return ret;

* find_best return NULL
  • outgoing 의 stillgoing 이 모두 0 인 경우
  • cur->chan 이 0 인 경우?
    • ast_request 를 아직 하지 않은 member
  • 좀 더 자세히 볼 것
  • for wait_for_answer 의 retry 부분관련

* static struct callattempt *find_best(struct callattempt *outgoing)
  • for (cur = outgoing; cur; cur = cur->q_next) {
    • if (cur->stillgoing && !cur->chan && (!best || cur->metric < best->metric)) {
      • best = cur;
    • }
  • }
  • return best;
  • stillgoing 이 -1 이고 chan 이 아직 없는 첫번째 callattempt 를 best 로 설정
  • stillgoing 이 -1 이고 chan 이 아직 없는 callattempt 의 metric 와 앞에 설정된 best 의 metric 를 비교하여 새로운 것이 적으면 best 를 현재 callattempt 로 재설정
  • 즉 stillgoing 이 -1 이고 chan 이 아직 없는 callattempt 중 metric 가 가장 적은 것을 선정함.

ring_entry


/*! \brief Part 2 of ring_one
 *
 * Does error checking before attempting to request a channel and call a member. This
 * function is only called from ring_one
 */

* stillgoing in callattempt
  • try_calling set stillgoing to -1
  • ring_entry, wait_for_answer, do_hang set stillgoing to 0
  • used in wait_for_answer, find_best, ring_one

* stillgoing in wait_for_answer
  • wait_for_answer set stillgoing to 1

* static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies)
  • if (qe->parent->wrapuptime && (time(NULL) - tmp->lastcall < qe->parent->wrapuptime)) {
    • lastcall 이후 wrapuptime 만큼 시간이 지나지 않은 경우
    • tmp->stillgoing = 0;
    • (*busies)++;
    • return 0;
  • }
  • if (!qe->parent->ringinuse && (tmp->member->status != AST_DEVICE_NOT_INUSE) && (tmp->member->status != AST_DEVICE_UNKNOWN)) {
    • tmp->stillgoing = 0;
    • return 0;
    • inuse versus busies ?
  • }
  • if (tmp->member->paused) {
    • member 가 paused 상태인 경우
    • tmp->stillgoing = 0;
    • return 0;
  • }
  • if (use_weight && compare_weight(qe->parent,tmp->member)) {
    • 이 member 가 속한 다른 queue 가 더 높은 weight 를 가지고 있는 경우
    • 즉 다른 queue 에서 사용의 우선권이 있음.
    • tmp->stillgoing = 0;
    • (*busies)++;
    • return 0;
  • }
  • tmp->chan = ast_request(tech, qe->chan->nativeformats, location, &status);
  • if (!tmp->chan) {
    • asr_request 에서 null 이 return 된 경우
    • tmp->stillgoing = 0;
    • update_status(tmp->member->interface, ast_device_state(tmp->member->interface));
    • qe->parent->rrpos++;
    • (*busies)++;
    • return 0;
  • }
  • if ((res = ast_call(tmp->chan, location, 0))) {
    • INVITE message 를 보냄.
    • ast_call 에서 non-zero 값을 return
    • do_hang(tmp);
    • (*busies)++;
    • update_status(tmp->member->interface, ast_device_state(tmp->member->interface));
    • return 0;
  • } else if (qe->parent->eventwhencalled) {
    • manager_event
  • }
  • update_status(tmp->member->interface, ast_device_state(tmp->member->interface));
  • return 1;

ringinuse - (default value - no) - If you want the queue to avoid sending calls to members whose devices are known to be 'in use' (via the channel driver supporting that device state) uncomment this option. (Note: only the SIP channel driver currently is able to report 'in use').
weight - when one channel is included in more than one queue, the queue with the higher weight will be the first one which will handle an incoming call on this channel.
static int compare_weight(struct call_queue *rq, struct member *member)
traverse all defined queues which have calls waiting and contain this member
return 0 if no other queue has precedence (higher weight) or 1 if found

wait_for_answer

/*! \brief Check if members are available
 *
 * This function checks to see if members are available to be called. If any member
 * is available, the function immediately returns QUEUE_NORMAL. If no members are available,
 * the appropriate reason why is returned
 */
if (qe->expire && (!qe->parent->timeout || (qe->expire - now) <= qe->parent->timeout))
                to = (qe->expire - now) * 1000;
else
                to = (qe->parent->timeout) ? qe->parent->timeout * 1000 : -1;

lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies, ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT), forwardsallowed);

* wait_for_answer * static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callattempt *outgoing, int *to, char *digit, int prebusies, int caller_disconnect, int forwardsallowed)
  • struct callattempt *peer = NULL;
  • while (*to && !peer) {
    • for (retry = 0; retry < 2; retry++) {
      • make watchers and call_next
      • for (o = outgoing; o; o = o->q_next) {
      • }
      • if (pos > 1 || !stillgoing || (qe->parent->strategy != QUEUE_STRATEGY_RINGALL))
        • break;
      • ring_one(qe, outgoing, &numbusies);
    • }
    • if (pos == 1) {
      • no watchers
      • *to = 0;
      • return NULL;
    • }
    • winner = ast_waitfor_n(watchers, pos, to);
    • for (o = start; o; o = o->call_next) {
      • if (o->stillgoing && (o->chan) && (o->chan->_state == AST_STATE_UP)) {
        • if (!peer) {
          • peer = o;
        • }
      • } else if (o->chan && (o->chan == winner)) {
        • forward 처리
        • f = ast_read(winner);
        • if (f) {
          • if (f->frametype == AST_FRAME_CONTROL) {
            • f->subclass 가 AST_CONTROL_ANSWER 이고 peer 이 NULL 이면
              • peer = o;
          • }
        • } else {
        • }
      • }
    • }
    • if (winner == in) {
      • f = ast_read(in);
      • if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) {
        • *to = -1;
        • return NULL;
      • }
      • if ((f->frametype == AST_FRAME_DTMF) && caller_disconnect && (f->subclass == '*')) {
        • *to = 0;
        • return NULL;
      • }
      • if ((f->frametype == AST_FRAME_DTMF) && valid_exit(qe, f->subclass)) {
        • *to = 0;
        • *digit = f->subclass;
        • return NULL;
      • }
    • }
    • if (!*to) {
    • }
  • }
  • return peer;

wait_for_answer 1


* wait_for_answer
  • watchers 와 callattempt 의 call_next 생성
  • struct callattempt *peer = NULL;
  • starttime = (long) time(NULL);
  • while (*to && !peer) {
    • int numlines, retry, pos = 1;
    • for (retry = 0; retry < 2; retry++) {
      • for (o = outgoing; o; o = o->q_next) {
        • if (o->stillgoing) {
          • stillgoing = 1;
          • if (o->chan) {
            • QUEUE_STRATEGY_RINGALL 이 아니면, ring_entry 를 best 에 대해서만 하므로, chan 이 생성된 것은 한개임.
            • watcherspos++ = o->chan;
            • if (!start)
              • start = o;
            • else
              • prev->call_next = o;
            • prev = o;
          • }
        • }
      • }
      • if (pos > 1 || !stillgoing || (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) )
        • break;
        • pos >1 : callattempt 의 stillgoing 가 -1 이고 chan 이 생성되어 있는 경우
        • !stillgoing : callattempt 의 stillgoing 가 -1 인 callattempt 가 하나도 없는 경우
        • QUEUE_STRATEGY_RINGALL 이 아닌 경우
      • ring_one(qe, outgoing, &numbusies);
      • QUEUE_STRATEGY_RINGALL 이고 callattempt 의 stillgoing 가 -1 인 callattempt 가 하나도 없는 경우, ring_one 을 수행하여 metric 값이 현재 보다 큰 callattempt 들을 찾아 한번 더 수행한다.
    • }
    • if (pos == 1 /* not found */) {
      • *to = 0;
      • return NULL;
      • ring_entry 에서 ast_request 가 성공한 것이 없는 경우
    • }
    • winner = ast_waitfor_n(watchers, pos, to);
    • for (o = start; o; o = o->call_next) {
      • if (o->stillgoing && (o->chan) && (o->chan->_state == AST_STATE_UP)) {
      • } else if (o->chan && (o->chan == winner)) {
      • }
    • }
    • if (winner == in) {
    • }
    • if (!*to) {
    • }
  • }
  • return peer;

wait_for_answer 2


* wait_for_answer
  • while (*to && !peer) {
    • for (retry = 0; retry < 2; retry++) {
      • for (o = outgoing; o; o = o->q_next) {
      • }
    • }
    • if (pos == 1 /* not found */) {
    • }
    • winner = ast_waitfor_n(watchers, pos, to);
    • for (o = start; o; o = o->call_next) {
      • if (o->stillgoing && (o->chan) && (o->chan->_state == AST_STATE_UP)) {
      • } else if (o->chan && (o->chan == winner)) {
        • if (!ast_strlen_zero(o->chan->call_forward) && !forwardsallowed) {
        • } else if (!ast_strlen_zero(o->chan->call_forward)) {
        • }
        • f = ast_read(winner);
        • if (f) {
        • } else {
        • }
      • }
    • }
    • if (winner == in) {
    • }
    • if (!*to) {
    • }
  • }
  • return peer;


wait_for_answer 2-1


* wait_for_answer
  • while (*to && !peer) {
    • for (retry = 0; retry < 2; retry++) {
      • for (o = outgoing; o; o = o->q_next) {
      • }
    • }
    • if (pos == 1 /* not found */) {
    • }
    • watchers 와 callattempt 의 call_next 생성
    • winner = ast_waitfor_n(watchers, pos, to);
    • Wait for x amount of time on a file descriptor to have input
    • return a file descriptor which have input
    • for (o = start; o; o = o->call_next) {
      • if (o->stillgoing && (o->chan) && (o->chan->_state == AST_STATE_UP)) {
        • try_calling 중 calc_metric 를 수행하고 break 한 경우
        • 이 외의 경우도 있는가?
        • if (!peer) {
          • peer = o;
        • }
      • } else if (o->chan && (o->chan == winner)) {
        • if (!ast_strlen_zero(o->chan->call_forward) && !forwardsallowed) {
        • chan_dahdi.c, chan_mgcp.c, chan_skinny.c
        • chan_sip.c
        • AST_STRING_FIELD(call_forward); /*!< Where to forward to if asked to dial on this interface */
        • call_forward 가 있는 channel 이 winner 가 될 수 있는가?
        • call_forward 가 있지만, forward 가 허용되지 않게 설정되어 있는 경우
          • numnochan++;
          • do_hang(o);
          • winner = NULL;
          • continue;
        • } else if (!ast_strlen_zero(o->chan->call_forward)) {
          • tech 설정
          • o->chan = ast_request(tech, in->nativeformats, stuff, &status);
          • if (!o->chan) {
            • o->stillgoing = 0;
            • numnochan++;
          • } else {
            • channel 설정
            • if (ast_call(o->chan, tmpchan, 0)) {
              • do_hang(o);
              • numnochan++;
            • }
          • }
          • ast_hangup(winner);
          • continue;
          • o->chan 을 forward channel 로 바꾸고, while loop 로 기회를 다시줌.
        • }
        • f = ast_read(winner);
        • if (f) {
        • } else {
        • }
      • }
    • }
    • if (winner == in) {
    • }
    • if (!*to) {
    • }
  • }
  • return peer;

wait_for_answer 2-2


* wait_for_answer
  • while (*to && !peer) {
    • for (retry = 0; retry < 2; retry++) {
      • for (o = outgoing; o; o = o->q_next) {
      • }
    • }
    • if (pos == 1 /* not found */) {
    • }
    • winner = ast_waitfor_n(watchers, pos, to);
    • for (o = start; o; o = o->call_next) {
      • if (o->stillgoing && (o->chan) && (o->chan->_state == AST_STATE_UP)) {
      • } else if (o->chan && (o->chan == winner)) {
        • if (!ast_strlen_zero(o->chan->call_forward) && !forwardsallowed) {
        • } else if (!ast_strlen_zero(o->chan->call_forward)) {
        • }
        • f = ast_read(winner);
        • if (f) {
          • if (f->frametype == AST_FRAME_CONTROL) {
            • switch (f->subclass) {
            • case AST_CONTROL_ANSWER:
              • if (!peer) {
                • peer = o;
              • }
              • break;
            • case AST_CONTROL_BUSY:
            • case AST_CONTROL_CONGESTION:
              • endtime-rna0-do_hang-ringone
              • numbusies++;
              • break;
            • case AST_CONTROL_RINGING
            • case AST_CONTROL_OFFHOOK
            • default:
            • while loop 를 통해 다시 점검함.
          • }
          • ast_frfree(f);
        • } else {
          • endtime-rna1-do_hang-ringone
          • }
        • }
      • }
    • }
    • if (winner == in) {
    • }
    • if (!*to) {
    • }
  • }
  • return peer;

* endtime-rna-do_hang-ringone
  • endtime = (long) time(NULL) - starttime;
  • rna(endtime * 1000, qe, on, membername, 0 or 1);
  • do_hang(o);
  • if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
    • if (qe->parent->timeoutrestart)
      • *to = orig;
    • ring_one(qe, outgoing, &numbusies);
  • }

* member 가 응답을 한 경우, ast_read(winner) 의 값이 AST_CONTROL_BUSY, AST_CONTROL_CONGESTION 인 경우 while loop 를 통해 기회를 다시 줌.

* member 가 응답을 한 경우, ast_read(winner) 의 값이 0 이면 while loop 를 통해 기회를 다시 줌.

wait_for_answer 3


* wait_for_answer
  • while (*to && !peer) {
    • for (retry = 0; retry < 2; retry++) {
      • for (o = outgoing; o; o = o->q_next) {
      • }
    • }
    • if (pos == 1) {
    • }
    • winner = ast_waitfor_n(watchers, pos, to);
    • for (o = start; o; o = o->call_next) {
      • if (o->stillgoing && (o->chan) && (o->chan->_state == AST_STATE_UP)) {
      • } else if (o->chan && (o->chan == winner)) {
      • }
    • }
    • if (winner == in) {
      • caller 에서 입력이 있는 경우
      • f = ast_read(in);
      • if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) {
      • caller 가 hangup 한 경우
        • *to = -1;
        • if (f)
          • ast_frfree(f);
        • return NULL;
      • }
      • if ((f->frametype == AST_FRAME_DTMF) && caller_disconnect && (f->subclass == '*')) {
      • caller 가 * 을 눌러 연결을 끊은 경우
        • *to = 0;
        • ast_frfree(f);
        • return NULL;
      • }
      • if ((f->frametype == AST_FRAME_DTMF) && valid_exit(qe, f->subclass)) {
        • caller 가 valid 한 exten 을 누른경우
        • *to = 0;
        • *digit = f->subclass;
        • ast_frfree(f);
        • return NULL;
      • }
      • ast_frfree(f);
    • }
    • if (!*to) {
      • time 이 남아 있으면 RINGNOANSWER log 하고 while loop
      • for (o = start; o; o = o->call_next)
        • rna(orig, qe, o->interface, o->member->membername, 1);
    • }
  • }
  • return peer;

* static void rna(int rnatime, struct queue_ent *qe, char *interface, char *membername, int pause)
  • ast_queue_log(qe->parent->name, qe->chan->uniqueid, membername, "RINGNOANSWER", "%d", rnatime);
  • if (qe->parent->autopause && pause) {
    • if (!set_member_paused(qe->parent->name, interface, 1)) {
    • } else {
    • }
  • }
  • return;

strategy

* strategy
  • ringall: All available agent should ring until one responds. (Default)
  • roundrobin: ring the each available agent one by one.
  • leastrecent: Call the agent who is longest idle in available agents
  • fewestcalls: ring the agent with fewest completed calls from this queue.
  • Random: Select the agent randomly from this queue.
  • rrmemory: Round-robin with memory. Starts the series in which, after the last call to the series.

* calc_metric, ring_one, find_best, store_next

Second settings for service level (default 0)
; Used for service level statistics (calls answered within service level time
; frame)
;servicelevel = 60

* static int update_queue(struct call_queue *q, struct member *member, int callcompletedinsl)
  • time(&member->lastcall);
  • member->calls++;
  • q->callscompleted++;
  • if (callcompletedinsl)
    • q->callscompletedinsl++;

* call completed in service level

QUEUE_PRIO, QUEUE_MAX_PENALTY

* priority
  • exten => 111,1,Playback(welcome)
  • exten => 111,2,Set(QUEUE_PRIO=10)
  • exten => 111,3,Queue(support)

* queue_exec
  • user_priority = pbx_builtin_getvar_helper(chan, "QUEUE_PRIO");
  • sscanf(user_priority, "%d", &prio)
  • prio = 0;
  • qe.prio = prio

* join_queue
  • if ((!inserted) && (qe->prio > cur->prio)) {
    • insert_entry(q, prev, qe, &pos);
    • inserted = 1;
  • }

* penalty: a member is not considered available if his penalty is less than QUEUE_MAX_PENALT
  • 반대인 것으로 프로그램은 되어 있는 것 같음.

* QUEUE_MAX_PENALTY: Maximum member penalty allowed to answer caller

* penalty
  • member => device/extension,penalty

* queue_exec
  • max_penalty_str = pbx_builtin_getvar_helper(chan, "QUEUE_MAX_PENALTY")
  • (sscanf(max_penalty_str, "%d", &max_penalty)
  • max_penalty = 0;
  • qe.max_penalty = max_penalty;
  • stat = get_member_status(qe.parent, qe.max_penalty);
* join_queue
  • stat = get_member_status(q, qe->max_penalty);

* wait_our_turn
  • stat = get_member_status(qe->parent, qe->max_penalty);

* get_member_status
  • if (max_penalty && (member->penalty > max_penalty)) {
    • ao2_ref(member, -1);
    • continue;
  • }

* calc_metric
  • if (qe->max_penalty && (mem->penalty > qe->max_penalty))
    • return -1;

stillgoing

struct callattempt {
        struct callattempt *q_next;
        struct callattempt *call_next;
        struct ast_channel *chan;
        char interface[256];
        int stillgoing;
        int metric;
        int oldstatus;
        time_t lastcall;
        struct member *member;
};

* try_calling
  • tmp->stillgoing = -1;
  • if (!calc_metric(qe->parent, cur, x++, qe, tmp)) {
    • tmp->q_next = outgoing;
    • outgoing = tmp;
  • ring_one(qe, outgoing, &numbusies);
  • lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies, ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT), forwardsallowed);

* return 값
  • mem->penalty > qe->max_penalty 면 -1
  • 아니면 무조건 0

* static int calc_metric(struct call_queue *q, struct member *mem, int pos, struct queue_ent *qe, struct callattempt *tmp)
  • max_penalty 보다 penalty 가 큰 member 면 return -1
    • outgoing 에 추가되지 않음.
  • QUEUE_STRATEGY_RINGALL
    • member penalty 에 1000000 을 곱한 값을 tmp metric 로 assign
  • QUEUE_STRATEGY_ROUNDROBIN
  • QUEUE_STRATEGY_RRMEMORY
    • pos 값과 member penalty 에 1000000 을 곱한 값을 더하여 tmp metric 에 assign
    • pos 값은 wrapped 와 rrpos 에 따라 계산됨.
  • QUEUE_STRATEGY_RANDOM
    • ast_random 을 1000 으로 나눈 값과 member penalty 에 1000000 을 곱한 값을 더하여 tmp metric 에 assign
  • QUEUE_STRATEGY_FEWESTCALLS
    • mem->calls 와 member penalty 에 1000000 을 곱한 값을 더하여 tmp metric 에 assign
  • QUEUE_STRATEGY_LEASTRECENT
    • call 된 적이 없으면 0 을, call 된 적이 있으면 1000000 - (time(NULL) - mem->lastcall) 값을 member penalty 에 1000000 을 곱한 값에 더하여 tmp metric 에 assign
    • lastcall 값은 ast_bridge_call 후에 attended_transfer_occurred(qe->chan) 이 0 을 return 한 경우, update_queue 를 하는 time 으로 설정됨.
    • 즉 통화가 끝난 뒤의 시간으로 생각됨.

* static int ring_one(struct queue_ent *qe, struct callattempt *outgoing, int *busies)
  • int ret = 0;
  • while (ret == 0) {
    • struct callattempt *best = find_best(outgoing);
    • if (!best) break;
    • if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) {
      • metric 이 가장 작은 값의 member 들에 대해 ring_entry 수행
      • for (cur = outgoing; cur; cur = cur->q_next) {
        • if (cur->stillgoing && !cur->chan && cur->metric <= best->metric) {
          • ret |= ring_entry(qe, cur, busies);
        • }
      • }
    • } else {
      • ret = ring_entry(qe, best, busies);
    • }
  • }
  • return ret;

* static struct callattempt *find_best(struct callattempt *outgoing)
  • metric 값이 가장 작은 member 를 return 함.
  • for (cur = outgoing; cur; cur = cur->q_next) {
    • if (cur->stillgoing && !cur->chan && (!best || cur->metric < best->metric)) {
      • best = cur;
    • }
  • }
  • return best;

* static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies)

* static void do_hang(struct callattempt *o)
  • o->stillgoing = 0;
  • ast_hangup(o->chan);
  • o->chan = NULL;

ast_hangup() hangs up the connection and also frees all the memory associated with the channel. You should never do that for inbound channels because Asterisk needs to access the channel data after it leaves your application. 

position


* join_queue
                inserted = 0;
                prev = NULL;
                cur = q->head;
                while (cur) {
                        /* We have higher priority than the current user, enter
                         * before him, after all the other users with priority
                         * higher or equal to our priority. */
                        if ((!inserted) && (qe->prio > cur->prio)) {
                                insert_entry(q, prev, qe, &pos);
                                inserted = 1;
                        }
                        cur->pos = ++pos;
                        prev = cur;
                        cur = cur->next;
                }

* leave_queue
        prev = NULL;
        for (cur = q->head; cur; cur = cur->next) {
                if (cur == qe) {
                        q->count--;

                        /* Take us out of the queue */
                        manager_event(EVENT_FLAG_CALL, "Leave",
                                "Channel: %s\r\nQueue: %s\r\nCount: %d\r\nUniqueid: %s\r\n",
                                qe->chan->name, q->name,  q->count, qe->chan->uniqueid);
                        if (option_debug)
                                ast_log(LOG_DEBUG, "Queue '%s' Leave, Channel '%s'\n", q->name, qe->chan->name );
                        /* Take us out of the queue */
                        if (prev)
                                prev->next = cur->next;
                        else
                                q->head = cur->next;
                } else {
                        /* Renumber the people after us in the queue based on a new count */
                        cur->pos = ++pos;
                        prev = cur;
                }
        }

* pos 는 초기값 0 으로 시작하여 차례대로 queue entry 에 재설정함.

* leave_queue 가 중복 시행되더라도, qe 가 없으므로 루프만 돌고 return 함.

channel register and request 1.4.24

/*! \brief Register a new telephony channel in Asterisk */
* int ast_channel_register(const struct ast_channel_tech *tech)
  • 모든 channel driver 는 이 함수를 통해 각각의 type, format 그리고 requester 함수를 channel list 에 등록한다.
  • 이미 등록되어 있으면 return -1;
  • chan = ast_calloc(1, sizeof(*chan)))
  • 필요한 memory alloc 을 못하면 return -1;
  • chan->tech = tech;
  • AST_LIST_INSERT_HEAD(&backends, chan, list);
    • channel list 에 insert
  • return 0;

/*! \brief 
        Structure to describe a channel "technology", ie a channel driver 
        See for examples:
        \arg chan_iax2.c - The Inter-Asterisk exchange protocol
        \arg chan_sip.c - The SIP channel driver
        \arg chan_zap.c - PSTN connectivity (TDM, PRI, T1/E1, FXO, FXS)

        If you develop your own channel driver, this is where you
        tell the PBX at registration of your driver what properties
        this driver supports and where different callbacks are 
        implemented.
*/
struct ast_channel_tech {
        const char * const type;
        const char * const description;
        int capabilities;               /*!< Bitmap of formats this channel can handle */
        int properties;                 /*!< Technology Properties */
        /*! \brief Requester - to set up call data structures (pvt's) */
        struct ast_channel *(* const requester)(const char *type, int format, void *data, int *cause);

        int (* const devicestate)(void *data);  /*!< Devicestate call back */

        /*! \brief Start sending a literal DTMF digit */
        int (* const send_digit_begin)(struct ast_channel *chan, char digit);
        /*! \brief Stop sending a literal DTMF digit */
        int (* const send_digit_end)(struct ast_channel *chan, char digit, unsigned int duration);

        /*! \brief Call a given phone number (address, etc), but don't
           take longer than timeout seconds to do so.  */
        int (* const call)(struct ast_channel *chan, char *addr, int timeout);
        /*! \brief Hangup (and possibly destroy) the channel */
        int (* const hangup)(struct ast_channel *chan);
        /*! \brief Answer the channel */
        int (* const answer)(struct ast_channel *chan);

        /*! \brief Read a frame, in standard format (see frame.h) */
        struct ast_frame * (* const read)(struct ast_channel *chan);
        /*! \brief Write a frame, in standard format (see frame.h) */
        int (* const write)(struct ast_channel *chan, struct ast_frame *frame);

        /*! \brief Display or transmit text */
        int (* const send_text)(struct ast_channel *chan, const char *text);
        /*! \brief Display or send an image */
        int (* const send_image)(struct ast_channel *chan, struct ast_frame *frame);

        /*! \brief Send HTML data */
        int (* const send_html)(struct ast_channel *chan, int subclass, const char *data, int len);

        /*! \brief Handle an exception, reading a frame */
        struct ast_frame * (* const exception)(struct ast_channel *chan);

        /*! \brief Bridge two channels of the same type together */
        enum ast_bridge_result (* const bridge)(struct ast_channel *c0, struct ast_channel *c1, int flags,
                                                struct ast_frame **fo, struct ast_channel **rc, int timeoutms);

        /*! \brief Indicate a particular condition (e.g. AST_CONTROL_BUSY or AST_CONTROL_RINGING or AST_CONTROL_CONGESTION */
        int (* const indicate)(struct ast_channel *c, int condition, const void *data, size_t datalen);

        /*! \brief Fix up a channel:  If a channel is consumed, this is called.  Basically update any ->owner links */
        int (* const fixup)(struct ast_channel *oldchan, struct ast_channel *newchan);

        /*! \brief Set a given option */
        int (* const setoption)(struct ast_channel *chan, int option, void *data, int datalen);
        /*! \brief Query a given option */
        int (* const queryoption)(struct ast_channel *chan, int option, void *data, int *datalen);

        /*! \brief Blind transfer other side (see app_transfer.c and ast_transfer() */
        int (* const transfer)(struct ast_channel *chan, const char *newdest);

        /*! \brief Write a frame, in standard format */
        int (* const write_video)(struct ast_channel *chan, struct ast_frame *frame);

        /*! \brief Find bridged channel */
        struct ast_channel *(* const bridged_channel)(struct ast_channel *chan, struct ast_channel *bridge);

        /*! \brief Provide additional read items for CHANNEL() dialplan function */
        int (* func_channel_read)(struct ast_channel *chan, char *function, char *data, char *buf, size_t len);
        /*! \brief Provide additional write items for CHANNEL() dialplan function */
        int (* func_channel_write)(struct ast_channel *chan, char *function, char *data, const char *value);

        /*! \brief Retrieve base channel (agent and local) */
        struct ast_channel* (* get_base_channel)(struct ast_channel *chan);

        /*! \brief Set base channel (agent and local) */
        int (* set_base_channel)(struct ast_channel *chan, struct ast_channel *base);
};

* struct ast_channel *ast_request(const char *type, int format, void *data, int *cause)
  • *cause = AST_CAUSE_NOTDEFINED;
  • channel list 에 lock 을 걸수 없으면 return NULL;
  • AST_LIST_TRAVERSE(&backends, chan, list) {
    • channel list 에서 type 이 맞는 channel 을 찾는다.
    • res = ast_translator_best_choice(&fmt, &capabilities);
      • 맞는 codec 이 있는가 찾는다.
    • 못 찾으면, *cause = AST_CAUSE_BEARERCAPABILITY_NOTAVAIL;
    • return NULL;
    • chan->tech->requester 가 없으면 return NULL;
    • if (!(c = chan->tech->requester(type, capabilities | videoformat, data, cause)))
      • return NULL;
    • return c;
  • }
  • 맞는 channel type 을 못 찾으면, *cause = AST_CAUSE_NOSUCHDRIVER;
  • return NULL;

queue.conf

* member
Member
It is possible to intervene directly in the queues.conf agents in the form of static

member => Technology Resource [, Malus]

&#8211; Also e.g. member => Zap / 2 - them (may be used several times, see queues.conf). But this can result in problems with joinempty and leavewhenempty, since these agents always be available, even if it is in fact not at their apparatus. It also has the disadvantage that always finds an agent is assigned to an apparatus and not from another apparatus from register.

We therefore prefer to use dynamic form and arrange queue support in the form:

member => Agent / AgentenNr

two agents 1001 and 1002 to:

member => Agent/1001
member => Agent/1002


 member => Agent/@1  ; a group
 member => Agent/501  ; a single agent
 member => Agent/:1,1  ; Any agent in group 1, wait for first available, but consider with penalty

The penalty parameter: You can have agents that are less likely to take calls (e.g. imagine a sales queue, you'd have the sales people with no penalty, you might have the receptionists with a penalty of 1 and us propeller heads in technical support with a penalty of 2). The technical support people would only be offered a call from the sales queue if all the sales people and the receptionists were busy. 

If you include groups in your queue definition the calls get routed in the order of the group regardless of the specified strategy. So I just have a member= line for each agent. 

Asterisk 1.4
The (very old and undocumented) ability to use BYEXTENSION for dialing
instead of ${EXTEN} has been removed.

load_module 1.4.21

* static int load_module(void)
  • if (!reload_queues())
    • return AST_MODULE_LOAD_DECLINE;

* static int reload_queues(void)
  • cfg = ast_config_load("queues.conf")
  • cat = NULL;
  • while ((cat = ast_category_browse(cfg, cat)) ) {
    • ast_config 에 있는 category list 에서 ast_category structure 를 하나씩 가져와 cat 에 assign 한다.
    • if (!strcasecmp(cat, "general")) {
      • category 가 general 이면 해당되는 변수들의 값을 해당하는 static 변수들에 assign 한다.
      • queue_persistent_members, autofill_default, montype_default
    • } else {
      • general 이 아닌 category name 은 queue name 이다.
      • queue list 를 traverse 하며 이미 존재하는지 점검한다.
      • 해당하는 queue 가 없으면, ast_caterogy structure 를 위한 memory 를 할당하고 new 값을 1 로 설정한다.
      • 해당하는 queue 가 있으면 new 를 0 으로 설정한다.
      • if (q) {
        • init_queue(q);
        • clear_queue(q);
        • mem_iter = ao2_iterator_init(q->members, 0);
        • for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
          • if (!strcasecmp(var->name, "member")) {
            • newm = create_queue_member(interface, membername, penalty, cur ? cur->paused : 0);
            • ao2_link(q->members, newm);
            • add_to_interfaces(interface);
            • q->membercount++;
          • } else {
            • queue_set_param(q, var->name, var->value, var->lineno, 1);
          • }
        • }
        • AST_LIST_INSERT_HEAD(&queues, q, list);
      • } else 면 memory alloc error 를 취해야 하는데....
    • }
  • }
  • AST_LIST_TRAVERSE_SAFE_BEGIN(&queues, q, list) {
  • }
  • return 1;

* queue.conf 파일을 변경시킨후 reload 를 하여 적용시킨다.(?)

* static int reload(void)
  • reload_queues();
  • return 0;

AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "True Call Queueing",
                .load = load_module,
                .unload = unload_module,
                .reload = reload,
               );
Lets say you have 2 SIP users defined in your sip.conf and you want to add a 3rd. You add them to the file then execute the command 'sip reload'. This re-reads your sip.conf and allows the 3rd to register.
With RealTime, all you do is add 1 new record to the table that sipusers has been bound to. No reloading necessary. 
Asterisk Real Time allows storage of users / peers and other configuration data in database such as MySQL. It is possible to use any database that has ODBC support. This way, there is no need to perform manual configuration reload for the modified entries to take effect. With the introduction of Asterisk Real Time it is possible to add/remove users or make call flow changes by entering data into appropriate database table(s). Once configured, Asterisk Real Time engine will perform database lookup(s) on a per call basis allowing for run time configuration changes.
http://books.google.co.kr/books?id=vtQxJ3oSm64C&pg=PA268&lpg=PA268&dq=asterisk+realtime&source=bl&ots=LVZcC6Io37&sig=Cx6ArjJC9sG1ORkViIEcNdQBbpY&hl=ko&ei=PihVSsmjMpWQ6AOTjtXqDw&sa=X&oi=book_result&ct=result&resnum=6


ast function

* ast_request

* ast_call

* wait_for_answer




sponsored by andamiro
sponsored by cdnetworks
sponsored by HP

Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2009-11-04 16:13:52
Processing time 0.1797 sec