Reliable method to get realtime device state?

Hello guys,

What is the best method to query REALTIME and ACTUAL device state please? Unfortunately, DEVICE_STATE() function is not reliable as it doesn’t seem to fetch the actual state of the device in realtime. I was wondering if there was any better method to query remote peer state other than sending a push notification. We’re using SIP.

Any suggestions?

Don’t duplicate threads, wait for a response we are in a community forum and we can answer in due course.

I Use DEVICE_STATE and it works as expected when you say realtime you mean SIP REALTIME or you are referring to get the information at the moment?

Do you have callcounter enabled and also hints in your sip.conf and dialplan?

Hello @navaismo,

Actually, I need to get the present state of the peer phone in reality, now. I have callcounter enabled but not hints. I will look it up. Any examples please?

Sorry for duplicating the issue but I thought this question was more direct. Should I delete the other thread?

I’ve already answered in the other thread - but configuration plays a big part in how accurate the option is and without seeing your configuration we can’t know if you’ve got it set up to provide the information you need.

OK. Here’s my sip.conf

[general]

context=sip1

allowguest=no
alwaysauthreject=yes

disallow=all

allow=speex
allow=ilbc
allow=g722
allow=h264

videosupport=yes

srvlookup=no
udpbindaddr=0.0.0.0
tcpenable=no
canreinvite=no
dtmfmode=auto
nat=force_rport,comedia
directrtpsetup=yes
callevents=yes
callcounter=yes

rtcachefriends=yes
notifyhold = yes
progressinband = yes

And we’re switched to realtime database mode. Record for each user in SIP table:

host = dynamic
qualify = no
hassip = yes
type = friend
context = sip1
transport = udp

In extensions table, we have one record for each extension which just calls the Dial application with SIP/extension as unique argument.

What other settings would you need? Thank you.

You haven’t enabled “qualify” so chan_sip will not send a message to the device periodically to determine if it is reachable or not. That is why you are seeing what you are seeing, Asterisk can’t know the device is unreachable. Additionally it may not work as you need even with realtime caching (something has to request the device in order for it to be loaded and cached). You’ll need to enable qualify and see if it works as you need.

2 Likes

Thank you jcolp.

I set qualify to yes for both test peers. I noticed two things:

  1. PeerB is disconnected from the Internet. PeerA calls PeerB. DEVICE_STATE(SIP/PeerB) (still) returns RINGING. It is as if the fact that any peer tries to make PeerB rings sets B’s state to ringing (when it is unavailable in fact).

  2. PeerB is registered but not in use. I disconnect it from the internet and call DEVICE_STATE(SIP/PeerB) repeatedly. The function returns NOT_INUSE during the first 45 seconds approx. after B’s disconnection before returning UNAVAILABLE eventually.

How do I get it to return UNAVAILABLE earlier? It seems like a timeout period must elapse before the state is updated. Looking in sip.conf, I see settings such as qualifyfreq and keepalive but I am not sure if they are related to my issue.

What is the setting for this period value please?

The qualify option I mentioned is that option. The frequency is how often to check. The lower the frequency the more traffic it will generate. The higher the frequency the less traffic but it will take longer to determine a device is unreachable. My comment about realtime also stands - it may not work as you need because of realtime. Using the sip.conf file would guarantee it would work.

2 Likes

I set qualify to 1000, that’s 1 second I suppose, and I did a first test: It took Asterisk nearly 10 seconds to report that PeerB was unavailable. In a second test, it took 60 seconds approx. My question is where/how is this delay determined? and why is realtime an issue? In fact, we chose realtime because we found it easier to manage users, extensions and dialplan remotely by accessing a database instead of manipulating conf files. Do you think we should switch to file mode if we want to solve this issue?

The options in sip.conf control how often and how many at a time are qualified. Once a message is sent it gets retransmitted a few times and if no response occurs then it is marked as unreachable, this is according to the SIP specification for request retransmission. Realtime is an issue because qualify inherently has state - you have to keep track of who is being qualified, when it should occur, etc. With realtime caching this is lessened but the device still has to be used in some way to bring it into memory.

If your goal is to INSTANTLY know that a device is offline then SIP isn’t really made for that out of the box. Using TCP or TLS can lessen this but you’d still need to send a constant stream of traffic to ensure the connection has remained up.

2 Likes

Thanks for the info. One more question please. What’s the difference between ‘qualify’ and ‘qualifyfreq’ ? Aren’t they the same?

;qualify=1000                    ; Consider it down if it's 1 second to reply
                                 ; Helps with NAT session
                                 ; qualify=yes uses default value
;qualifyfreq=60                  ; Qualification: How often to check for the
                                 ; host to be up in seconds
2 Likes

Can’t the client ask Asterisk to send an OPTIONS message to remote peer on demand? I am new to SIP and just wondering if this is possible… but then how would the client retrieve the remote peer state? My understanding is that calling DEVICE_STATE() after sending OPTIONS to peer would return accurate state result…?

It’s as accurate as can be at that point in time.

