Saturday, January 12, 2013

Asterisk 10 or 11 Messaging (SMS/SIP Messaging) with offline message sending

Article revision: 3
If you read and tried my post here, you would have probably got the AstSMS working. It is important to get that part working first then read the rest of this article. This article enhances that dialplan to add offline sending of SIP message (or SMS) even if a host/SIP peer is offline and they will get the message once they come back online.
HOW IT WORKS
  • It uses Asterisk .call files to requeue
  • It checks device state using the function DEVICE_STATE to determine if a call is successful or not so that it can start/stop the queuing of the .call file
  • Runs a script within dialplan using the SYSTEM application to call a bash file with certain variables that will then auto generate the .call files
IMPORTANT
  • Since the messaging feature will automatically return OK if a device status is seen as online on Asterisk, irregarless of its actual state (probably qualify has not run yet to let asterisk know its offline). So to mitigate, reduce the qualifyfreq value on extensions sip.conf or general section.
  • I’ve not tested this in large setups or production environment. So please take caution and test.
  • DEVICE_STATE must exist/work in order for you to make this work too. E,g SIP/1000 should show me state of NOT_INUSE or UNAVAILABLE etc…Read more on DEVICE_STATES .
  • When sending messages, if there’s a break character, e.g. apostrophe, it will break the script since it runs on CLI and uses BASH, so you need to clean it up through Asterisk dialplan before sending to CLI/SYSTEM app
  • If multiple offline messages are bombarded at once, it may not be received in that order.
  • I don’t regard this as a final final solution, this would just give you an idea how to get to your final solution
  • I don’t enable CDR logs, but if you have CEL they may appear, you can re-enable it by modding NoCDR accordingly and change the priority accordingly too
  • Modify the bash script to suite your operating system, mine was designed for Debian
ENVIRONMENT (that worked for me)
  • Debian 6.0.x
  • Asterisk 10/11
  • FreePBX 2.10/2.11(Beta2) – If you don’t use FBX, modify and code accordingly
  • I had working DEVICE_STATES for SIP extensions
  • PortGo softphone as sender
  • CSIPSimple softphone on Android as recipient

