Stefan Schuermans commited on 2019-05-04 14:47:32
Showing 25 changed files, with 1025 additions and 1 deletions.
| ... | ... |
@@ -0,0 +1,70 @@ |
| 1 |
+<html> |
|
| 2 |
+ <head> |
|
| 3 |
+ <title>Blinker - SIP Phone Connector</title> |
|
| 4 |
+ </head> |
|
| 5 |
+ <body> |
|
| 6 |
+ <h1>Blinker - SIP Phone Connector</h1> |
|
| 7 |
+ <p> |
|
| 8 |
+ The SIP phone connector module acts as SIP client and provides an |
|
| 9 |
+ interface to the internal operator connections. |
|
| 10 |
+ Each module instance is a separate SIP client and can handle a |
|
| 11 |
+ single call at a time. The incoming calls are connected to an |
|
| 12 |
+ internal operator connector. Received DTMF tones are delivered |
|
| 13 |
+ to the operator connection. Sound play requests received over |
|
| 14 |
+ the operator connection are processed based on <code>*.wav</code> |
|
| 15 |
+ files in the <code>sounds</code> subdirectory, i.e., the sounds are |
|
| 16 |
+ played on the SIP connection if the file is found. |
|
| 17 |
+ </p> |
|
| 18 |
+ <h2>Configuration</h2> |
|
| 19 |
+ <p> |
|
| 20 |
+ The configuration of the SIP phone connector module with name |
|
| 21 |
+ <code>NAME</code> is located in the <code>sipphones/NAME</code> |
|
| 22 |
+ subdirectory. |
|
| 23 |
+ </p> |
|
| 24 |
+ <h3>SIP Server Name</h3> |
|
| 25 |
+ <p> |
|
| 26 |
+ The file <code>server</code> contains the host name of the SIP |
|
| 27 |
+ server. |
|
| 28 |
+ <br> |
|
| 29 |
+ The SIP phone connector module will register with this server and |
|
| 30 |
+ accept incoming calls (or reject them if another call is already |
|
| 31 |
+ in progress). |
|
| 32 |
+ </p> |
|
| 33 |
+ <h3>SIP User Name</h3> |
|
| 34 |
+ <p> |
|
| 35 |
+ The file <code>username</code> contains the user name for the SIP |
|
| 36 |
+ connection. It is used for authentication and to form the SIP identity |
|
| 37 |
+ by prepending it to the SIP server name (separated <code>@</code>). |
|
| 38 |
+ </p> |
|
| 39 |
+ <h3>SIP Password</h3> |
|
| 40 |
+ <p> |
|
| 41 |
+ The file <code>password</code> contains the password for the SIP |
|
| 42 |
+ authentication. Please note that those files are not present in |
|
| 43 |
+ the example configuration. |
|
| 44 |
+ </p> |
|
| 45 |
+ <h3>Target Operator Connection Slot</h3> |
|
| 46 |
+ <p> |
|
| 47 |
+ The file <code>target</code> contains the name of the operator |
|
| 48 |
+ interface to contact via an operator connection if the extension |
|
| 49 |
+ is called. |
|
| 50 |
+ </p> |
|
| 51 |
+ <h3>Sound Directory</h3> |
|
| 52 |
+ <p> |
|
| 53 |
+ The <code>*.wav</code> files used to process sound playback requests |
|
| 54 |
+ received on the internal operator connection are read from the |
|
| 55 |
+ <code>sounds</code> subdirectory. The file name extension |
|
| 56 |
+ <code>.wav</code> is automatically appended to the name of the |
|
| 57 |
+ requested sound. This means if a sound play request for |
|
| 58 |
+ <code>SOUND</code> is received, the file <code>sounds/SOUND.wav</code> |
|
| 59 |
+ if opened. If it exists, it is played in the SIP connection. |
|
| 60 |
+ </p> |
|
| 61 |
+ <h3>Log Files</h3> |
|
| 62 |
+ <p> |
|
| 63 |
+ Log information from the SIP library is written to the file |
|
| 64 |
+ <code>sip.log</code>. When the file reaches 10000 lines, a new |
|
| 65 |
+ log file is started (using the same file name). The previous |
|
| 66 |
+ log file is kept under the name <code>sip.log.prev</code>. |
|
| 67 |
+ </p> |
|
| 68 |
+ </body> |
|
| 69 |
+</html> |
|
| 70 |
+ |
| ... | ... |
@@ -0,0 +1 @@ |
| 1 |
+/password |
| ... | ... |
@@ -0,0 +1 @@ |
| 1 |
+voip.eventphone.de |
| ... | ... |
@@ -0,0 +1 @@ |
| 1 |
+opprinters/debug |
| ... | ... |
@@ -0,0 +1 @@ |
| 1 |
+7664 |
| ... | ... |
@@ -1,7 +1,7 @@ |
| 1 | 1 |
CONFIG := |
| 2 | 2 |
DEFINE := |
| 3 | 3 |
INCLUDE := -Icommon |
| 4 |
-CFLAGS := -Wall -Wextra -Werror -O2 $(EXTRA_CFLAGS) |
|
| 4 |
+CFLAGS := -Wall -Wextra -Werror -std=c++11 -O2 $(EXTRA_CFLAGS) |
|
| 5 | 5 |
LDFLAGS := $(EXTRA_LDFLAGS) |
| 6 | 6 |
LIBS := -lBlinkenLib $(EXTRA_LIBS) |
| 7 | 7 |
LAST_LIBS := |
| ... | ... |
@@ -15,6 +15,7 @@ TARGET := Blinker.exe |
| 15 | 15 |
else |
| 16 | 16 |
CXX := g++ |
| 17 | 17 |
PLATFORM := linux |
| 18 |
+LIBS += -lpthread |
|
| 18 | 19 |
endif |
| 19 | 20 |
INCLUDE += -I$(PLATFORM) |
| 20 | 21 |
|
| ... | ... |
@@ -0,0 +1,803 @@ |
| 1 |
+/* Blinker |
|
| 2 |
+ Copyright 2011-2014 Stefan Schuermans <stefan@blinkenarea.org> |
|
| 3 |
+ Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html |
|
| 4 |
+ a blinkenarea.org project */ |
|
| 5 |
+ |
|
| 6 |
+#include <chrono> |
|
| 7 |
+#include <condition_variable> |
|
| 8 |
+#include <iostream> |
|
| 9 |
+#include <mutex> |
|
| 10 |
+#include <sstream> |
|
| 11 |
+#include <stdarg.h> |
|
| 12 |
+#include <stdio.h> |
|
| 13 |
+#include <string> |
|
| 14 |
+#include <string.h> |
|
| 15 |
+#include <thread> |
|
| 16 |
+ |
|
| 17 |
+#ifdef BLINKER_CFG_LINPHONE |
|
| 18 |
+#include <linphone/linphonecore.h> |
|
| 19 |
+#endif |
|
| 20 |
+ |
|
| 21 |
+#include "Directory.h" |
|
| 22 |
+#include "File.h" |
|
| 23 |
+#include "Module.h" |
|
| 24 |
+#include "NameFile.h" |
|
| 25 |
+#include "OpConn.h" |
|
| 26 |
+#include "OpConnIf.h" |
|
| 27 |
+#include "SipPhone.h" |
|
| 28 |
+#include "Time.h" |
|
| 29 |
+#include "TimeCallee.h" |
|
| 30 |
+ |
|
| 31 |
+namespace Blinker {
|
|
| 32 |
+ |
|
| 33 |
+#ifdef BLINKER_CFG_LINPHONE |
|
| 34 |
+ |
|
| 35 |
+/** |
|
| 36 |
+ * everything using the linphone library happens in a separate thread, |
|
| 37 |
+ * because some call to linphone take long (up to two seconds) |
|
| 38 |
+ */ |
|
| 39 |
+namespace linphone {
|
|
| 40 |
+ |
|
| 41 |
+static char const *loglevel2str(OrtpLogLevel lev) |
|
| 42 |
+{
|
|
| 43 |
+ switch(lev) {
|
|
| 44 |
+ case ORTP_DEBUG: return "DEBUG"; |
|
| 45 |
+ case ORTP_MESSAGE: return "MESSAGE"; |
|
| 46 |
+ case ORTP_WARNING: return "WARNING"; |
|
| 47 |
+ case ORTP_ERROR: return "ERROR"; |
|
| 48 |
+ case ORTP_FATAL: return "FATAL"; |
|
| 49 |
+ case ORTP_TRACE: return "TRACE"; |
|
| 50 |
+ case ORTP_LOGLEV_END: return "LOGLEV_END"; |
|
| 51 |
+ default: return "?"; |
|
| 52 |
+ } |
|
| 53 |
+} |
|
| 54 |
+ |
|
| 55 |
+static char const *gstate2str(LinphoneGlobalState gstate) |
|
| 56 |
+{
|
|
| 57 |
+ switch (gstate) {
|
|
| 58 |
+ case LinphoneGlobalOff: return "Off"; |
|
| 59 |
+ case LinphoneGlobalStartup: return "Startup"; |
|
| 60 |
+ case LinphoneGlobalOn: return "On"; |
|
| 61 |
+ case LinphoneGlobalShutdown: return "Shutdown"; |
|
| 62 |
+ default: return "?"; |
|
| 63 |
+ } |
|
| 64 |
+} |
|
| 65 |
+ |
|
| 66 |
+static char const *rstate2str(LinphoneRegistrationState rstate) |
|
| 67 |
+{
|
|
| 68 |
+ switch (rstate) {
|
|
| 69 |
+ case LinphoneRegistrationNone: return "None"; |
|
| 70 |
+ case LinphoneRegistrationProgress: return "Progress"; |
|
| 71 |
+ case LinphoneRegistrationOk: return "Ok"; |
|
| 72 |
+ case LinphoneRegistrationCleared: return "Cleared"; |
|
| 73 |
+ case LinphoneRegistrationFailed: return "Failed"; |
|
| 74 |
+ default: return "?"; |
|
| 75 |
+ } |
|
| 76 |
+} |
|
| 77 |
+ |
|
| 78 |
+static char const *cstate2str(LinphoneCallState cstate) |
|
| 79 |
+{
|
|
| 80 |
+ switch (cstate) {
|
|
| 81 |
+ case LinphoneCallIdle: return "Idle"; |
|
| 82 |
+ case LinphoneCallIncomingReceived: return "IncomingReceived"; |
|
| 83 |
+ case LinphoneCallOutgoingInit: return "OutgoingInit"; |
|
| 84 |
+ case LinphoneCallOutgoingProgress: return "OutgoingProgress"; |
|
| 85 |
+ case LinphoneCallOutgoingRinging: return "OutgoingRinging"; |
|
| 86 |
+ case LinphoneCallOutgoingEarlyMedia: return "OutgoingEarlyMedia"; |
|
| 87 |
+ case LinphoneCallConnected: return "Connected"; |
|
| 88 |
+ case LinphoneCallStreamsRunning: return "StreamsRunning"; |
|
| 89 |
+ case LinphoneCallPausing: return "Pausing"; |
|
| 90 |
+ case LinphoneCallPaused: return "Paused"; |
|
| 91 |
+ case LinphoneCallResuming: return "Resuming"; |
|
| 92 |
+ case LinphoneCallRefered: return "Refered"; |
|
| 93 |
+ case LinphoneCallError: return "Error"; |
|
| 94 |
+ case LinphoneCallEnd: return "End"; |
|
| 95 |
+ case LinphoneCallPausedByRemote: return "PausedByRemote"; |
|
| 96 |
+ case LinphoneCallUpdatedByRemote: return "UpdatedByRemote"; |
|
| 97 |
+ case LinphoneCallIncomingEarlyMedia: return "IncomingEarlyMedia"; |
|
| 98 |
+ case LinphoneCallUpdating: return "Updating"; |
|
| 99 |
+ case LinphoneCallReleased: return "Released"; |
|
| 100 |
+ default: return "?"; |
|
| 101 |
+ } |
|
| 102 |
+} |
|
| 103 |
+ |
|
| 104 |
+std::string glLogFileName; ///< name of global log file |
|
| 105 |
+std::ofstream glLogStream; ///< stream to open global log file |
|
| 106 |
+unsigned int glLogLineCnt; ///< number of lines already logged |
|
| 107 |
+ |
|
| 108 |
+/** |
|
| 109 |
+ * @brief log information from linphone library |
|
| 110 |
+ * @param[in] lev log level |
|
| 111 |
+ * @param[in] fmt format string |
|
| 112 |
+ * @param[in] args arguments to insert into format string |
|
| 113 |
+ */ |
|
| 114 |
+void log_handler(OrtpLogLevel lev, const char *fmt, va_list args) |
|
| 115 |
+{
|
|
| 116 |
+ // (re-)open logfile on first line |
|
| 117 |
+ if (glLogLineCnt == 0) {
|
|
| 118 |
+ // close old file and clean up (just to be on the safe side) |
|
| 119 |
+ glLogStream.close(); |
|
| 120 |
+ glLogStream.clear(); |
|
| 121 |
+ // keep previous log file |
|
| 122 |
+ rename(glLogFileName.c_str(), (glLogFileName + ".prev").c_str()); |
|
| 123 |
+ // open new file |
|
| 124 |
+ glLogStream.open(glLogFileName.c_str(), std::ios::out); |
|
| 125 |
+ } |
|
| 126 |
+ // write log line |
|
| 127 |
+ char buffer[4096]; |
|
| 128 |
+ vsnprintf(buffer, sizeof(buffer) - 1, fmt, args); |
|
| 129 |
+ buffer[sizeof(buffer) - 1] = 0; |
|
| 130 |
+ glLogStream << Time::now().toStr() << " " |
|
| 131 |
+ << loglevel2str(lev) << " " << buffer << std::endl; |
|
| 132 |
+ // count lines |
|
| 133 |
+ ++glLogLineCnt; |
|
| 134 |
+ // close file if maximum line count reached |
|
| 135 |
+ if (glLogLineCnt >= 10000) {
|
|
| 136 |
+ // close old file and clean up |
|
| 137 |
+ glLogStream.close(); |
|
| 138 |
+ glLogStream.clear(); |
|
| 139 |
+ // reset line count, so new file is opened on next line |
|
| 140 |
+ glLogLineCnt = 0; |
|
| 141 |
+ } |
|
| 142 |
+} |
|
| 143 |
+ |
|
| 144 |
+/// global init |
|
| 145 |
+void init(std::string const &logFileName) |
|
| 146 |
+{
|
|
| 147 |
+ glLogFileName = logFileName; |
|
| 148 |
+ glLogLineCnt = 0; |
|
| 149 |
+ linphone_core_set_log_handler(log_handler); |
|
| 150 |
+} |
|
| 151 |
+ |
|
| 152 |
+/// data of SIP client worker thread |
|
| 153 |
+struct Data {
|
|
| 154 |
+ SipPhone::ConfigData const *configData; ///< config data for worker thread |
|
| 155 |
+ SipPhone::SharedData *sharedData; ///< data shared with main Blinker thread |
|
| 156 |
+ LinphoneCoreVTable callbacks; ///< callback table |
|
| 157 |
+ LinphoneCore *lc; ///< linphone core object, if active |
|
| 158 |
+ LinphoneCall *call; ///< current call, if available |
|
| 159 |
+ std::string playback; ///< name of playback file |
|
| 160 |
+ std::ofstream logStream; ///< stream to open log file |
|
| 161 |
+ unsigned int logLineCnt; ///< number of lines already logged to file |
|
| 162 |
+}; |
|
| 163 |
+ |
|
| 164 |
+/** |
|
| 165 |
+ * @breif set playback file |
|
| 166 |
+ * @param[in,out] data data of SIP client |
|
| 167 |
+ * @param[in] name name of sound file (without extension) |
|
| 168 |
+ */ |
|
| 169 |
+void set_playback(Data &data, std::string const &name) |
|
| 170 |
+{
|
|
| 171 |
+ Directory dir(data.configData->soundDir); |
|
| 172 |
+ data.playback = dir.getFile(name + ".wav").getPath(); |
|
| 173 |
+} |
|
| 174 |
+ |
|
| 175 |
+void start_playback(Data &data) |
|
| 176 |
+{
|
|
| 177 |
+ if (data.lc && ! data.playback.empty()) {
|
|
| 178 |
+ linphone_core_set_play_file(data.lc, data.playback.c_str()); |
|
| 179 |
+ } |
|
| 180 |
+} |
|
| 181 |
+ |
|
| 182 |
+/** |
|
| 183 |
+ * @brief write log message |
|
| 184 |
+ * @param[in,out] data data of SIP client |
|
| 185 |
+ * @param[in] line lie to log |
|
| 186 |
+ */ |
|
| 187 |
+void log(Data &data, std::string const &line) |
|
| 188 |
+{
|
|
| 189 |
+ // (re-)open logfile on first line |
|
| 190 |
+ if (data.logLineCnt == 0) {
|
|
| 191 |
+ // close old file and clean up (just to be on the safe side) |
|
| 192 |
+ data.logStream.close(); |
|
| 193 |
+ data.logStream.clear(); |
|
| 194 |
+ // keep previous log file |
|
| 195 |
+ rename(data.configData->logFileName.c_str(), |
|
| 196 |
+ (data.configData->logFileName + ".prev").c_str()); |
|
| 197 |
+ // open new file |
|
| 198 |
+ data.logStream.open(data.configData->logFileName.c_str(), std::ios::out); |
|
| 199 |
+ } |
|
| 200 |
+ // write log line |
|
| 201 |
+ data.logStream << Time::now().toStr() << " " << line << std::endl; |
|
| 202 |
+ // count lines |
|
| 203 |
+ ++data.logLineCnt; |
|
| 204 |
+ // close file if maximum line count reached |
|
| 205 |
+ if (data.logLineCnt >= 10000) {
|
|
| 206 |
+ // close old file and clean up |
|
| 207 |
+ data.logStream.close(); |
|
| 208 |
+ data.logStream.clear(); |
|
| 209 |
+ // reset line count, so new file is opened on next line |
|
| 210 |
+ data.logLineCnt = 0; |
|
| 211 |
+ } |
|
| 212 |
+} |
|
| 213 |
+ |
|
| 214 |
+/** |
|
| 215 |
+ * @brief global SIP state changed |
|
| 216 |
+ * @param[in] lc linphone core object |
|
| 217 |
+ * @param[in] gstate new global SIP state |
|
| 218 |
+ * @param[in] message informational message |
|
| 219 |
+ */ |
|
| 220 |
+void global_state_changed(struct _LinphoneCore *lc, |
|
| 221 |
+ LinphoneGlobalState gstate, |
|
| 222 |
+ const char *message) |
|
| 223 |
+{
|
|
| 224 |
+ Data *data = (Data*)linphone_core_get_user_data(lc); |
|
| 225 |
+ |
|
| 226 |
+ std::stringstream line; |
|
| 227 |
+ line << "global state changed to " << gstate2str(gstate) << ": " << message; |
|
| 228 |
+ log(*data, line.str()); |
|
| 229 |
+} |
|
| 230 |
+ |
|
| 231 |
+/** |
|
| 232 |
+ * @brief registration state changed |
|
| 233 |
+ * @param[in] lc linphone core object |
|
| 234 |
+ * @param[in] cfg proxy configuration |
|
| 235 |
+ * @param[in] rstate new registration state |
|
| 236 |
+ * @param[in] message informational message |
|
| 237 |
+ */ |
|
| 238 |
+void registration_state_changed(struct _LinphoneCore *lc, |
|
| 239 |
+ LinphoneProxyConfig *cfg, |
|
| 240 |
+ LinphoneRegistrationState rstate, |
|
| 241 |
+ const char *message) |
|
| 242 |
+{
|
|
| 243 |
+ Data *data = (Data*)linphone_core_get_user_data(lc); |
|
| 244 |
+ (void)cfg; |
|
| 245 |
+ |
|
| 246 |
+ std::stringstream line; |
|
| 247 |
+ line << "registration state changed to " << rstate2str(rstate) << ": " |
|
| 248 |
+ << message; |
|
| 249 |
+ log(*data, line.str()); |
|
| 250 |
+} |
|
| 251 |
+ |
|
| 252 |
+/** |
|
| 253 |
+ * @brief call state changed |
|
| 254 |
+ * @param[in] lc linphone core object |
|
| 255 |
+ * @param[in] call call object |
|
| 256 |
+ * @param[in] cstate new call state |
|
| 257 |
+ * @param[in] message informational message |
|
| 258 |
+ */ |
|
| 259 |
+void call_state_changed(struct _LinphoneCore *lc, LinphoneCall *call, |
|
| 260 |
+ LinphoneCallState cstate, const char *message) |
|
| 261 |
+{
|
|
| 262 |
+ Data *data = (Data*)linphone_core_get_user_data(lc); |
|
| 263 |
+ |
|
| 264 |
+ std::stringstream line; |
|
| 265 |
+ line << "call state changed to " << cstate2str(cstate) << ": " |
|
| 266 |
+ << message; |
|
| 267 |
+ log(*data, line.str()); |
|
| 268 |
+ |
|
| 269 |
+ // current call -> handle state changes |
|
| 270 |
+ if (data->call == call) {
|
|
| 271 |
+ switch (cstate) {
|
|
| 272 |
+ case LinphoneCallStreamsRunning: |
|
| 273 |
+ start_playback(*data); |
|
| 274 |
+ break; |
|
| 275 |
+ case LinphoneCallRefered: |
|
| 276 |
+ case LinphoneCallError: |
|
| 277 |
+ case LinphoneCallEnd: |
|
| 278 |
+ case LinphoneCallReleased: |
|
| 279 |
+ data->call = nullptr; |
|
| 280 |
+ {
|
|
| 281 |
+ std::lock_guard<std::mutex> lock(data->sharedData->mtx); |
|
| 282 |
+ data->sharedData->terminated = true; |
|
| 283 |
+ } |
|
| 284 |
+ break; |
|
| 285 |
+ default: |
|
| 286 |
+ break; |
|
| 287 |
+ } |
|
| 288 |
+ } |
|
| 289 |
+ // other call, but current call active -> reject call |
|
| 290 |
+ else if (data->call) {
|
|
| 291 |
+ switch (cstate) {
|
|
| 292 |
+ case LinphoneCallIncomingReceived: |
|
| 293 |
+ log(*data, "rejecting call (busy)"); |
|
| 294 |
+ linphone_core_decline_call(lc, call, LinphoneReasonBusy); |
|
| 295 |
+ break; |
|
| 296 |
+ default: |
|
| 297 |
+ break; |
|
| 298 |
+ } |
|
| 299 |
+ } |
|
| 300 |
+ // no call active -> accept call |
|
| 301 |
+ else {
|
|
| 302 |
+ switch (cstate) {
|
|
| 303 |
+ case LinphoneCallIncomingReceived: |
|
| 304 |
+ data->call = call; |
|
| 305 |
+ {
|
|
| 306 |
+ std::lock_guard<std::mutex> lock(data->sharedData->mtx); |
|
| 307 |
+ data->sharedData->accepted = true; |
|
| 308 |
+ } |
|
| 309 |
+ data->playback.clear(); |
|
| 310 |
+ log(*data, "accepting call"); |
|
| 311 |
+ linphone_core_accept_call(lc, call); |
|
| 312 |
+ break; |
|
| 313 |
+ default: |
|
| 314 |
+ break; |
|
| 315 |
+ } |
|
| 316 |
+ } |
|
| 317 |
+} |
|
| 318 |
+ |
|
| 319 |
+/** |
|
| 320 |
+ * @brief DTMF received |
|
| 321 |
+ * @param[in] lc linphone core object |
|
| 322 |
+ * @param[in] call call object |
|
| 323 |
+ * @param[in] dtmf DTMF as ASCII character |
|
| 324 |
+ */ |
|
| 325 |
+void dtmf_received(struct _LinphoneCore* lc, LinphoneCall *call, int dtmf) |
|
| 326 |
+{
|
|
| 327 |
+ Data *data = (Data*)linphone_core_get_user_data(lc); |
|
| 328 |
+ |
|
| 329 |
+ // check if current call |
|
| 330 |
+ bool current = data->call == call; |
|
| 331 |
+ |
|
| 332 |
+ // check DTMF |
|
| 333 |
+ char c; |
|
| 334 |
+ if (dtmf >= '0' && dtmf <= '9') {
|
|
| 335 |
+ c = dtmf; |
|
| 336 |
+ } else if (dtmf == '*') {
|
|
| 337 |
+ c = '*'; |
|
| 338 |
+ } else if (dtmf == '#') {
|
|
| 339 |
+ c = '#'; |
|
| 340 |
+ } else {
|
|
| 341 |
+ c = '?'; |
|
| 342 |
+ } |
|
| 343 |
+ |
|
| 344 |
+ std::stringstream line; |
|
| 345 |
+ line << "dtmf received " << (current ? "(current call)" : "(other call)") |
|
| 346 |
+ << ": " << c; |
|
| 347 |
+ log(*data, line.str()); |
|
| 348 |
+ |
|
| 349 |
+ // ignore DTMF from other calls |
|
| 350 |
+ if (! current) {
|
|
| 351 |
+ return; |
|
| 352 |
+ } |
|
| 353 |
+ |
|
| 354 |
+ // report DTMF keys |
|
| 355 |
+ {
|
|
| 356 |
+ std::lock_guard<std::mutex> lock(data->sharedData->mtx); |
|
| 357 |
+ data->sharedData->dtmf += c; |
|
| 358 |
+ } |
|
| 359 |
+} |
|
| 360 |
+ |
|
| 361 |
+/** |
|
| 362 |
+ * @brief register if not active and login data available |
|
| 363 |
+ * @param[in,out] data data of SIP client |
|
| 364 |
+ * @param[in] server SIP server |
|
| 365 |
+ * @param[in] username SIP username |
|
| 366 |
+ * @param[in] password SIP password |
|
| 367 |
+ */ |
|
| 368 |
+void do_register(Data &data, std::string const &server, |
|
| 369 |
+ std::string const &username, std::string &password) |
|
| 370 |
+{
|
|
| 371 |
+ if (data.lc) {
|
|
| 372 |
+ return; |
|
| 373 |
+ } |
|
| 374 |
+ if (server.empty()) {
|
|
| 375 |
+ return; |
|
| 376 |
+ } |
|
| 377 |
+ |
|
| 378 |
+ // create linphone core object |
|
| 379 |
+ data.lc = linphone_core_new(&data.callbacks, NULL, NULL, &data); |
|
| 380 |
+ if (! data.lc) {
|
|
| 381 |
+ log(data, "failed to create linphone core"); |
|
| 382 |
+ return; |
|
| 383 |
+ } |
|
| 384 |
+ log(data, "linphone core created"); |
|
| 385 |
+ |
|
| 386 |
+ // set auth data |
|
| 387 |
+ if (! username.empty() && ! password.empty()) {
|
|
| 388 |
+ LinphoneAuthInfo *auth_info = |
|
| 389 |
+ linphone_auth_info_new(username.c_str(), username.c_str(), |
|
| 390 |
+ password.c_str(), "", ""); |
|
| 391 |
+ if (! auth_info) {
|
|
| 392 |
+ log(data, "failed to create auth info"); |
|
| 393 |
+ linphone_core_destroy(data.lc); |
|
| 394 |
+ data.lc = nullptr; |
|
| 395 |
+ return; |
|
| 396 |
+ } |
|
| 397 |
+ linphone_core_add_auth_info(data.lc, auth_info); |
|
| 398 |
+ log(data, "auth info set"); |
|
| 399 |
+ } else {
|
|
| 400 |
+ log(data, "no auth data available"); |
|
| 401 |
+ } |
|
| 402 |
+ |
|
| 403 |
+ // configure proxy |
|
| 404 |
+ std::string sipserver = "sip:" + server; |
|
| 405 |
+ std::string identity = "sip:" + username + "@" + server; |
|
| 406 |
+ LinphoneProxyConfig *proxy = linphone_core_create_proxy_config(data.lc); |
|
| 407 |
+ if (! proxy) {
|
|
| 408 |
+ log(data, "failed to create proxy config"); |
|
| 409 |
+ linphone_core_destroy(data.lc); |
|
| 410 |
+ data.lc = nullptr; |
|
| 411 |
+ return; |
|
| 412 |
+ } |
|
| 413 |
+ linphone_proxy_config_set_server_addr(proxy, sipserver.c_str()); |
|
| 414 |
+ linphone_proxy_config_set_identity(proxy, identity.c_str()); |
|
| 415 |
+ linphone_proxy_config_enable_register(proxy, TRUE); |
|
| 416 |
+ linphone_core_add_proxy_config(data.lc, proxy); |
|
| 417 |
+ log(data, "proxy config set"); |
|
| 418 |
+ |
|
| 419 |
+ // tell linphone not to rind and to use files instead of sound card |
|
| 420 |
+ linphone_core_set_ring(data.lc, nullptr); |
|
| 421 |
+ linphone_core_use_files(data.lc, TRUE); |
|
| 422 |
+ linphone_core_set_play_file(data.lc, nullptr); |
|
| 423 |
+} |
|
| 424 |
+ |
|
| 425 |
+/** |
|
| 426 |
+ * @brief deregister if active |
|
| 427 |
+ * @param[in,out] data data of SIP client |
|
| 428 |
+ */ |
|
| 429 |
+void do_deregister(Data &data) |
|
| 430 |
+{
|
|
| 431 |
+ if (! data.lc) {
|
|
| 432 |
+ return; |
|
| 433 |
+ } |
|
| 434 |
+ |
|
| 435 |
+ log(data, "destroying..."); |
|
| 436 |
+ linphone_core_destroy(data.lc); |
|
| 437 |
+ data.lc = nullptr; |
|
| 438 |
+ data.call = nullptr; |
|
| 439 |
+ log(data, "destroyed"); |
|
| 440 |
+} |
|
| 441 |
+ |
|
| 442 |
+/** |
|
| 443 |
+ * @brief play sound on call if active |
|
| 444 |
+ * @param[in,out] data data of SIP client |
|
| 445 |
+ * @param[in] name name of sound |
|
| 446 |
+ */ |
|
| 447 |
+void do_play(Data &data, std::string const &name) |
|
| 448 |
+{
|
|
| 449 |
+ if (! data.lc) {
|
|
| 450 |
+ return; |
|
| 451 |
+ } |
|
| 452 |
+ if (! data.call) {
|
|
| 453 |
+ return; |
|
| 454 |
+ } |
|
| 455 |
+ |
|
| 456 |
+ std::stringstream line; |
|
| 457 |
+ line << "playing sound \"" << name << "\""; |
|
| 458 |
+ log(data, line.str()); |
|
| 459 |
+ set_playback(data, name); |
|
| 460 |
+ start_playback(data); |
|
| 461 |
+} |
|
| 462 |
+ |
|
| 463 |
+/** |
|
| 464 |
+ * @brief hangup call if active |
|
| 465 |
+ * @param[in,out] data data of SIP client |
|
| 466 |
+ */ |
|
| 467 |
+void do_hangup(Data &data) |
|
| 468 |
+{
|
|
| 469 |
+ if (! data.lc) {
|
|
| 470 |
+ return; |
|
| 471 |
+ } |
|
| 472 |
+ if (! data.call) {
|
|
| 473 |
+ return; |
|
| 474 |
+ } |
|
| 475 |
+ |
|
| 476 |
+ log(data, "terminating call..."); |
|
| 477 |
+ linphone_core_terminate_call(data.lc, data.call); |
|
| 478 |
+ data.call = nullptr; |
|
| 479 |
+ log(data, "call terminated"); |
|
| 480 |
+} |
|
| 481 |
+ |
|
| 482 |
+/** |
|
| 483 |
+ * @brief linphone worker thread |
|
| 484 |
+ * @param[in] soundDir directory of sound files |
|
| 485 |
+ * @param[in,out] data shared data for communication with main Blinker thread |
|
| 486 |
+ */ |
|
| 487 |
+void worker(SipPhone::ConfigData const &configData, |
|
| 488 |
+ SipPhone::SharedData &sharedData) |
|
| 489 |
+{
|
|
| 490 |
+ // set up data of SIP client |
|
| 491 |
+ Data data; |
|
| 492 |
+ data.configData = &configData; |
|
| 493 |
+ data.sharedData = &sharedData; |
|
| 494 |
+ memset(&data.callbacks, 0, sizeof(data.callbacks)); |
|
| 495 |
+ data.callbacks.global_state_changed = global_state_changed; |
|
| 496 |
+ data.callbacks.registration_state_changed = registration_state_changed; |
|
| 497 |
+ data.callbacks.call_state_changed = call_state_changed; |
|
| 498 |
+ data.callbacks.dtmf_received = dtmf_received; |
|
| 499 |
+ data.lc = nullptr; |
|
| 500 |
+ data.call = nullptr; |
|
| 501 |
+ data.logLineCnt = 0; |
|
| 502 |
+ |
|
| 503 |
+ // main loop of worker - with shared data locked |
|
| 504 |
+ std::chrono::milliseconds timeout(10); |
|
| 505 |
+ std::unique_lock<std::mutex> lock(sharedData.mtx); |
|
| 506 |
+ while (sharedData.run) {
|
|
| 507 |
+ |
|
| 508 |
+ // hangup |
|
| 509 |
+ if (sharedData.hangup) {
|
|
| 510 |
+ sharedData.hangup = false; |
|
| 511 |
+ sharedData.reqPlay = false; |
|
| 512 |
+ sharedData.dtmf.clear(); |
|
| 513 |
+ sharedData.terminated = true; |
|
| 514 |
+ // execute hangup with unlocked shared data (parallelism!) |
|
| 515 |
+ lock.unlock(); |
|
| 516 |
+ do_hangup(data); |
|
| 517 |
+ lock.lock(); |
|
| 518 |
+ continue; // re-check everything after re-locking mutex |
|
| 519 |
+ } |
|
| 520 |
+ |
|
| 521 |
+ // re-register (including initial registration and derigstration) |
|
| 522 |
+ if (sharedData.reregister) {
|
|
| 523 |
+ sharedData.reregister = false; |
|
| 524 |
+ sharedData.hangup = false; |
|
| 525 |
+ sharedData.reqPlay = false; |
|
| 526 |
+ sharedData.dtmf.clear(); |
|
| 527 |
+ sharedData.terminated = true; |
|
| 528 |
+ std::string server = sharedData.server; |
|
| 529 |
+ std::string username = sharedData.username; |
|
| 530 |
+ std::string password = sharedData.password; |
|
| 531 |
+ // execute re-registration with unlocked shared data (parallelism!) |
|
| 532 |
+ lock.unlock(); |
|
| 533 |
+ do_deregister(data); |
|
| 534 |
+ do_register(data, server, username, password); |
|
| 535 |
+ lock.lock(); |
|
| 536 |
+ continue; // re-check everything after re-locking mutex |
|
| 537 |
+ } |
|
| 538 |
+ |
|
| 539 |
+ // play sound |
|
| 540 |
+ if (sharedData.reqPlay) {
|
|
| 541 |
+ sharedData.reqPlay = false; |
|
| 542 |
+ std::string name = sharedData.reqPlayName; |
|
| 543 |
+ // execute hangup with unlocked shared data (parallelism!) |
|
| 544 |
+ lock.unlock(); |
|
| 545 |
+ do_play(data, name); |
|
| 546 |
+ lock.lock(); |
|
| 547 |
+ continue; // re-check everything after re-locking mutex |
|
| 548 |
+ } |
|
| 549 |
+ |
|
| 550 |
+ // background processing by linphone if active |
|
| 551 |
+ if (data.lc) {
|
|
| 552 |
+ lock.unlock(); |
|
| 553 |
+ linphone_core_iterate(data.lc); |
|
| 554 |
+ lock.lock(); |
|
| 555 |
+ } |
|
| 556 |
+ |
|
| 557 |
+ // nothing to do, wait for signal |
|
| 558 |
+ sharedData.cond.wait_for(lock, timeout); |
|
| 559 |
+ |
|
| 560 |
+ } // while (sharedData.run) |
|
| 561 |
+} |
|
| 562 |
+ |
|
| 563 |
+} // namespace linphone |
|
| 564 |
+ |
|
| 565 |
+#endif // #ifdef BLINKER_CFG_LINPHONE |
|
| 566 |
+ |
|
| 567 |
+/** |
|
| 568 |
+ * @brief global initialization (call once before using this class) |
|
| 569 |
+ * @param[in] globalLogDir directory for global SIP log files |
|
| 570 |
+ */ |
|
| 571 |
+void SipPhone::init(const Directory &globalLogDir) |
|
| 572 |
+{
|
|
| 573 |
+#ifdef BLINKER_CFG_LINPHONE |
|
| 574 |
+ linphone::init(globalLogDir.getFile("sip.log").getPath());
|
|
| 575 |
+#endif |
|
| 576 |
+} |
|
| 577 |
+ |
|
| 578 |
+/** |
|
| 579 |
+ * @brief constructor |
|
| 580 |
+ * @param[in] name module name |
|
| 581 |
+ * @param[in] mgrs managers |
|
| 582 |
+ * @param[in] dirBase base directory |
|
| 583 |
+ */ |
|
| 584 |
+SipPhone::SipPhone(const std::string &name, Mgrs &mgrs, |
|
| 585 |
+ const Directory &dirBase): |
|
| 586 |
+ Module(name, mgrs, dirBase), |
|
| 587 |
+ m_fileServer(dirBase.getFile("server")),
|
|
| 588 |
+ m_fileUsername(dirBase.getFile("username")),
|
|
| 589 |
+ m_filePassword(dirBase.getFile("password")),
|
|
| 590 |
+ m_fileTarget(dirBase.getFile("target")),
|
|
| 591 |
+ m_configData(), |
|
| 592 |
+ m_worker(), |
|
| 593 |
+ m_sharedData(), |
|
| 594 |
+ m_curConn(nullptr) |
|
| 595 |
+{
|
|
| 596 |
+ m_configData.soundDir = dirBase.getSubdir("sounds").getPath();
|
|
| 597 |
+ m_configData.logFileName = dirBase.getFile("sip.log").getPath();
|
|
| 598 |
+ m_sharedData.run = true; |
|
| 599 |
+ m_sharedData.reregister = false; |
|
| 600 |
+ m_sharedData.reqPlay = false; |
|
| 601 |
+ m_sharedData.hangup = false; |
|
| 602 |
+ m_sharedData.accepted = false; |
|
| 603 |
+ m_sharedData.terminated = false; |
|
| 604 |
+ |
|
| 605 |
+ m_mgrs.m_callMgr.requestTimeCall(this, Time::now()); |
|
| 606 |
+ |
|
| 607 |
+#ifdef BLINKER_CFG_LINPHONE |
|
| 608 |
+ m_worker = std::thread(linphone::worker, std::ref(m_configData), |
|
| 609 |
+ std::ref(m_sharedData)); |
|
| 610 |
+#endif |
|
| 611 |
+ |
|
| 612 |
+ sipRegister(); |
|
| 613 |
+} |
|
| 614 |
+ |
|
| 615 |
+/// virtual destructor |
|
| 616 |
+SipPhone::~SipPhone() |
|
| 617 |
+{
|
|
| 618 |
+ if (m_curConn) {
|
|
| 619 |
+ m_curConn->close(); |
|
| 620 |
+ m_curConn = nullptr; |
|
| 621 |
+ } |
|
| 622 |
+ |
|
| 623 |
+ sipDeregister(); |
|
| 624 |
+ |
|
| 625 |
+ {
|
|
| 626 |
+ std::lock_guard<std::mutex> lock(m_sharedData.mtx); |
|
| 627 |
+ m_sharedData.run = false; |
|
| 628 |
+ } |
|
| 629 |
+ m_sharedData.cond.notify_one(); |
|
| 630 |
+#ifdef BLINKER_CFG_LINPHONE |
|
| 631 |
+ m_worker.join(); |
|
| 632 |
+#endif |
|
| 633 |
+ |
|
| 634 |
+ m_mgrs.m_callMgr.cancelTimeCall(this); |
|
| 635 |
+} |
|
| 636 |
+ |
|
| 637 |
+/// check for update of configuration |
|
| 638 |
+void SipPhone::updateConfig() |
|
| 639 |
+{
|
|
| 640 |
+ // server, username or password file modified -> re-connect |
|
| 641 |
+ if (m_fileServer.checkModified() || m_fileUsername.checkModified() || |
|
| 642 |
+ m_filePassword.checkModified()) {
|
|
| 643 |
+ // re-register |
|
| 644 |
+ sipDeregister(); |
|
| 645 |
+ sipRegister(); |
|
| 646 |
+ } |
|
| 647 |
+ |
|
| 648 |
+ // target file was modified -> re-get target operator interface to connect to |
|
| 649 |
+ if (m_fileTarget.checkModified()) {
|
|
| 650 |
+ m_fileTarget.update(); |
|
| 651 |
+ } |
|
| 652 |
+} |
|
| 653 |
+ |
|
| 654 |
+/// callback when requested time reached |
|
| 655 |
+void SipPhone::timeCall() |
|
| 656 |
+{
|
|
| 657 |
+ Time now = Time::now(); |
|
| 658 |
+ |
|
| 659 |
+ // get information from SIP phone worker |
|
| 660 |
+ bool accepted; |
|
| 661 |
+ std::string dtmf; |
|
| 662 |
+ bool terminated; |
|
| 663 |
+ {
|
|
| 664 |
+ std::lock_guard<std::mutex> lock(m_sharedData.mtx); |
|
| 665 |
+ accepted = m_sharedData.accepted; |
|
| 666 |
+ m_sharedData.accepted = false; |
|
| 667 |
+ dtmf = m_sharedData.dtmf; |
|
| 668 |
+ m_sharedData.dtmf.clear(); |
|
| 669 |
+ terminated = m_sharedData.terminated; |
|
| 670 |
+ m_sharedData.terminated = false; |
|
| 671 |
+ } |
|
| 672 |
+ |
|
| 673 |
+ // phone call has terminated or new one has been accepted |
|
| 674 |
+ if (terminated || accepted) {
|
|
| 675 |
+ // close connection if any |
|
| 676 |
+ if (m_curConn) {
|
|
| 677 |
+ // request hang up of phone |
|
| 678 |
+ {
|
|
| 679 |
+ std::lock_guard<std::mutex> lock(m_sharedData.mtx); |
|
| 680 |
+ m_sharedData.hangup = true; |
|
| 681 |
+ } |
|
| 682 |
+ // close and forget connection |
|
| 683 |
+ m_curConn->close(); |
|
| 684 |
+ m_curConn = nullptr; |
|
| 685 |
+ } |
|
| 686 |
+ } |
|
| 687 |
+ |
|
| 688 |
+ // phone call has been accepted |
|
| 689 |
+ if (accepted) {
|
|
| 690 |
+ // try to open new connection to target operator interface |
|
| 691 |
+ if (m_fileTarget.m_valid) {
|
|
| 692 |
+ m_curConn = m_mgrs.m_opMgr.connect(m_fileTarget.m_obj.m_str, this); |
|
| 693 |
+ } |
|
| 694 |
+ } |
|
| 695 |
+ |
|
| 696 |
+ // DTMF received -> send to operator connection |
|
| 697 |
+ if (! dtmf.empty()) {
|
|
| 698 |
+ if (m_curConn) {
|
|
| 699 |
+ for (char key: dtmf) {
|
|
| 700 |
+ m_curConn->sendKey(key); |
|
| 701 |
+ } |
|
| 702 |
+ } |
|
| 703 |
+ } |
|
| 704 |
+ |
|
| 705 |
+ // request next call |
|
| 706 |
+ Time interval; |
|
| 707 |
+ interval.fromMs(20); |
|
| 708 |
+ m_mgrs.m_callMgr.requestTimeCall(this, now + interval); |
|
| 709 |
+} |
|
| 710 |
+ |
|
| 711 |
+/** |
|
| 712 |
+ * @brief key command received on operator connection |
|
| 713 |
+ * @param[in] pConn operator connection object |
|
| 714 |
+ * @param[in] key key that was pressed |
|
| 715 |
+ */ |
|
| 716 |
+void SipPhone::opConnRecvKey(OpConn *pConn, char key) |
|
| 717 |
+{
|
|
| 718 |
+ // ignore keys received |
|
| 719 |
+ (void)pConn; |
|
| 720 |
+ (void)key; |
|
| 721 |
+} |
|
| 722 |
+ |
|
| 723 |
+/** |
|
| 724 |
+ * @brief play command received on operator connection |
|
| 725 |
+ * @param[in] pConn operator connection object |
|
| 726 |
+ * @param[in] sound name of sound to play |
|
| 727 |
+ */ |
|
| 728 |
+void SipPhone::opConnRecvPlay(OpConn *pConn, const std::string &sound) |
|
| 729 |
+{
|
|
| 730 |
+ // only process current connection |
|
| 731 |
+ if (pConn != m_curConn) {
|
|
| 732 |
+ return; |
|
| 733 |
+ } |
|
| 734 |
+ |
|
| 735 |
+ // request playing sound |
|
| 736 |
+ {
|
|
| 737 |
+ std::lock_guard<std::mutex> lock(m_sharedData.mtx); |
|
| 738 |
+ m_sharedData.reqPlay = true; |
|
| 739 |
+ m_sharedData.reqPlayName = sound; |
|
| 740 |
+ } |
|
| 741 |
+} |
|
| 742 |
+ |
|
| 743 |
+/** |
|
| 744 |
+ * @brief operator connection is closed |
|
| 745 |
+ * @param[in] pConn operator connection object |
|
| 746 |
+ * |
|
| 747 |
+ * The connection may not be used for sending any more in this callback. |
|
| 748 |
+ */ |
|
| 749 |
+void SipPhone::opConnClose(OpConn *pConn) |
|
| 750 |
+{
|
|
| 751 |
+ // only process current connection |
|
| 752 |
+ if (pConn != m_curConn) {
|
|
| 753 |
+ return; |
|
| 754 |
+ } |
|
| 755 |
+ |
|
| 756 |
+ // request hang up of phone |
|
| 757 |
+ {
|
|
| 758 |
+ std::lock_guard<std::mutex> lock(m_sharedData.mtx); |
|
| 759 |
+ m_sharedData.hangup = true; |
|
| 760 |
+ } |
|
| 761 |
+ |
|
| 762 |
+ // forget connection |
|
| 763 |
+ m_curConn = nullptr; |
|
| 764 |
+} |
|
| 765 |
+ |
|
| 766 |
+/// (re-)register with SIP server |
|
| 767 |
+void SipPhone::sipRegister() |
|
| 768 |
+{
|
|
| 769 |
+ // read settings |
|
| 770 |
+ m_fileServer.update(); |
|
| 771 |
+ m_fileUsername.update(); |
|
| 772 |
+ m_filePassword.update(); |
|
| 773 |
+ |
|
| 774 |
+ // put server, username and password into shared data, trigger registration |
|
| 775 |
+ {
|
|
| 776 |
+ std::lock_guard<std::mutex> lock(m_sharedData.mtx); |
|
| 777 |
+ m_sharedData.server = m_fileServer.m_valid |
|
| 778 |
+ ? m_fileServer.m_obj.toStr() : std::string(); |
|
| 779 |
+ m_sharedData.username = m_fileUsername.m_valid |
|
| 780 |
+ ? m_fileUsername.m_obj.toStr() : std::string(); |
|
| 781 |
+ m_sharedData.password = m_filePassword.m_valid |
|
| 782 |
+ ? m_filePassword.m_obj.toStr() : std::string(); |
|
| 783 |
+ m_sharedData.reregister = true; |
|
| 784 |
+ } |
|
| 785 |
+ m_sharedData.cond.notify_one(); |
|
| 786 |
+} |
|
| 787 |
+ |
|
| 788 |
+/// deregister with SIP server |
|
| 789 |
+void SipPhone::sipDeregister() |
|
| 790 |
+{
|
|
| 791 |
+ // remove login infor from shared data, trigger re-registration |
|
| 792 |
+ {
|
|
| 793 |
+ std::lock_guard<std::mutex> lock(m_sharedData.mtx); |
|
| 794 |
+ m_sharedData.server.clear(); |
|
| 795 |
+ m_sharedData.username.clear(); |
|
| 796 |
+ m_sharedData.password.clear(); |
|
| 797 |
+ m_sharedData.reregister = true; |
|
| 798 |
+ } |
|
| 799 |
+ m_sharedData.cond.notify_one(); |
|
| 800 |
+} |
|
| 801 |
+ |
|
| 802 |
+} // namespace Blinker |
|
| 803 |
+ |
| ... | ... |
@@ -0,0 +1,132 @@ |
| 1 |
+/* Blinker |
|
| 2 |
+ Copyright 2011-2014 Stefan Schuermans <stefan@blinkenarea.org> |
|
| 3 |
+ Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html |
|
| 4 |
+ a blinkenarea.org project */ |
|
| 5 |
+ |
|
| 6 |
+#ifndef BLINKER_SIPPHONE_H |
|
| 7 |
+#define BLINKER_SIPPHONE_H |
|
| 8 |
+ |
|
| 9 |
+#include <condition_variable> |
|
| 10 |
+#include <mutex> |
|
| 11 |
+#include <string> |
|
| 12 |
+#include <thread> |
|
| 13 |
+ |
|
| 14 |
+#include "File.h" |
|
| 15 |
+#include "Module.h" |
|
| 16 |
+#include "NameFile.h" |
|
| 17 |
+#include "OpConn.h" |
|
| 18 |
+#include "OpConnIf.h" |
|
| 19 |
+#include "Time.h" |
|
| 20 |
+#include "TimeCallee.h" |
|
| 21 |
+ |
|
| 22 |
+namespace Blinker {
|
|
| 23 |
+ |
|
| 24 |
+/// SIP phone connector |
|
| 25 |
+class SipPhone: public Module, public OpConnIf, public TimeCallee |
|
| 26 |
+{
|
|
| 27 |
+public: |
|
| 28 |
+ /// configuration data for worker |
|
| 29 |
+ struct ConfigData {
|
|
| 30 |
+ std::string soundDir; ///< directory of sound files |
|
| 31 |
+ std::string logFileName; ///< base name of log file |
|
| 32 |
+ }; |
|
| 33 |
+ /// data shared between linphone thread and main Blinker thread |
|
| 34 |
+ struct SharedData {
|
|
| 35 |
+ std::mutex mtx; ///< protection for this structure |
|
| 36 |
+ std::condition_variable cond; ///< worker threads waits on this condition |
|
| 37 |
+ /// Blinker -> SIP |
|
| 38 |
+ //@{
|
|
| 39 |
+ bool run; ///< whether to keep worker running (false = exit) |
|
| 40 |
+ bool reregister; ///< flag to trigger (re-)registration |
|
| 41 |
+ std::string server; ///< SIP server, empty means invalid |
|
| 42 |
+ std::string username; ///< SIP username, empty = no auth |
|
| 43 |
+ std::string password; ///< SIP password, empty = no auth |
|
| 44 |
+ bool reqPlay; ///< flag to trigger playing sound |
|
| 45 |
+ std::string reqPlayName; ///< name of sound to play |
|
| 46 |
+ bool hangup; ///< flag to trigger hangup of phone call |
|
| 47 |
+ //@} |
|
| 48 |
+ /// SIP -> Blinker |
|
| 49 |
+ //@{
|
|
| 50 |
+ bool accepted; ///< flag for incoming/accepted phone call |
|
| 51 |
+ std::string dtmf; ///< DTMF keys received |
|
| 52 |
+ bool terminated; ///< flag for phone call is terminated |
|
| 53 |
+ //@} |
|
| 54 |
+ }; |
|
| 55 |
+ |
|
| 56 |
+public: |
|
| 57 |
+ /** |
|
| 58 |
+ * @brief global initialization (call once before using this class) |
|
| 59 |
+ * @param[in] globalLogDir directory for global SIP log files |
|
| 60 |
+ */ |
|
| 61 |
+ static void init(const Directory &globalLogDir); |
|
| 62 |
+ |
|
| 63 |
+public: |
|
| 64 |
+ /** |
|
| 65 |
+ * @brief constructor |
|
| 66 |
+ * @param[in] name module name |
|
| 67 |
+ * @param[in] mgrs managers |
|
| 68 |
+ * @param[in] dirBase base directory |
|
| 69 |
+ */ |
|
| 70 |
+ SipPhone(const std::string &name, Mgrs &mgrs, const Directory &dirBase); |
|
| 71 |
+ |
|
| 72 |
+ /// virtual destructor |
|
| 73 |
+ virtual ~SipPhone(); |
|
| 74 |
+ |
|
| 75 |
+private: |
|
| 76 |
+ /// copy constructor disabled |
|
| 77 |
+ SipPhone(const SipPhone &that); |
|
| 78 |
+ |
|
| 79 |
+ /// assignment operator disabled |
|
| 80 |
+ const SipPhone & operator=(const SipPhone &that); |
|
| 81 |
+ |
|
| 82 |
+public: |
|
| 83 |
+ /// check for update of configuration |
|
| 84 |
+ virtual void updateConfig(); |
|
| 85 |
+ |
|
| 86 |
+ /// callback when requested time reached |
|
| 87 |
+ virtual void timeCall(); |
|
| 88 |
+ |
|
| 89 |
+ /** |
|
| 90 |
+ * @brief key command received on operator connection |
|
| 91 |
+ * @param[in] pConn operator connection object |
|
| 92 |
+ * @param[in] key key that was pressed |
|
| 93 |
+ */ |
|
| 94 |
+ virtual void opConnRecvKey(OpConn *pConn, char key); |
|
| 95 |
+ |
|
| 96 |
+ /** |
|
| 97 |
+ * @brief play command received on operator connection |
|
| 98 |
+ * @param[in] pConn operator connection object |
|
| 99 |
+ * @param[in] sound name of sound to play |
|
| 100 |
+ */ |
|
| 101 |
+ virtual void opConnRecvPlay(OpConn *pConn, const std::string &sound); |
|
| 102 |
+ |
|
| 103 |
+ /** |
|
| 104 |
+ * @brief operator connection is closed |
|
| 105 |
+ * @param[in] pConn operator connection object |
|
| 106 |
+ * |
|
| 107 |
+ * The connection may not be used for sending any more in this callback. |
|
| 108 |
+ */ |
|
| 109 |
+ virtual void opConnClose(OpConn *pConn); |
|
| 110 |
+ |
|
| 111 |
+protected: |
|
| 112 |
+ /// (re-)register with SIP server |
|
| 113 |
+ void sipRegister(); |
|
| 114 |
+ |
|
| 115 |
+ /// deregister with SIP server |
|
| 116 |
+ void sipDeregister(); |
|
| 117 |
+ |
|
| 118 |
+protected: |
|
| 119 |
+ NameFile m_fileServer; ///< name of SIP server |
|
| 120 |
+ NameFile m_fileUsername; ///< SIP username |
|
| 121 |
+ NameFile m_filePassword; ///< SIP password |
|
| 122 |
+ NameFile m_fileTarget; ///< target operator interface name to connect to |
|
| 123 |
+ ConfigData m_configData; ///< configuration data for worker |
|
| 124 |
+ std::thread m_worker; ///< worker thread |
|
| 125 |
+ SharedData m_sharedData; ///< data shared between worker and main |
|
| 126 |
+ OpConn *m_curConn; ///< current connection |
|
| 127 |
+}; // class SipPhone |
|
| 128 |
+ |
|
| 129 |
+} // namespace Blinker |
|
| 130 |
+ |
|
| 131 |
+#endif // #ifndef BLINKER_SIPPHONE_H |
|
| 132 |
+ |
| ... | ... |
@@ -24,6 +24,7 @@ |
| 24 | 24 |
#include "RateLimiter.h" |
| 25 | 25 |
#include "Resizer.h" |
| 26 | 26 |
#include "Scaler.h" |
| 27 |
+#include "SipPhone.h" |
|
| 27 | 28 |
#include "SyncNameSplitter.h" |
| 28 | 29 |
#include "SyncPrinter.h" |
| 29 | 30 |
#include "Transformer.h" |
| ... | ... |
@@ -42,6 +43,8 @@ void run(const std::string &dirConfig) |
| 42 | 43 |
{
|
| 43 | 44 |
Directory cfg(dirConfig); |
| 44 | 45 |
|
| 46 |
+ SipPhone::init(dirConfig); |
|
| 47 |
+ |
|
| 45 | 48 |
Mgrs mgrs; |
| 46 | 49 |
|
| 47 | 50 |
#define MODULEMGR(TYPE, CLASS) \ |
| ... | ... |
@@ -60,6 +63,7 @@ void run(const std::string &dirConfig) |
| 60 | 63 |
MODULEMGR(Resizer, resizers); |
| 61 | 64 |
MODULEMGR(RateLimiter, ratelimiters); |
| 62 | 65 |
MODULEMGR(Scaler, scalers); |
| 66 |
+ MODULEMGR(SipPhone, sipphones); |
|
| 63 | 67 |
MODULEMGR(SyncNameSplitter, syncnamesplitters); |
| 64 | 68 |
MODULEMGR(SyncPrinter, syncprinters); |
| 65 | 69 |
MODULEMGR(Transformer, transformers); |