daniele77 / aricpp Goto Github PK
View Code? Open in Web Editor NEWAsterisk ARI interface bindings for modern C++
License: Boost Software License 1.0
Asterisk ARI interface bindings for modern C++
License: Boost Software License 1.0
Hello.
First and foremost, I'm not sure if this is the correct way to control a recording. I create a new recording when a channel enters the Stasis application. Then, I want to be able to control said recording (i.e. pause it). I decided to add a new method to the Recording class in recording.hpp
:
Proxy& Pause(std::string id)
{
return Proxy::Command(Method::post, "/ari/recordings/live/" + id + "/pause", client);
}
This is the code for the Stasis application:
#include <iostream>
#include <boost/asio/io_service.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <aricpp/client.h>
#include <aricpp/jsontree.h>
#include <aricpp/arimodel.h>
#include <aricpp/channel.h>
int main()
{
boost::asio::io_service ios;
aricpp::Client client(ios, "localhost", "8088", "asterisk", "asterisk", "Announcement");
aricpp::AriModel model(client);
std::unique_ptr<aricpp::Bridge> bridge;
aricpp::Recording recordingControl = aricpp::Recording(); // Used to control a recording
std::string recordingId;
client.Connect([&](boost::system::error_code e) {
if (e) std::cerr << "Error connecting: " << e.message() << '\n';
else std::cout << "Connected to Asterisk ARI" << "\n";
model.CreateBridge(
[&bridge](std::unique_ptr<aricpp::Bridge> newBridge)
{
bridge = move(newBridge);
std::cout << "Bridge created" << std::endl;
},
aricpp::Bridge::Type::mixing);
model.OnStasisStarted([&](std::shared_ptr<aricpp::Channel> channel, bool external) {
std::string channelName = (std::string)channel->Name();
bridge->Add(*channel, false, aricpp::Bridge::Role::participant);
std::cout << "CHANNEL JOINED AND ADDED TO BRIDGE: " << channelName << std::endl;
if (!channelName.rfind("Recorder", 0) == 0) {
boost::uuids::uuid uuid = boost::uuids::random_generator()();
recordingId = boost::uuids::to_string(uuid);
std::cout << "NEW RECORDING STARTED: " << uuid << std::endl;
bridge->Play("sound:tt-monkeys");
bridge->Record(recordingId, "wav");
}
});
client.OnEvent("ChannelDtmfReceived", [&](const aricpp::JsonTree& event) {
auto digit = aricpp::Get<std::string>(event, { "digit" });
if (digit == "*") {
std::cout << "PAUSING RECORDING " << recordingId << std::endl;
recordingControl.Pause(recordingId);
}
});
});
ios.run();
}
When trying to Pause
the recording, by calling recordingControl.Pause(recordingId)
in the snippet above, this exception is raised:
For reference:
GET
method to list all live recordings):Any idea why this is happening? Am I using the Recording functionality wrong here?
The aricpp::Client constructor creates a Websocket connection to get events from the Asterisk server. This is meant to be a long term connection to the server; however, in production environments the websocket can get disconnected. From that point on, no messages will be received from Asterisk.
This issue is related to the following discussion:
#31
Hi Experts,
I am completely new to the asterisk and i want to play with asterisk with my customized application.
I have gone through the wiki.asterisk.org and got knowledge on dial plans, extensions, channels and bridges.
I built asterisk 13 on pc. From here i stuck with few points.
Thanks in advance
Add a coroutine interface, to simplify the async programming, emulating a sequential one.
The current directory setup forces us to add the repositories top level directory to the include search dirs. (for making #include <aricpp/channel.h>
working) This makes aricpp's version file getting included by #include <version>
on case insensitive file systems (like the default file systems in macOS or Windows). However, this conflicts with clang's libc++ standard library which uses exactly this include for obtaining its own version information.
My proposal is to move the aricpp/aricpp folder to aricpp/include/aricpp. This way, we can add aricpp/include to our compile flags and only add the aricpp headers to our search paths.
Any thoughts or comments? Otherwise I'll go ahead and prepare a PR for this.
Currently, Client
can receive events from just one stasis application (first problem?) but one must specify the application as a parameter of Channel::Call
(second problem?)
Check the authentication mechanism in HttpClient
class.
At the first request, the client directly submit the authorization.
Maybe it should first issue a simple request then, once receiving a negative response, it should send a new request with the authorization field.
Currently, the Error
enum distinguish just the following cases:
It would be useful to add all the various errors that can occour during ARI requests.
The main issue is that each ARI request has its own error, mapped on the same http status.
Hello,
Is it possible that I can find some sample json files to show the input and output from the channels?
Thank you
In environments where security is required, we need to be able to connect to Asterisk using secure web socket (wss) and send/receive POST requests over https.
The application should provide the certificates to use to connect to the asterisk server, pass them to the client so it can be used by the httpclient and the websocket classes
Some implementation classes are currently under the main namespace aricpp
and visible to the user.
Move them to a aricpp::detail
namespace and a relative subfolder.
Originally posted by DVPOM November 7, 2023
Hello Daniele,
Sorry to bother you.
I wanted to try ARICPP to connect to asterisk .
But I see below error
[root] ~/ari_112/aricpp-1.1.2/build > make all
[ 6%] Building CXX object examples/CMakeFiles/query.dir/query.cpp.o
In file included from /root/ari_112/aricpp-1.1.2/examples/../include/aricpp/client.h:42,
from /root/ari_112/aricpp-1.1.2/examples/query.cpp:36:
/root/ari_112/aricpp-1.1.2/examples/../include/aricpp/httpclient.h: In lambda function:
/root/ari_112/aricpp-1.1.2/examples/../include/aricpp/httpclient.h:220:62: error: ‘using string_view = boost::core::string_view’ {aka ‘class boost::core::basic_string_view’} has no member named ‘to_string’
220 | CallBack(e, resp.result(), resp.reason().to_string(), resp.body());
| ^~~~~~~~~
make[2]: *** [examples/CMakeFiles/query.dir/build.make:76: examples/CMakeFiles/query.dir/query.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:112: examples/CMakeFiles/query.dir/all] Error 2
make: *** [Makefile:136: all] Error 2
[root] ~/ari_112/aricpp-1.1.2/build > g++ --version
g++ (GCC) 10.2.1 20210130 (Red Hat 10.2.1-11)
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
build_examples.txt
Add the "holding" bridge type that allows adding "participant" or "announcer" channels
Currently, Channel
destructor issues a hangup to asterisk.
Is it correct/useful?
I can start bridge and channel recordings, but only bridge recordings can be stopped.
On channels the stop command would succeed but then not actually work.
After this a channel play would also fail.
I found one missing character, after digging through Asterisk debug logs, that actually fixes everything.
My company prefers I not submit patches but I could not leave you hanging.
Beside you probably want to consider if anything else needs to be done.
In your channel.h line 360 change Recording recording(name, client); to Recording recording(_name, client);
aricpp-1.1.1 and Asterisk 18.9.0
In the high_level_dial and low_level_dial sample, the order of the operations should be modified to first add the channels to the bridge and then dial the outgoing channel. In this way the caller can hear the media before the called answers.
See http://blogs.asterisk.org/2016/08/24/asterisk-14-ari-create-bridge-dial/
The aricpp::Channel
class is not default-copyable because of the constant id
member. Clang warns about this while gcc seems to ignore the const
.
Clang error message is (with -Werror
):
<path redacted>/aricpp/channel.h:102:14: error: explicitly defaulted move assignment operator is implicitly deleted [-Werror,-Wdefaulted-function-deleted]
Channel& operator=(Channel&& rhs) = default;
^
<path redacted>/aricpp/channel.h:479:23: note: move assignment operator of 'Channel' is implicitly deleted because field 'id' has no move assignment operator
const std::string id;
^
The fix is pretty simple. Just forbid copying. Aricpp is using shared pointers for Channel objects, hence copying is not required.
--- a/aricpp/channel.h
+++ b/aricpp/channel.h
@@ -99,7 +99,7 @@ public:
Channel(const Channel& rhs) = delete;
Channel& operator=(const Channel& rhs) = delete;
Channel(Channel&& rhs) = default;
- Channel& operator=(Channel&& rhs) = default;
+ Channel& operator=(Channel&& rhs) = delete;
~Channel()
{
aricpp currently uses deprecated items of boost asio library.
Update aricpp to use both old and new boost versions.
I'd like to write a sequence of aricpp operation in this way:
ch1->Hangup()
.Then([=](){ return bridge->Destroy(); })
.Then([=](){ return ch2->Hold(); })
.Then([=](){ cout << "ch2 in hold\n"; })
.OnError([](exception const& e) {cerr << e.what() << endl; });
Every user parameter sent through http to ARI should be encoded to escape special characters.
Calling:
void Client::Connect(ConnectHandler h, const std::chrono::seconds& connectionRetry = std::chrono::seconds::zero())
leaving the second parameter unspecified (i.e., no reconnection required),
the handler h
is not called after asterisk disconnection.
Hi
As i tried to test with samples but it's always throws error as call not found with channel ID value for each channel event including ringing event.
Can you help me to identify the Active/Inactive call with this aricpp by listening channel event.
Using asterisk 16.4.0
Add to Channel
class all the methods correspondent to the ARI channel actions.
Hi, I have a question. First of all thanks for all the good work. I'm willing to use this great library for my project. I am familiar with C++ but the 5 mins tutorial confused me a little bit. How can I install boost library in CentOS 7 and compile the examples?
I installed boost library on CentOS 7 by yum install boost boost-thread boost-devel. Then I tried to make the samples in aricpp/samples directory. But I get the following error:
[root@vmcentos1 samples]# make
g++ -O3 -Werror -Wall -Wextra -Wpedantic -std=c++1y -I.. low_level_dial.cpp -lboost_program_options -lboost_system -lpthread -o low_level_dial
In file included from ../aricpp/client.h:43:0,
from low_level_dial.cpp:42:
../aricpp/websocket.h:41:32: fatal error: boost/beast/core.hpp: No such file or directory
#include <boost/beast/core.hpp>
^
compilation terminated.
make: *** [low_level_dial] Error 1
Any help is appreciated.
Thanks in advanced...
My use case was getting the first argument of the Stasis args
array:
auto caller_ani = aricpp::Get<std::string>(event, { "args", "[0]" });
This resulted in this output:
Exception in handler of event: StasisStart: No such node (args.[0])
After further investigation, I noticed that JsonTree::Get
only takes into account key-value types (this is likely not the best wording) i.e. aricpp::Get<std::string>(event, {"channel", "id"})
. This is because it automatically adds dot to the JSON tree i.e. the second parameter to aricpp::Get
.
It would be nice to be able to access arrays within an event.
Originally posted by ferchor2003 June 23, 2021
In my existing application I already have a custom mechanism to log information. I want to use that in my aricpp application instead of simply writing to std::out.
To that effect I have modified client.h and websocket.h to use a function of my choosing to log events with an added severity parameter.
Hopefully you will find this useful.
Add the LoggingSeverity and pLogInfoFnType definitions and calls to the new logging function:
namespace aricpp
{
enum class LoggingSeverity { Info, Warning, Error };
using pLogInfoFnType = std::function< void(const std::string& msg, LoggingSeverity sev)>;
class WebSocket
{
public:
using ConnectHandler = std::function< void( const boost::system::error_code& ) >;
using ReceiveHandler = std::function< void( const std::string&, const boost::system::error_code& ) >;
WebSocket( boost::asio::io_service& _ios, std::string _host, std::string _port, pLogInfoFnType pLogInfoFn) :
ios(_ios),
host(std::move(_host)),
port(std::move(_port)),
resolver(ios),
socket( new socket_type(ios)),
websocket( new socket_stream_type( *socket)),
pingTimer(ios),
LogCallback(pLogInfoFn)
{}
Where LogCallback is a private member of the class:
pLogInfoFnType LogCallback;
I can then log events like in the following, replacing std::cout:
void Received( boost::system::error_code ec )
{
std::string s( (std::istreambuf_iterator<char>(&rxData)), std::istreambuf_iterator<char>() );
#ifdef ARICPP_TRACE_WEBSOCKET
if ( ec ) LogCallback("*** websocket error: " + ec.message() + '\n', LoggingSeverity::Info);
else LogCallback("*** <== " + s + '\n', LoggingSeverity::Info);
#endif
if ( ec )
onReceive( std::string(), ec );
else
onReceive( s, ec );
rxData.consume( rxData.size() );
if ( ec != boost::asio::error::eof && ec != boost::asio::error::operation_aborted ) Read();
}
Add the ability to pass the custom logging function as a parameter to the constructor:
Client(boost::asio::io_service& ios, const std::string& host, const std::string& port,
std::string _user, std::string _password, std::string _application, pLogInfoFnType pLogInfoFn) :
user(std::move(_user)), password(std::move(_password)),
application(std::move(_application)),
websocket(ios, host, port, pLogInfoFn),
httpclient(ios, host, port, user, password, pLogInfoFn),
LogCallback(pLogInfoFn)
{
assert(LogCallback != nullptr);
}
~Client() noexcept
{
LogCallback("~Client() - Close websocket", LoggingSeverity::Info);
Close();
}
void Connect( ConnectHandler h, std::size_t connectionRetrySeconds )
{
onConnection = std::move(h);
LogCallback("Sending websocket connect request", LoggingSeverity::Info);
websocket.Connect("/ari/events?api_key="+user+":"+password+"&app="+application+"&subscribeAll=false",
[this](auto e)
{
if (e)
{
std::string errMsg = "websocket connect failure: " + e.message();
LogCallback(errMsg, LoggingSeverity::Error);
onConnection(e);
}
else this->WebsocketConnected(); // gcc requires "this"
},
connectionRetrySeconds );
}
//
// Allows the aricpp::client class to log information with the application
//
void LogInPlugin(const string& msg, aricpp::LoggingSeverity sev)
{
switch (sev) {
case aricpp::LoggingSeverity::Info:
MyLogInfo(msg);
break;
case aricpp::LoggingSeverity::Warning:
MyLogWarning(msg);
break;
case aricpp::LoggingSeverity::Error:
MyLogError(msg);
break;
default:
assert(true); // Don't know this severity setting
}
}
...
client = make_unique<aricpp::Client>(ios, astHost, astPort, astUser, astPassword, astAppName, LogInPlugin);
```</div>
the concatination of the mohClass parameter has no "=" between the mohClass and the name of the moh class
old: if ( !mohClass.empty() ) query += "?mohClass" + mohClass;
new: if ( !mohClass.empty() ) query += "?mohClass=" + mohClass;
Channel::Dial
is similar to asterisk commands.
As in the subject.
See post in discussions:
I wanna share something that I have found really useful in my usage of the library.
I have found that in my code I have configuration parameters that are sometimes expressed in milliseconds and sometimes in seconds; I want to make sure that when reading the code I know what time units I'm dealing with.
I have modified the Channel::Record for this purpose like this:
#if __cplusplus <= 201103L
// Simpler, more expressive version working on C++ 11
ProxyPar<Recording>& Record(const std::string& name, const std::string& format,
const std::chrono::seconds& maxDurationSeconds = std::chrono::seconds::zero(),
const std::chrono::seconds& maxSilenceSeconds = std::chrono::seconds::zero(),
const std::string& ifExists = {}, bool beep = false, const std::string& terminateOn = "none") const
{
Recording recording(name, client);
return ProxyPar<Recording>::Command(
Method::post,
"/ari/channels/" + id + "/record?"
"name=" + UrlEncode(name) +
"&format=" + format +
"&terminateOn=" + terminateOn +
(beep ? "&beep=true" : "&beep=false") +
(ifExists.empty() ? "" : "&ifExists=" + ifExists) +
(maxDurationSeconds == std::chrono::seconds::zero() ? "" : "&maxDurationSeconds=" + std::to_string(maxDurationSeconds.count())) +
(maxSilenceSeconds == std::chrono::seconds::zero() ? "" : "&maxSilenceSeconds=" + std::to_string(maxSilenceSeconds.count())),
client,
recording
);
}
#else
// C++ 17 version
#endif
Originally posted by @ferchor2003 in #34
When trying to use
inline std::string ToString(const JsonTree& e)
it uses the wrong template version of boost::property_tree::write_json(os, e);
I have fixed it like this:
inline std::string ToString(const JsonTree& e)
{
std::ostringstream os;
boost::property_tree::write_json(os, e);
return os.str();
}
If the server response were quicker than .After
code calling, a dangling pointer is used.
My aricpp application has subscribed for and receives DTMF events from one inbound call. However after dialing an outbound call with both answered channels added to a bridge then DTMF events are no longer received but trap shows they are passed between endpoints.
I suspect this has something to do with bridge type "mixing,dtmf_events" but I do not see how to set that except with client::RawCmd post ari/bridges type list. But then I could no longer use your bridge object and would have to use RawCmd with bridge ID for all other bridge commands. I'm just looking for guidance before making that change.
I am using the latest version of aricpp and Asterisk 18.
I have not been able to find anything on this question and would appreciate any help.
Hello!
I using CentOS 7, g++ version 4.8.5
I tried to compile samples using makefile which include to sample directory and receved lot of errrors like as following:
In file included from ../aricpp/httpclient.h:43:0,
from ../aricpp/client.h:44,
from low_level_dial.cpp:42:
../aricpp/method.h: In function ‘std::string aricpp::ToString(aricpp::Method)’:
../aricpp/method.h:61:27: error: expected type-specifier
return d[ static_cast<std::underlying_type_t>(m) ];
^
../aricpp/method.h:61:27: error: expected ‘>’
../aricpp/method.h:61:27: error: expected ‘(’
../aricpp/method.h:61:27: error: ‘underlying_type_t’ is not a member of ‘std’
../aricpp/method.h:61:56: error: expected primary-expression before ‘>>’ token
return d[ static_cast<std::underlying_type_t>(m) ];
^
../aricpp/method.h:61:62: error: expected ‘)’ before ‘]’ token
return d[ static_cast<std::underlying_type_t>(m) ];
Could you help me undarsand what is wrong?
Thank you in adwance.
This is a master issue for keeping track of my PRs for adding all the channel methods.
Error message:
In file included from /home/sadel/lib/aricpp/aricpp/client.h:44:0,
from /home/sadel/lib/aricpp/aricpp/bridge.h:37,
from /home/sadel/lib/aricpp/aricpp/arimodel.h:41,
from /home/sadel/modules/cti/pbx.h:13,
from /home/sadel/modules/cti/ctimanager.cpp:9:
/home/sadel/lib/aricpp/aricpp/websocket.h: In lambda function:
/home/sadel/lib/aricpp/aricpp/websocket.h:121:36: error: 'const error_code' has no member named 'failed'
if (error_code.failed())
It seems that boost::system::error_code
does not have method failed()
in v. 1.66
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.