HOWTO

  1. Create a temp spool folder wherever your Asterisk spool folder is at, this here is default, this will be used to spool the .call file temporarily
    # mkdir /var/spool/asterisk/temp
    # chown -R asterisk:asterisk /var/spool/asterisk/temp
  2. Create or modify your astsms context, to use this new one.  Everything should start with exten on single lines.

    [astsms]
    exten => _X.,1,NoOp(SMS receiving dialplan invoked)
    exten => _X.,n,NoOp(To ${MESSAGE(to)})
    exten => _X.,n,NoOp(From ${MESSAGE(from)})
    exten => _X.,n,NoOp(Body ${MESSAGE(body)})
    exten => _X.,n,Set(ACTUALTO=${CUT(MESSAGE(to),@,1)})
    exten => _X.,n,ExecIf($["${ACTUALTO}" != "sip:${EXTEN}"]?Set(ACTUALTO=sip:${EXTEN}))
    exten => _X.,n,MessageSend(${ACTUALTO},${MESSAGE(from)})
    exten => _X.,n,NoOp(Send status is ${MESSAGE_SEND_STATUS})
    exten => _X.,n,GotoIf($["${MESSAGE_SEND_STATUS}" != "SUCCESS"]?sendfailedmsg)
    exten => _X.,n,Hangup()
    ;
    ; Handle failed messaging
    exten => _X.,n(sendfailedmsg),NoOp(Sending error to user)
    exten => _X.,n,Set(SRC=${MESSAGE(from)})
    exten => _X.,n,Set(DST=${MESSAGE(to)})
    exten => _X.,n,Set(MSG=${MESSAGE(body)})
    exten => _X.,n,Set(MESSAGE(body)="[${STRFTIME(${EPOCH},,%d%m%Y-%H:%M:%S)}] Your message to ${EXTEN} has failed. Sending when available")
    exten => _X.,n,Set(ME_1=${CUT(MESSAGE(from),<,2)})
    exten => _X.,n,Set(ACTUALFROM=${CUT(ME_1,@,1)})
    exten => _X.,n,MessageSend(${ACTUALFROM},ServiceCenter)
    exten => _X.,n,GotoIf($["${INQUEUE}" != "1"]?startq)
    exten => _X.,n,Hangup()
    ;
    exten => _X.,n(startq),NoOp(Queueing messaging for offline)
    exten => _X.,n,Set(MSGTIME=${STRFTIME(${EPOCH},,%d%m%Y-%H:%M:%S)})
    exten => _X.,n,SYSTEM(/var/lib/asterisk/agi-bin/astqueue.sh –SRC ‘${SRC}’ –DST ‘${DST}’ –MSG ‘${MSG}’)
    exten => _X.,n,Hangup()
    [app-fakeanswer]
    exten => _X.,1,NoCDR
    exten => _X.,n,Set(DESTDEV=${EXTEN})
    exten => _X.,n,Set(THISDEVSTATE=${DEVICE_STATE(SIP/${DESTDEV})})
    exten => _X.,n,GotoIf($["${THISDEVSTATE}" = "UNAVAILABLE"]?hang)
    exten => _X.,n,GotoIf($["${THISDEVSTATE}" = "UNKNOWN"]?hang)
    exten => _X.,n,Answer
    exten => _X.,n,Hangup()

    exten => _X.,n(hang),Hangup()
  3. Now, here’s the magic script called astqueue.sh, create it like this
    # nano /var/lib/asterisk/agi-bin/astqueue.sh
    Paste this below, adjust the maxretry and retryint so that your Asterisk wont blow up in smokes and act like on steroids retrying like crazy

    #!/bin/bash
    # v0.2
    # copyleft Sanjay Willie sanjayws@gmail.com
    # SCRIPT PURPOSE: GENERATE SMS OFFLINE QUEUE
    # GEN INFO: Change variables sections
    ###########################################
    #
    #VARIABLES
    maxretry=100
    retryint=30
    #
    #CONSTANTS
    ERRORCODE=0
    d_unique=`date +%s`
    d_friendly=`date +%T_%D`
    astbin=`which asterisk`
    myrandom=$[ ( $RANDOM % 1000 )  + 1 ]
    #
    function bail(){
          echo "SMS:[$ERRORCODE] $MSGOUT. Runtime:$d_friendly. UniqueCode:$d_unique"
        exit $ERRORCODE
    }
    while test -n "$1"; do
        case "$1" in
            -SRC)
                source="$2"
                shift
               ;;
            -DST)
                dest="$2"
                shift
               ;;
            -MSG)
                message="$2"
                shift
               ;;
            -TIME)
                originaltime="$2"
                shift
               ;;             
    esac
    shift
    done
    #
    #
    if [[ "$source" == "" ]]; then
        echo "ERROR: No source. Quitting."
        ERRORCODE=1
        bail
    fi
    if [[ "$dest" == "" ]]; then
        echo "ERROR: No usable destination. Quitting."
        ERRORCODE=1
        bail
    fi
    if [[ "$message" == "" ]]; then
        echo "ERROR: No message specified.Quitting."
        ERRORCODE=1
        bail
    fi
    #
    # generate call file
    mydate=`date +%d%m%y`
    logdate=`date`
    #
    # Check to see if extension exist
    destexten=`echo $dest | cut -d @ -f1 | cut -d : -f2`
    ifexist=`$astbin -rx "sip show peers" | grep -c $destexten`
    if [[ "$ifexist" == "0" ]]; then
        echo "Destination extension don't exist, exiting.."
        ERRORCODE=1
        bail
    fi

    # If that conditions pass, then we will queue,
    # you can write other conditions too to keep the sanity of the looping

    filename="$destexten-$d_unique.$myrandom.call"
    echo -e "Channel: Local/$destexten@app-fakeanswer
    CallerID: $source
    Maxretries: $maxretry
    RetryTime: $retryint
    Context: astsms
    Extension: $destexten
    Priority: 1 
    Set: MESSAGE(body)=$message
    Set: MESSAGE(to)=$dest
    Set: MESSAGE(from)=$source
    Set: INQUEUE=1 "> /var/spool/asterisk/temp/$filename
    # move files
    chown asterisk:asterisk /var/spool/asterisk/temp/$filename
    chmod 777 /var/spool/asterisk/temp/$filename
    sleep 3
    mv /var/spool/asterisk/temp/$filename /var/spool/asterisk/outgoing/
    #
    ERRORCODE=0
    bail

  4. Change permission and ownership accordingly
    # chown asterisk:asterisk /var/lib/asterisk/agi-bin/astqueue.sh
    #chmod +X /var/lib/asterisk/agi-bin/astqueue.sh
  5. Try sending a message when the device is online, then try again offline. Then go back online and you should get the message.
  6. Do modify and enhance and do share if you found something interesting or bugs..er

