Rekord
文章38
标签23
分类4

一言

文章分类

短信和语音推送

短信和语音推送

短信推送

方案1

创建一个新的sms-c网元(c表示control)。

该网元的功能主要有:

  1. 与sms建立diameter连接。
  2. 能够正常向sms发送ofr信令,并接收ofa信令。
  3. 接收并处理命令行输入(由用户给出被推送目标信息)

成品:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 进入容器内部
docker exec -it sms-c env LANG=C.UTF-8 bash
# 启动sms接管程序
./etc/sms-c.sh
# 等待diameter连接成功后即可开始推送短信
> help
Usage: called data caller sc-addr
called          Required argument
data            Required argument
caller          Optional argument
sc-addr         Optional argument
help            Show this help message
Ctrl+D          Quit the program

方案2:

  1. 增加一条用于发送短信的extension
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
exten => 203,1,NoOp(SMS Sending Extension)

 same => n,Set(MESSAGE_DATA(Content-Type)=application/vnd.3gpp.sms)

 same => n,AGI(send_sms.py,1234,1234,hello)

 same => n,Set(MESSAGE(body)=hello)

 same => n,Set(MESSAGE(from)=sip:0000f30A0A01@192.168.70.1)

 same => n,Set(MESSAGE(to)=sip:0000f30B0B02@192.168.70.1)

 same => n,MessageSend(pjsip:0000f30A0A01@192.168.70.1, sip:0000f30B0B02@192.168.70.1)

 same => n,Hangup()
  1. 使用ari触发这条extension。

==Warning==:

方案2的宏观流程确实可以分为以上两步,但是第一步中想要正确的设置MESSAGE(body)非常不容易。

如果不使用MESSAGE_DATA设置sip头字段,则body发送的是纯文本,理论上手机肯定不吃这一套。

如果设置了body类型为application/vnd.3gpp.sms,那么body需要设置为rp-data(deliver)的utf8字符串形式。

Asterisk并没有对于gsm编码的支持,但是我们可以使用agi脚本通过传参交由脚本去完成短信编码工作(通常是shell脚本或python脚本)。

最后再将该结果传递进环境变量,交由asterisk并将这些utf8字符再转为字节流从网络发送出去。


但是短信编解码只是一个小众领域,所以很难找到好用完善的库。另外很多库对中文编码也不感兴趣。

综上,即使使用agi脚本调用外部程序也需要满足几个条件:

  1. 库足够完善
  2. 能够生成最后编码结果(通常是字节数组形式)的utf8字符串形式。

最终简单修改了smspdu库,使之能够支持中文编码。

==Error==:

事实上外部程序中最终结果只能以字节数组的形式输出,因为最终的字节数组通常无法被编码为ascii或utf-8格式(其他格式不考虑可行性)。

也就意味着无论如何都无法将结果传递给MESSAGE(body),所以这种方案是不可行的。

语音推送

命令示例:

asterisk内部命令:

channel originate PJSIP/0000f30A0A01 extension 200@sets

Ari请求:

http://192.168.70.128:8088/ari/channels?endpoint=PJSIP%2F0000f30A0A01&extension=200&context=sets&timeout=30&api_key=asterisk:passwd

如果要使用变量,就在请求体中加上:

1
2
3
4
5
{
  "variables": {
    "self_sound": "hello-world"
  }
}

如果asterisk报错Strict RTP learning…

则需要修改rtp.conf文件,将其中的strictrtp设为no

程序

使用aricpp库写一个小程序,其至少应包含以下功能:

  1. 与asterisk建立连接
  2. 能够发送ari命令
  3. 接收命令行输入(由用户给出被推送目标信息)

成品:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include <boost/program_options.hpp>
#include <string>
#include <thread>
#include "../include/aricpp/client.h"
#include "../include/aricpp/urlencode.h"

using namespace std;
using namespace aricpp;

int main( int argc, char* argv[] )
{
    try
    {
        string host = "172.16.30.49";
        string port = "8088";
        string username = "asterisk";
        string password = "passwd";
        string application = "zarniwoop";


        namespace po = boost::program_options;
        po::options_description desc("Allowed options");
        desc.add_options()
            ("help,h", "produce help message")
            ("version,V", "print version string")

            ("host,H", po::value(&host), "ip address of the ARI server [localhost]")
            ("port,P", po::value(&port), "port of the ARI server [8088]")
            ("username,u", po::value(&username), "username of the ARI account on the server [asterisk]")
            ("password,p", po::value(&password), "password of the ARI account on the server [asterisk]")
            ("application,a", po::value(&application), "stasis application to use [attendant]")
        ;

        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, desc), vm);
        po::notify(vm);

        if (vm.count("help"))
        {
            std::cout << desc << "\n";
            return 0;
        }

        if (vm.count("version"))
        {
            cout << "This is push application v1.0.\n";
            return 0;
        }

#if BOOST_VERSION < 106600
        using IoContext = boost::asio::io_service;
#else
        using IoContext = boost::asio::io_context;
#endif
        IoContext ios;

        Client client( ios, host, port, username, password, application );
        client.Connect( [&](boost::system::error_code e){
            if (e) cerr << "Error connecting: " << e.message() << '\n';
            else {
                cout << "Connected" << '\n';
            }
        });

        auto inputReader = [&]()
        {
            string line;
            while (true)
            {
                cout << "> Enter called and the voice you need to push:\n";
                getline( std::cin, line );

                std::istringstream iss(line);
                std::string called, voice, caller;

                if (!(iss >> called >> voice)) {
                    std::cerr << "Error: Not enough input." << std::endl;
                    continue;
                }

                auto url = "/ari/channels?endpoint=PJSIP%2F"+called+"&extension=2839&context=ims&timeout=30&api_key=asterisk:passwd";
                if (iss >> caller) {
                    url += "&callerId=" + caller;
                }
                if (iss.rdbuf()->in_avail() != 0) {
                    std::cerr << "Warning: Extra input detected." << std::endl;
                }

                auto sendRequest =
                    [&client,url,voice]()
                    {
                        client.RawCmd(Method::post, url, [](auto,auto,auto,auto){}, "{\"variables\": {\"push_sound\": \""+voice+"\"}}");
                    };

#if BOOST_VERSION < 106600
                ios.post(sendRequest);
#else
                boost::asio::post(ios.get_executor(), sendRequest);
#endif
            }

            cout << "Exiting application\n";
            ios.stop();
        };
        thread readerThread( inputReader );

        ios.run();

        readerThread.join();

    }
    catch ( const exception& e )
    {
        cerr << "Exception in app: " << e.what() << ". Aborting\n";
        return -1;
    }
    return 0;
}
本文作者:Rekord
本文链接:https://sxrekord.com/4g-push/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可