Also, you would have to wait for several seconds after sending the OPTIONS before the response for the last retransmission was timed out.

2 Likes

Thank you for your answers.

Is there any command or way to let Asterisk send the OPTIONS message to a peer immediately (on demand)? Via AMI perhaps??

Here’s what I use to send the device states to a database using AMI (obviously change the “password”…

manager.conf

[devicestate]
secret = password
read = call

eventfilter=Event: DeviceStateChange
eventfilter=Event: DialBegin
eventfilter=Event: UnParkedCall

PHP program running in init.

#!/usr/bin/php -q
<?php
ini_set("default_socket_timeout", -1);

include('/opt/asterisk/includes.php');

$web = db_connect_web();

$socket = fsockopen("localhost","5038");
fputs($socket, "Action: Login\r\n");
fputs($socket, "Username: devicestate\r\n");
fputs($socket, "Secret: password\r\n\r\n");

$event = "";
while($ret = fgets($socket)){		
	if(substr($ret,0,6) == "Event:"){
		$e = explode(':', $ret);
		$event = trim($e[1]);
	}
	
	if($event == "DeviceStateChange"){
		$data = explode(':', $ret);
		
		if($data[0] == "Timestamp"){
			$ts = floor(trim($data[1]));
		}
	
		if($data[0] == "Device" && substr(trim($data[1]),0,3) == 'SIP'){
			$d = explode('/', trim($data[1]));
			$dev = trim($d[1]);
			$device = "";
			
			if(is_numeric($dev)){
				$device = $dev;
			}
		}
	
		if($data[0] == "State" && $device != ""){
			$state = trim($data[1]);
			
			if($state == "NOT_INUSE"){
				//Clear CID fields and update presence state
				$sql = "update asterisk.web_presence set state='$state',cidnum = NULL, cidname = NULL, inorout = NULL, callstart = NULL where ext='$device'";
				mysql_query($sql);
			}else{
				//Update presence state
				$sql = "update asterisk.web_presence set state='$state' where ext='$device'";
				mysql_query($sql);
			}
			
			$event = "";
			$device = "";
		}	
	}
	
	if($event == "DialBegin"){
		$data = explode(':', $ret);
		
		if($data[0] == "Timestamp"){
			$ts = floor(trim($data[1]));
		}
	
		if($data[0] == "Channel"){
			$c = explode('/',trim($data[1]));
			$c2 = explode('-', trim($c[1]));
			$channel = trim($c2[0]);
		}
	
		if($data[0] == "CallerIDNum"){
			$cidnum = trim($data[1]);
		}
	
		if($data[0] == "CallerIDName"){
			$cidname = trim($data[1]);
		}
	
		if($data[0] == "DialString"){
			if(substr(trim($data[1]),0,3) == 'SIP' || is_numeric(trim($data[1]))){				
				if(is_numeric(trim($data[1]))){
					$exten = trim($data[1]);
				}else{
					$e = explode('/', trim($data[1]));
					$exten = trim($e[1]);
				}
				
				//Update inbound presence call
				$sql = "update asterisk.web_presence set cidnum = '$cidnum', cidname = '$cidname', inorout='I', callstart='$ts' where ext='$exten' and cidnum is null";
				mysql_query($sql);	
				
				$sql = "update asterisk.web_presence set cidnum = '$exten', inorout='O', callstart='$ts' where ext='$channel' and cidnum is null";
				mysql_query($sql);			
			}else{
				$e = explode('@', trim($data[1]));
				$dialed = trim($e[0]);
				
				if($channel != 'gateway'){					
					//Update outbound presence call
					$sql = "update asterisk.web_presence set cidnum = '$dialed', inorout='O', callstart='$ts' where ext='$channel'";
					mysql_query($sql);	
				}				
			}

			$event = "";
			$exten = "";
		}	
	}
		
	if($event == "UnParkedCall"){
		$data = explode(':', $ret);
		
		if($data[0] == "Timestamp"){
			$ts = floor(trim($data[1]));
		}
	
		if($data[0] == "RetrieverChannel"){
			$c = explode('/',trim($data[1]));
			$c2 = explode('-', trim($c[1]));
			$channel = trim($c2[0]);
		}
		
		if($data[0] == "ParkeeCallerIDNum"){
			$cidnum = trim($data[1]);
		}
		
		if($data[0] == "ParkeeCallerIDName"){
			$cidname = trim($data[1]);
		}		
	
		if($data[0] == "ParkingSpace"){
			$dialed = trim($data[1]);
			
			$pickup = "$cidnum ($dialed)";
			
			//Update outbound presence call
			$sql = "update asterisk.web_presence set cidnum = '$pickup', cidname='$cidname', inorout='O', state='INUSE', callstart='$ts' where ext='$channel'";
			mysql_query($sql);	
			
			$event = "";	
			$channel = "";		
		}	
	}		
}

# fputs($socket, "Action: Logoff\r\n\r\n");
mysql_close($web);

sleep(5);

exit;	
?>
1 Like