Its already 4am here in Malaysia, …sleep now. Good luck and enjoy your weekend.

39 comments:

Anonymous said...

you could encode message using base64 before passing to script and decode it in the call file.

JayWS said...

Yes absolutely.

Have you made it work for yourself?

Anonymous said...

Yes, I change "Set: MESSAGE(body)=$message" to "Set: BASE64MSG=$message" and add "ExecIf($["${INQUEUE}" = "1"]?Set(MESSAGE(body)=${BASE64_DECODE(${BASE64MSG})}))" before calling MessageSend.

Also I'm using openwrt, the busybox came with it don't have '$RANDOM' support unless recompiling the firmware, so I have to use "$(date +%s).$$" as unique filename. Since this script will sleep 3 seconds at the end, that makes "$(date +%s).$$" truly unique.

Midhilesh Kumar said...

Hi Sanjay Willie, i follow your blog regularly and i am trying to do the SMS in asterisk as per the current page. i have done changes as per the asterisk and i am not using free pbx. i am using VOIP mobile phones(seowonintech-SWP-1000).

Kindly advise

Midhilesh Kumar said...

i have tried the above messaging but i am getting 'CHANUNAVAIL'.

Please help me

Rconnect said...

Hi, I have working when both phones are online, but offline msgs are not working.
I tried to run astqueue.sh manually and I am getting this error:

ERROR: No source. Quitting.
SMS:[1] . Runtime:10:33:38_04/18/13. UniqueCode:1366277618

Can you help please.

thank you

JayWS said...

You can't run it manually without specifying certain things.

Show me your asterisk full log after sending an offline sms.

Asterisk SIP said...

Hi sanjay i got SMS as you said but when i am trying to do it with VoIP phones i am unable to receive in other VoIP phone. But VoIP phone can send SMS to CSipSimple.
How can i resolve this issue please advice me.

Asterisk SIP said...

Hi sanjay,
i did it with your code and i am able to send SMS between two CSipSimple clients and also from Voip phone to CSipSimple Client. But if i send message to VoIP mobile(either from CSipSimple or from VoIP phone) it was reaching asterisk server (showing accepted 202) but not delivered to the Voip phone.

Please advice me

JayWS said...

Please send ur dialplan output and paste into pastebin and share it here.

Rconnect said...

Hi Sanjay, sorry for the delay. here is my log when sending the offline message.

[2013-05-08 00:02:08] VERBOSE[1502][C-00000000] pbx.c: -- Executing [101@astsms:16] Set("Message/ast_msg_queue", "ME_1=sip:100@192.168.1.5>") in new stack
[2013-05-08 00:02:08] VERBOSE[1502][C-00000000] pbx.c: -- Executing [101@astsms:17] Set("Message/ast_msg_queue", "ACTUALFROM=sip:100") in new stack
[2013-05-08 00:02:08] VERBOSE[1502][C-00000000] pbx.c: -- Executing [101@astsms:18] MessageSend("Message/ast_msg_queue", "sip:100,ServiceCenter") in new stack
[2013-05-08 00:02:08] VERBOSE[1502][C-00000000] pbx.c: -- Executing [101@astsms:19] GotoIf("Message/ast_msg_queue", "1?startq") in new stack
[2013-05-08 00:02:08] VERBOSE[1502][C-00000000] pbx.c: -- Goto (astsms,101,21)
[2013-05-08 00:02:08] VERBOSE[1502][C-00000000] pbx.c: -- Executing [101@astsms:21] NoOp("Message/ast_msg_queue", "Queueing messaging for offline") in new stack
[2013-05-08 00:02:08] VERBOSE[1502][C-00000000] pbx.c: -- Executing [101@astsms:22] Set("Message/ast_msg_queue", "MSGTIME=08052013-00:02:08") in new stack
[2013-05-08 00:02:08] VERBOSE[1502][C-00000000] pbx.c: -- Executing [101@astsms:23] System("Message/ast_msg_queue", "/var/lib/asterisk/agi-bin/astqueue.sh .SRC ."Alex" . .DST .sip:101@192.168.1.5. .MSG .off2.") in new stack
[2013-05-08 00:02:08] VERBOSE[1502][C-00000000] pbx.c: -- Executing [101@astsms:24] Hangup("Message/ast_msg_queue", "") in new stack
[2013-05-08 00:02:08] VERBOSE[1502][C-00000000] pbx.c: == Spawn extension (astsms, 101, 24) exited non-zero on 'Message/ast_msg_queue'
[2013-05-08 00:02:45] VERBOSE[1529] chan_sip.c: -- Registered SIP '101' at 192.168.1.204:48985

