added option to turn off quotes in AT+CMGS command in generic SMS driver; additional...
[public/netxms.git] / src / smsdrv / generic / main.cpp
CommitLineData
9f24efb3
VK
1/*
2** NetXMS - Network Management System
3** Generic SMS driver
f19168bd 4** Copyright (C) 2003-2016 Raden Solutions
9f24efb3
VK
5**
6** This program is free software; you can redistribute it and/or modify
7** it under the terms of the GNU Lesser General Public License as published by
8** the Free Software Foundation; either version 3 of the License, or
9** (at your option) any later version.
10**
11** This program is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU Lesser General Public License
17** along with this program; if not, write to the Free Software
18** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19**
20** File: main.cpp
21**
22**/
5039dede 23
9b9b8709 24#include "generic_smsdrv.h"
5039dede 25
9b9b8709 26bool SMSCreatePDUString(const char* phoneNumber, const char* message, char* pduBuffer);
770f8c64 27
9b9b8709
VK
28/**
29 * Static data
30 */
31static Serial s_serial;
32static const char *s_eosMarks[] = { "OK", "ERROR", NULL };
33static const char *s_eosMarksSend[] = { ">", "ERROR", NULL };
34static enum { OM_TEXT, OM_PDU } s_operationMode = OM_TEXT;
fce42f5b 35static bool s_cmgsUseQuotes = true;
17a4c8ab 36
9b9b8709
VK
37/**
38 * Read input to OK
39 */
40static bool ReadToOK(Serial *serial, char *data = NULL)
41{
42 char buffer[1024];
43 memset(buffer, 0, 1024);
44 while(true)
45 {
46 char *mark;
47 int rc = serial->readToMark(buffer, 1024, s_eosMarks, &mark);
48 if (rc <= 0)
49 {
623e2f0e 50 nxlog_debug(5, _T("SMS: ReadToOK: readToMark returned %d"), rc);
9b9b8709
VK
51 return false;
52 }
53 if (mark != NULL)
54 {
55 if (data != NULL)
56 {
57 int len = (int)(mark - buffer);
58 memcpy(data, buffer, len);
59 data[len] = 0;
60 }
61
62 if (!strncmp(mark, "OK", 2))
63 return true;
64
65#ifdef UNICODE
623e2f0e 66 nxlog_debug(5, _T("SMS: non-OK response (%hs)"), mark);
9b9b8709 67#else
623e2f0e 68 nxlog_debug(5, _T("SMS: non-OK response (%s)"), mark);
9b9b8709
VK
69#endif
70 return false;
71 }
72 }
73}
74
75/**
76 * Initialize modem
77 */
78static bool InitModem(Serial *serial)
79{
80 serial->write("\x1A\r\n", 3); // in case of pending send operation
81 ReadToOK(serial);
82
83 serial->write("ATZ\r\n", 5); // init modem
84 if (!ReadToOK(serial))
85 return false;
623e2f0e 86 nxlog_debug(5, _T("SMS: ATZ sent, got OK"));
9b9b8709
VK
87
88 serial->write("ATE0\r\n", 6); // disable echo
89 if (!ReadToOK(serial))
90 return false;
623e2f0e 91 nxlog_debug(5, _T("SMS: ATE0 sent, got OK"));
9b9b8709
VK
92
93 return true;
94}
5039dede 95
9b9b8709
VK
96/**
97 * Initialize driver
98 *
fce42f5b 99 * pszInitArgs format: portname,speed,databits,parity,stopbits,mode
9b9b8709 100 */
f19168bd 101extern "C" bool EXPORT SMSDriverInit(const TCHAR *pszInitArgs, Config *config)
5039dede 102{
9f24efb3 103 TCHAR *portName;
5039dede
AK
104
105 if (pszInitArgs == NULL || *pszInitArgs == 0)
106 {
107#ifdef _WIN32
9f24efb3 108 portName = _tcsdup(_T("COM1:"));
5039dede 109#else
9f24efb3 110 portName = _tcsdup(_T("/dev/ttyS0"));
5039dede
AK
111#endif
112 }
113 else
114 {
9f24efb3 115 portName = _tcsdup(pszInitArgs);
5039dede
AK
116 }
117
623e2f0e 118 nxlog_debug(1, _T("Loading Generic SMS Driver (configuration: %s)"), pszInitArgs);
5039dede 119
9f24efb3 120 TCHAR *p;
9b9b8709 121 const TCHAR *parityAsText;
5039dede
AK
122 int portSpeed = 9600;
123 int dataBits = 8;
124 int parity = NOPARITY;
125 int stopBits = ONESTOPBIT;
126
9f24efb3 127 if ((p = _tcschr(portName, _T(','))) != NULL)
5039dede
AK
128 {
129 *p = 0; p++;
9f24efb3 130 int tmp = _tcstol(p, NULL, 10);
5039dede
AK
131 if (tmp != 0)
132 {
133 portSpeed = tmp;
134
9f24efb3 135 if ((p = _tcschr(p, _T(','))) != NULL)
5039dede
AK
136 {
137 *p = 0; p++;
9f24efb3 138 tmp = _tcstol(p, NULL, 10);
5039dede
AK
139 if (tmp >= 5 && tmp <= 8)
140 {
141 dataBits = tmp;
142
143 // parity
9f24efb3 144 if ((p = _tcschr(p, _T(','))) != NULL)
5039dede
AK
145 {
146 *p = 0; p++;
147 switch (tolower(*p))
148 {
9479b6d0
VK
149 case _T('n'): // none
150 parity = NOPARITY;
151 break;
152 case _T('o'): // odd
153 parity = ODDPARITY;
154 break;
155 case _T('e'): // even
156 parity = EVENPARITY;
157 break;
5039dede
AK
158 }
159
160 // stop bits
9f24efb3 161 if ((p = _tcschr(p, _T(','))) != NULL)
5039dede
AK
162 {
163 *p = 0; p++;
164
9f24efb3 165 if (*p == _T('2'))
5039dede
AK
166 {
167 stopBits = TWOSTOPBITS;
168 }
17a4c8ab
AK
169
170 // Text or PDU mode
171 if ((p = _tcschr(p, _T(','))) != NULL)
172 {
173 *p = 0; p++;
0dbd8390 174 if (*p == _T('T'))
fce42f5b 175 {
9b9b8709 176 s_operationMode = OM_TEXT;
fce42f5b
VK
177 }
178 else if (*p == _T('N'))
179 {
180 s_operationMode = OM_TEXT;
181 s_cmgsUseQuotes = false;
182 }
17a4c8ab 183 else if (*p == _T('P'))
fce42f5b 184 {
9b9b8709 185 s_operationMode = OM_PDU;
fce42f5b 186 }
17a4c8ab 187 }
5039dede
AK
188 }
189 }
190 }
191 }
192 }
193 }
194
195 switch (parity)
196 {
9f24efb3 197 case ODDPARITY:
9b9b8709 198 parityAsText = _T("ODD");
9f24efb3
VK
199 break;
200 case EVENPARITY:
9b9b8709 201 parityAsText = _T("EVEN");
9f24efb3
VK
202 break;
203 default:
9b9b8709 204 parityAsText = _T("NONE");
9f24efb3 205 break;
5039dede 206 }
fce42f5b 207 nxlog_debug(2, _T("SMS init: port=\"%s\", speed=%d, data=%d, parity=%s, stop=%d, pduMode=%s, numberInQuotes=%s"),
9479b6d0 208 portName, portSpeed, dataBits, parityAsText, stopBits == TWOSTOPBITS ? 2 : 1,
fce42f5b
VK
209 (s_operationMode == OM_PDU) ? _T("true") : _T("false"),
210 s_cmgsUseQuotes ? _T("true") : _T("false"));
5039dede 211
9b9b8709 212 if (s_serial.open(portName))
5039dede 213 {
623e2f0e 214 nxlog_debug(5, _T("SMS: port opened"));
9b9b8709 215 s_serial.setTimeout(2000);
5039dede 216
9b9b8709
VK
217 if (!s_serial.set(portSpeed, dataBits, parity, stopBits))
218 {
623e2f0e 219 nxlog_debug(0, _T("SMS: cannot configure serial port %s"), pszInitArgs);
9b9b8709
VK
220 goto cleanup;
221 }
222
223 if (!InitModem(&s_serial))
224 goto cleanup;
225
5039dede
AK
226 // enter PIN: AT+CPIN="xxxx"
227 // register network: AT+CREG1
228
9b9b8709
VK
229 s_serial.write("ATI3\r\n", 6); // read vendor id
230 char vendorId[1024];
231 if (!ReadToOK(&s_serial, vendorId))
232 goto cleanup;
623e2f0e 233 nxlog_debug(5, _T("SMS init: ATI3 sent, got OK"));
5039dede 234
9b9b8709
VK
235 char *sptr, *eptr;
236 for(sptr = vendorId; (*sptr != 0) && ((*sptr == '\r') || (*sptr == '\n') || (*sptr == ' ') || (*sptr == '\t')); sptr++);
237 for(eptr = sptr; (*eptr != 0) && (*eptr != '\r') && (*eptr != '\n'); eptr++);
238 *eptr = 0;
623e2f0e 239 nxlog_debug(0, _T("SMS: GSM modem found on %s: %hs"), pszInitArgs, sptr);
5039dede
AK
240 }
241 else
242 {
623e2f0e 243 nxlog_debug(0, _T("SMS: cannot open serial port %s"), pszInitArgs);
5039dede 244 }
9b9b8709
VK
245
246cleanup:
247 safe_free(portName);
248 s_serial.close();
f19168bd 249 return true; // return TRUE always to keep driver in memory
5039dede
AK
250}
251
9b9b8709
VK
252/**
253 * Send SMS
254 */
f19168bd 255extern "C" bool EXPORT SMSDriverSend(const TCHAR *pszPhoneNumber, const TCHAR *pszText)
5039dede 256{
9b9b8709 257 if ((pszPhoneNumber == NULL) || (pszText == NULL))
f19168bd 258 return false;
17a4c8ab 259
623e2f0e 260 nxlog_debug(3, _T("SMS: send to {%s}: {%s}"), pszPhoneNumber, pszText);
9b9b8709
VK
261 if (!s_serial.restart())
262 {
623e2f0e 263 nxlog_debug(5, _T("SMS: failed to open port"));
f19168bd 264 return false;
9b9b8709
VK
265 }
266
f19168bd 267 bool success = false;
9b9b8709
VK
268 if (!InitModem(&s_serial))
269 goto cleanup;
270
271 if (s_operationMode == OM_PDU)
272 {
273 s_serial.write("AT+CMGF=0\r\n", 11); // =0 - PDU message
274 if (!ReadToOK(&s_serial))
275 goto cleanup;
623e2f0e 276 nxlog_debug(5, _T("SMS: AT+CMGF=0 sent, got OK"));
9b9b8709
VK
277
278 char pduBuffer[PDU_BUFFER_SIZE];
9f24efb3 279#ifdef UNICODE
9b9b8709
VK
280 char mbPhoneNumber[128], mbText[161];
281
282 WideCharToMultiByte(CP_ACP, WC_DEFAULTCHAR | WC_COMPOSITECHECK, pszPhoneNumber, -1, mbPhoneNumber, 128, NULL, NULL);
283 mbPhoneNumber[127] = 0;
284
285 WideCharToMultiByte(CP_ACP, WC_DEFAULTCHAR | WC_COMPOSITECHECK, pszText, -1, mbText, 161, NULL, NULL);
286 mbText[160] = 0;
287
288 SMSCreatePDUString(mbPhoneNumber, mbText, pduBuffer);
9f24efb3 289#else
9b9b8709 290 SMSCreatePDUString(pszPhoneNumber, pszText, pduBuffer);
9f24efb3 291#endif
5039dede 292
9b9b8709
VK
293 char buffer[256];
294 snprintf(buffer, sizeof(buffer), "AT+CMGS=%d\r\n", (int)strlen(pduBuffer) / 2 - 1);
295 s_serial.write(buffer, (int)strlen(buffer));
770f8c64 296
9b9b8709
VK
297 char *mark;
298 if (s_serial.readToMark(buffer, sizeof(buffer), s_eosMarksSend, &mark) <= 0)
299 goto cleanup;
300 if ((mark == NULL) || (*mark != '>'))
301 {
fce42f5b 302 nxlog_debug(5, _T("SMS: wrong response to AT+CMGS=%d (%hs)"), (int)strlen(pduBuffer) / 2 - 1, mark);
9b9b8709
VK
303 goto cleanup;
304 }
770f8c64 305
9b9b8709
VK
306 s_serial.write(pduBuffer, (int)strlen(pduBuffer)); // send PDU
307 s_serial.write("\x1A\r\n", 3); // send ^Z
308 }
309 else
310 {
311 s_serial.write("AT+CMGF=1\r\n", 11); // =1 - text message
312 if (!ReadToOK(&s_serial))
313 goto cleanup;
623e2f0e 314 nxlog_debug(5, _T("SMS: AT+CMGF=1 sent, got OK"));
9b9b8709
VK
315
316 char buffer[256];
cbc290b0 317#ifdef UNICODE
9b9b8709
VK
318 char mbPhoneNumber[128];
319 WideCharToMultiByte(CP_ACP, WC_DEFAULTCHAR | WC_COMPOSITECHECK, pszPhoneNumber, -1, mbPhoneNumber, 128, NULL, NULL);
320 mbPhoneNumber[127] = 0;
fce42f5b
VK
321 if (s_cmgsUseQuotes)
322 snprintf(buffer, sizeof(buffer), "AT+CMGS=\"%s\"\r\n", mbPhoneNumber);
323 else
324 snprintf(buffer, sizeof(buffer), "AT+CMGS=%s\r\n", mbPhoneNumber);
cbc290b0 325#else
fce42f5b
VK
326 if (s_cmgsUseQuotes)
327 snprintf(buffer, sizeof(buffer), "AT+CMGS=\"%s\"\r\n", pszPhoneNumber);
328 else
329 snprintf(buffer, sizeof(buffer), "AT+CMGS=%s\r\n", pszPhoneNumber);
cbc290b0 330#endif
9b9b8709 331 s_serial.write(buffer, (int)strlen(buffer)); // set number
7653c4fb 332
9b9b8709
VK
333 char *mark;
334 if (s_serial.readToMark(buffer, sizeof(buffer), s_eosMarksSend, &mark) <= 0)
335 goto cleanup;
336 if ((mark == NULL) || (*mark != '>'))
337 {
623e2f0e 338 nxlog_debug(5, _T("SMS: wrong response to AT+CMGS=\"%hs\" (%hs)"), pszPhoneNumber, mark);
9b9b8709
VK
339 goto cleanup;
340 }
341
342#ifdef UNICODE
343 char mbText[161];
344 WideCharToMultiByte(CP_ACP, WC_DEFAULTCHAR | WC_COMPOSITECHECK, pszText, -1, mbText, 161, NULL, NULL);
345 mbText[160] = 0;
346 snprintf(buffer, sizeof(buffer), "%s\x1A\r\n", mbText);
347#else
348 if (strlen(pszText) <= 160)
349 {
350 snprintf(buffer, sizeof(buffer), "%s\x1A\r\n", pszText);
351 }
352 else
353 {
354 strncpy(buffer, pszText, 160);
355 strcpy(&buffer[160], "\x1A\r\n");
356 }
357#endif
358 s_serial.write(buffer, (int)strlen(buffer)); // send text, end with ^Z
359 }
7653c4fb 360
9b9b8709
VK
361 s_serial.setTimeout(30000);
362 if (!ReadToOK(&s_serial))
363 goto cleanup;
7653c4fb 364
623e2f0e 365 nxlog_debug(5, _T("SMS: AT+CMGS + message body sent, got OK"));
f19168bd 366 success = true;
770f8c64 367
9b9b8709
VK
368cleanup:
369 s_serial.setTimeout(2000);
370 s_serial.close();
371 return success;
770f8c64
AK
372}
373
826fcc1f
VK
374/**
375 * Driver unload handler
376 */
9f24efb3 377extern "C" void EXPORT SMSDriverUnload()
5039dede 378{
5039dede
AK
379}
380
9b9b8709
VK
381#ifdef _WIN32
382
826fcc1f
VK
383/**
384 * DLL Entry point
385 */
5039dede
AK
386BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
387{
388 if (dwReason == DLL_PROCESS_ATTACH)
389 DisableThreadLibraryCalls(hInstance);
390 return TRUE;
391}
392
393#endif