JayWS said...

Hi Rconnect:
"/var/lib/asterisk/agi-bin/astqueue.sh .SRC ."Alex" . .DST .sip:101@192.168.1.5. .MSG .off2.") in new stack

Rconnect said...

sorry did not understand :)

Anonymous said...

Online message running on the xlite to another xlite working. Im using Deb6, Ast11 and Freepbx2.1. The offline message are not working. Run the astqueue manually. ERROR: No source. Quitting.
SMS:[1] . Runtime:17:16:46_05/15/13. UniqueCode:1368609406
Please advice me

catwong said...

online message works on the xlite to another xite. My use Deb6, Ast11 and Freepbx2.x. offline msgs are not working.
Running manually astqueue ERROR: No source. Quitting.
SMS:[1] . Runtime:17:16:46_05/15/13. UniqueCode:1368609406, Please advice me

Anonymous said...

Hi Catwong,

I had the same issue. You can not copy/paste the source code from this blog just like that. There are some strange characters, missing EOL and that's why the dialplan as well as the .sh file is failing to work.

Would be better if Sanjay would attach the source code in a txt file as well.

Anonymous said...

good work i understand the code..
but how i can use this in my dialplan should i call this macro astsms below the line answer() in my dialplan e.g:

exten => s,1,Dial(SIP/${EXTEN})
exten => s,2,Answer
exten => s,3,Macro(astsms)

that works or i have to do something else or just put all this code same as given and that would be enough.
plz do reply thanx...

JayWS said...

Try it as is.

Siptai said...

Hi Sanjay
Really nice article and great commendation from me.
I wanted to know if there's a way to get the messages use a database instead and also, the offline messages as well.
I know there're some products that do that like kamailio, but seems to complicated and i'll prefer the asterisk SMS solution if one can log SMS in database instead.

Unknown said...

I was searching for a hook/trigger in asterisk for this but with no luck.
Is there a way to react on state change or on sip register / unregister?!
Is there a way for asterisk to react on state change or on sip register / unregister?!
For example, is there a way for asterisk to run a custom part of dialplan, a hint or something on device change or on sip register / unregister.

Shafique Wains said...

Hi Sanjay,

Thanx for the code, :)
is there a way to get message delivery report or seen status when the message actually delivered on the other end device?
using asterisk 10, freepbx 2, Bria 3 on mobiles and Xlite-4 on PCs,

and what about group sms? is that possible and how?

Your kind words are needed.

Shafique Wains said...

Hi Sanjay,

Thanks for the code :).

Is it possible to group message and to get message delivery report (when it actually delivered to device)

we are using Bria on phones, and Xlite-4 on PCs,

Your advice is needed.

Unknown said...

Hi,
I found this solution a great thing and I want to adapt it for running on Alpine Linux platform which uses busybox binary. When calls the script I get the error /bin/bash: can't open sip:extension@iasteriskipaddress: no such file. I tried to adapt the script myself running it separately but is above my knowledge.
Please, if is there someone who can help me with script adapting I will be more than happy.
Thank you!

Mikeisfly said...

Could you please post instruction on how to get the DEVICE_STATE working in FreePBX v 5.211.65-12 Asterisk v11.9? I have followed your instructions but the offline messaging is not working.

I have set Enable Custom Device States to "True".

below is the output of asterisk -vvvvvvvvr when I send a messaage to a offline client. I have edited a little for security:

-- Executing [5552002@astsms:1] NoOp("Message/ast_msg_queue", "SMS receiving dialplan invoked") in new stack
-- Executing [5552002@astsms:2] NoOp("Message/ast_msg_queue", "To sip:5552002@freepbx.hidden.for.secuirty.com") in new stack
-- Executing [5552002@astsms:3] NoOp("Message/ast_msg_queue", "From "Michael" ") in new stack
-- Executing [5552002@astsms:4] NoOp("Message/ast_msg_queue", "Body This is a test message to a offline client.") in new stack
-- Executing [5552002@astsms:5] Set("Message/ast_msg_queue", "ACTUALTO=sip:5552002") in new stack
-- Executing [5552002@astsms:6] ExecIf("Message/ast_msg_queue", "0?Set(ACTUALTO=sip:5552002)") in new stack
-- Executing [5552002@astsms:7] MessageSend("Message/ast_msg_queue", "sip:5552002,"Michael" ") in new stack
-- Executing [5552002@astsms:8] NoOp("Message/ast_msg_queue", "Send status is FAILURE") in new stack
-- Executing [5552002@astsms:9] GotoIf("Message/ast_msg_queue", "1?sendfailedmsg") in new stack
-- Goto (astsms,5552002,11)
-- Executing [5552002@astsms:11] NoOp("Message/ast_msg_queue", "Sending error to user") in new stack
-- Executing [5552002@astsms:12] Set("Message/ast_msg_queue", "SRC="Michael A. Gates" ") in new stack
-- Executing [5552002@astsms:13] Set("Message/ast_msg_queue", "DST=sip:5552002@freepbx.hidden.for.secuirty.com") in new stack
-- Executing [5552002@astsms:14] Set("Message/ast_msg_queue", "MSG=This is a test message to a offline client.") in new stack
-- Executing [5552002@astsms:15] Set("Message/ast_msg_queue", "MESSAGE(body)="[18052014-23:26:42] Your message to 5552002 has failed. Sending when available"") in new stack
-- Executing [5552002@astsms:16] Set("Message/ast_msg_queue", "ME_1=sip:5552000@freepbx.hidden.for.secuirty.com>") in new stack
-- Executing [5552002@astsms:17] Set("Message/ast_msg_queue", "ACTUALFROM=sip:5552000") in new stack
-- Executing [5552002@astsms:18] MessageSend("Message/ast_msg_queue", "sip:5552000,ServiceCenter") in new stack
-- Executing [5552002@astsms:19] GotoIf("Message/ast_msg_queue", "1?startq") in new stack
-- Goto (astsms,5552002,21)
-- Executing [5552002@astsms:21] NoOp("Message/ast_msg_queue", "Queueing messaging for offline") in new stack
-- Executing [5552002@astsms:22] Set("Message/ast_msg_queue", "MSGTIME=18052014-23:26:42") in new stack
-- Executing [5552002@astsms:23] System("Message/ast_msg_queue", "/var/lib/asterisk/agi-bin/astqueue.sh –SRC ‘"Michael" ’ –DST ‘sip:5552002@freepbx.hidden.for.secuirty.com’ –MSG ‘This is a test message to a offline client.’") in new stack
-- Executing [5552002@astsms:24] Hangup("Message/ast_msg_queue", "") in new stack
== Spawn extension (astsms, 5552002, 24) exited non-zero on 'Message/ast_msg_queue'

JayWS said...

pls post the output of

# asterisk -rx "core show states"

mikeisfly said...

What I get is No such command 'core show states' (type 'core show help core show states' for other possible commands)

Also seems like the file .call file is not being generated when I check the temp and the outgoing directories.

I do have a DEVICE_STATE function for what it's worth.

Unknown said...

The output of core show hint 5552000
5552000@ext-local : SIP/5552000&Custom:D State:Idle Watchers 0
1 hint matching extension 5552000


The output of core show hint 5552002 is:

core show hint 5552002
5552002@ext-local : SIP/5552002&Custom:D State:Unavailable Watchers 0
1 hint matching extension 5552002


In my test I tried to send a message from 5552000 to 5552002. When they are both online no issue, when 5552002 is offline the message never sends and I'm not sure if it is queuing.

Hope this helps, I would really like to get this working.

Paul Walsh said...

This works great. There are couple of typos when copying the code (hyphens and EOL is the custom extension file) but once you figure out those it works as described.

I added one enhancement to make sure messages are delivered in order. Only one message is queued at a time.

Well done Sanjay

See here for the details on the change I made

http://www.irishvoip.com/w/knowledgebase.php?action=displayarticle&id=13

Paul Walsh said...

This works great. There are couple of typos when copying the code (hyphens and EOL is the custom extension file) but once you figure out those it works as described.

I added one enhancement to make sure messages are delivered in order. Only one message is queued at a time.

Well done Sanjay

See here for the details on the change I made

http://www.irishvoip.com/w/knowledgebase.php?action=displayarticle&id=13

Unknown said...

I just want to say that my knowledge of Linux and Asterisk is pretty low. With that being said I'm looking at your astqueue.sh script to see how the .call file is generated and I'm not seeing how the file is actually written to disk?

This line:
Set: INQUEUE=1 "> /var/spool/asterisk/temp/$filename

not sure if I'm off base here? Should there be a " and the end of this line?

Thanks for any help.

Unknown said...

I have fixed the issues for freepbx 5.211 with the centos 6.5.

There were a couple of problem like the [app-fakeanswer] context was missing. I have also hacked the astqueue.sh script to not leak usage about a user when he/she is not online. If you are using Bria for Android if you try to text a non existent number you will get a message in the same windows that the extension doesn't exist. If you are using Zoiper you will get a message from a unknown user. Don't know why.

You can get astqueue.sh from http://pastebin.com/9frJ8aBS

you can get extensions_custom.conf from http://pastebin.com/RhM4kSwm

JayWS said...

Thank you for sharing with the community! you're a rock star :-) and im glad this thing kinda works....

Unknown said...

Taking the code from Paul Walsh I have gotten the messages to queue in order and wanted to post the code a directions:

1. create a folder /var/spool/asterisk/hold
2. change owner using the command:
chown -R asterisk:asterisk /var/spool/asterisk/hold
3. get astqueue.sh from my pastebin
http://http://pastebin.com/wdAi0AA7
4. get astcron.sh from my pastebin
http://pastebin.com/DviuuquB

5. put them in your /var/lib/asterisk/agi-bin folder
I did that by using a ftp server, the "get" command is very helpful otherwise you can just cut a paste into nano
6. change permission to 775
chmod 775 /var/lib/asterisk/agi-bin/ast*.sh
7. use nano to add the following lines to your crontab:

nano /etc/crontab

#Below Handles sending of queued messages in order
* * * * * asterisk /var/lib/asterisk/agi-bin/astcron.sh

That's it your all done! Thanks everyone for helping me accomplish this.

Barry Cisna said...

Hello All,

Thanks to Sanjay and rest who contributed to this SMS - asterisk setup.
Question: I am a newbie to texting/SMSing. I have a default Android 4.4 phone,with no data plan on it.
I have went through the steps described here.
Now how do I send a text message with the default Messaging app that is built into Android. I also have CSipSimple installed as my voip program along with an GoogleVoice account which does work.
Can someone give me ,,,a
1.
2.
3.
steps to make this happen?

Thank You

Unknown said...

To use the SMS system here you would need to have your own Asterisk based PBX system. If you have that then you will need a client like Zoiper or Bria for android. Once you have all that and you configure the PBX correctly you will only be able to text numbers connected to your PBX. If you want to text outside of your PBX then you will need a SMS gateway provider. This article is about configuring your own PBX for SMS on your local extensions.

Anonymous said...

Everything works fine except the case statement within astqueue.sh
Although it looks fine the problem exists.

That's why people receive a message "ERROR: No source. Quitting."

I use Debian 7.6 minimum install

setting source, dest, message to $2, $4 and $6 respectively makes it work. Unfortunately I'm not able to identify the problem.

Unknown said...

Thanks so much for this man. This really gave me direction and have written an altered version to meet my sms needs.

Thanks so much

Anonymous said...

Thank you guys for your work. I also add line "NoCDR" at the top because asterisk, after I send the message, records the call when the recepient comes back online.

[astsms]
exten => _X.,1,NoCDR
exten => _X.,n,NoOp(SMS receiving dialplan invoked)
exten => _X.,n,NoOp(To ${MESSAGE(to)})
...
..
.

Warnerhill said...

I Find it very informative about sms marketing.Thanks for sharing such great information. hope you keep sharing such kind of information software to send bulk SMS