fixed bugs in generic SMS driver; added configurable option for serial write block...
[public/netxms.git] / src / smsdrv / generic / main.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Generic SMS driver
4 ** Copyright (C) 2003-2016 Raden Solutions
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 **/
23
24 #include "generic_smsdrv.h"
25
26 bool SMSCreatePDUString(const char* phoneNumber, const char* message, char* pduBuffer);
27
28 /**
29 * Static data
30 */
31 static Serial s_serial;
32 static const char *s_eosMarks[] = { "OK", "ERROR", NULL };
33 static const char *s_eosMarksSend[] = { ">", "ERROR", NULL };
34 static enum { OM_TEXT, OM_PDU } s_operationMode = OM_TEXT;
35 static bool s_cmgsUseQuotes = true;
36
37 /**
38 * Normalize text buffer (remove non-printable characters)
39 */
40 static void NormalizeText(char *text)
41 {
42 for(int i = 0; text[i] != 0; i++)
43 if (text[i] < ' ')
44 text[i] = ' ';
45 }
46
47 /**
48 * Read input to OK
49 */
50 static bool ReadToOK(Serial *serial, char *data = NULL)
51 {
52 char buffer[1024];
53 memset(buffer, 0, 1024);
54 while(true)
55 {
56 char *mark;
57 int rc = serial->readToMark(buffer, 1024, s_eosMarks, &mark);
58 if (rc <= 0)
59 {
60 nxlog_debug(5, _T("SMS: ReadToOK: readToMark returned %d"), rc);
61 return false;
62 }
63 NormalizeText(buffer);
64 nxlog_debug(5, _T("SMS: ReadToOK buffer content: '%hs'"), buffer);
65 if (mark != NULL)
66 {
67 if (data != NULL)
68 {
69 int len = (int)(mark - buffer);
70 memcpy(data, buffer, len);
71 data[len] = 0;
72 }
73
74 if (!strncmp(mark, "OK", 2))
75 return true;
76
77 #ifdef UNICODE
78 nxlog_debug(5, _T("SMS: non-OK response (%hs)"), mark);
79 #else
80 nxlog_debug(5, _T("SMS: non-OK response (%s)"), mark);
81 #endif
82 return false;
83 }
84 }
85 }
86
87 /**
88 * Initialize modem
89 */
90 static bool InitModem(Serial *serial)
91 {
92 serial->write("\x1A\r\n", 3); // in case of pending send operation
93 ReadToOK(serial);
94
95 serial->write("ATZ\r\n", 5); // init modem
96 if (!ReadToOK(serial))
97 return false;
98 nxlog_debug(5, _T("SMS: ATZ sent, got OK"));
99
100 serial->write("ATE0\r\n", 6); // disable echo
101 if (!ReadToOK(serial))
102 return false;
103 nxlog_debug(5, _T("SMS: ATE0 sent, got OK"));
104
105 return true;
106 }
107
108 /**
109 * Initialize driver
110 *
111 * pszInitArgs format: portname,speed,databits,parity,stopbits,mode,blocksize,writedelay
112 */
113 extern "C" bool EXPORT SMSDriverInit(const TCHAR *pszInitArgs, Config *config)
114 {
115 TCHAR *portName;
116
117 if (pszInitArgs == NULL || *pszInitArgs == 0)
118 {
119 #ifdef _WIN32
120 portName = _tcsdup(_T("COM1:"));
121 #else
122 portName = _tcsdup(_T("/dev/ttyS0"));
123 #endif
124 }
125 else
126 {
127 portName = _tcsdup(pszInitArgs);
128 }
129
130 nxlog_debug(1, _T("Loading Generic SMS Driver (configuration: %s)"), pszInitArgs);
131
132 TCHAR *p;
133 const TCHAR *parityAsText;
134 int portSpeed = 9600;
135 int dataBits = 8;
136 int parity = NOPARITY;
137 int stopBits = ONESTOPBIT;
138 int blockSize = 8;
139 int writeDelay = 100;
140
141 if ((p = _tcschr(portName, _T(','))) != NULL)
142 {
143 *p = 0; p++;
144 int tmp = _tcstol(p, NULL, 10);
145 if (tmp != 0)
146 {
147 portSpeed = tmp;
148
149 if ((p = _tcschr(p, _T(','))) != NULL)
150 {
151 *p = 0; p++;
152 tmp = _tcstol(p, NULL, 10);
153 if (tmp >= 5 && tmp <= 8)
154 {
155 dataBits = tmp;
156
157 // parity
158 if ((p = _tcschr(p, _T(','))) != NULL)
159 {
160 *p = 0; p++;
161 switch (tolower(*p))
162 {
163 case _T('n'): // none
164 parity = NOPARITY;
165 break;
166 case _T('o'): // odd
167 parity = ODDPARITY;
168 break;
169 case _T('e'): // even
170 parity = EVENPARITY;
171 break;
172 }
173
174 // stop bits
175 if ((p = _tcschr(p, _T(','))) != NULL)
176 {
177 *p = 0; p++;
178
179 if (*p == _T('2'))
180 {
181 stopBits = TWOSTOPBITS;
182 }
183
184 // Text or PDU mode
185 if ((p = _tcschr(p, _T(','))) != NULL)
186 {
187 *p = 0; p++;
188 if (*p == _T('T'))
189 {
190 s_operationMode = OM_TEXT;
191 }
192 else if (*p == _T('P'))
193 {
194 s_operationMode = OM_PDU;
195 }
196
197 // Use quotes
198 if ((p = _tcschr(p, _T(','))) != NULL)
199 {
200 *p = 0; p++;
201 if (*p == _T('N'))
202 {
203 s_cmgsUseQuotes = false;
204 }
205 else
206 {
207 s_cmgsUseQuotes = true;
208 }
209
210 // block size
211 if ((p = _tcschr(p, _T(','))) != NULL)
212 {
213 *p = 0; p++;
214 blockSize = _tcstol(p, NULL, 10);
215 if ((blockSize < 1) || (blockSize > 256))
216 blockSize = 8;
217
218 // write delay
219 if ((p = _tcschr(p, _T(','))) != NULL)
220 {
221 *p = 0; p++;
222 writeDelay = _tcstol(p, NULL, 10);
223 if ((writeDelay < 1) || (writeDelay > 10000))
224 writeDelay = 100;
225 }
226 }
227 }
228 }
229 }
230 }
231 }
232 }
233 }
234 }
235
236 switch (parity)
237 {
238 case ODDPARITY:
239 parityAsText = _T("ODD");
240 break;
241 case EVENPARITY:
242 parityAsText = _T("EVEN");
243 break;
244 default:
245 parityAsText = _T("NONE");
246 break;
247 }
248 nxlog_debug(2, _T("SMS init: port=\"%s\", speed=%d, data=%d, parity=%s, stop=%d, pduMode=%s, numberInQuotes=%s blockSize=%d writeDelay=%d"),
249 portName, portSpeed, dataBits, parityAsText, stopBits == TWOSTOPBITS ? 2 : 1,
250 (s_operationMode == OM_PDU) ? _T("true") : _T("false"),
251 s_cmgsUseQuotes ? _T("true") : _T("false"), blockSize, writeDelay);
252
253 s_serial.setWriteBlockSize(blockSize);
254 s_serial.setWriteDelay(writeDelay);
255
256 if (s_serial.open(portName))
257 {
258 nxlog_debug(5, _T("SMS: port opened"));
259 s_serial.setTimeout(2000);
260
261 if (!s_serial.set(portSpeed, dataBits, parity, stopBits))
262 {
263 nxlog_debug(0, _T("SMS: cannot configure serial port %s"), pszInitArgs);
264 goto cleanup;
265 }
266
267 if (!InitModem(&s_serial))
268 goto cleanup;
269
270 // enter PIN: AT+CPIN="xxxx"
271 // register network: AT+CREG1
272
273 s_serial.write("ATI3\r\n", 6); // read vendor id
274 char vendorId[1024];
275 if (!ReadToOK(&s_serial, vendorId))
276 goto cleanup;
277 nxlog_debug(5, _T("SMS init: ATI3 sent, got OK"));
278
279 char *sptr, *eptr;
280 for(sptr = vendorId; (*sptr != 0) && ((*sptr == '\r') || (*sptr == '\n') || (*sptr == ' ') || (*sptr == '\t')); sptr++);
281 for(eptr = sptr; (*eptr != 0) && (*eptr != '\r') && (*eptr != '\n'); eptr++);
282 *eptr = 0;
283 nxlog_debug(0, _T("SMS: GSM modem found on %s: %hs"), pszInitArgs, sptr);
284 }
285 else
286 {
287 nxlog_debug(0, _T("SMS: cannot open serial port %s"), pszInitArgs);
288 }
289
290 cleanup:
291 safe_free(portName);
292 s_serial.close();
293 return true; // return TRUE always to keep driver in memory
294 }
295
296 /**
297 * Send SMS
298 */
299 extern "C" bool EXPORT SMSDriverSend(const TCHAR *pszPhoneNumber, const TCHAR *pszText)
300 {
301 if ((pszPhoneNumber == NULL) || (pszText == NULL))
302 return false;
303
304 nxlog_debug(3, _T("SMS: send to {%s}: {%s}"), pszPhoneNumber, pszText);
305 if (!s_serial.restart())
306 {
307 nxlog_debug(5, _T("SMS: failed to open port"));
308 return false;
309 }
310
311 bool success = false;
312 if (!InitModem(&s_serial))
313 goto cleanup;
314
315 if (s_operationMode == OM_PDU)
316 {
317 s_serial.write("AT+CMGF=0\r\n", 11); // =0 - PDU message
318 if (!ReadToOK(&s_serial))
319 goto cleanup;
320 nxlog_debug(5, _T("SMS: AT+CMGF=0 sent, got OK"));
321
322 char pduBuffer[PDU_BUFFER_SIZE];
323 #ifdef UNICODE
324 char mbPhoneNumber[128], mbText[161];
325
326 WideCharToMultiByte(CP_ACP, WC_DEFAULTCHAR | WC_COMPOSITECHECK, pszPhoneNumber, -1, mbPhoneNumber, 128, NULL, NULL);
327 mbPhoneNumber[127] = 0;
328
329 WideCharToMultiByte(CP_ACP, WC_DEFAULTCHAR | WC_COMPOSITECHECK, pszText, -1, mbText, 161, NULL, NULL);
330 mbText[160] = 0;
331
332 SMSCreatePDUString(mbPhoneNumber, mbText, pduBuffer);
333 #else
334 SMSCreatePDUString(pszPhoneNumber, pszText, pduBuffer);
335 #endif
336
337 char buffer[256];
338 snprintf(buffer, sizeof(buffer), "AT+CMGS=%d\r\n", (int)strlen(pduBuffer) / 2 - 1);
339 s_serial.write(buffer, (int)strlen(buffer));
340
341 char *mark;
342 if (s_serial.readToMark(buffer, sizeof(buffer), s_eosMarksSend, &mark) <= 0)
343 goto cleanup;
344 if ((mark == NULL) || (*mark != '>'))
345 {
346 NormalizeText(buffer);
347 nxlog_debug(5, _T("SMS: wrong response to AT+CMGS=%d (%hs)"), (int)strlen(pduBuffer) / 2 - 1, mark);
348 goto cleanup;
349 }
350
351 s_serial.write(pduBuffer, (int)strlen(pduBuffer)); // send PDU
352 s_serial.write("\x1A", 1); // send ^Z
353 }
354 else
355 {
356 s_serial.write("AT+CMGF=1\r\n", 11); // =1 - text message
357 if (!ReadToOK(&s_serial))
358 goto cleanup;
359 nxlog_debug(5, _T("SMS: AT+CMGF=1 sent, got OK"));
360
361 char buffer[256];
362 #ifdef UNICODE
363 char mbPhoneNumber[128];
364 WideCharToMultiByte(CP_ACP, WC_DEFAULTCHAR | WC_COMPOSITECHECK, pszPhoneNumber, -1, mbPhoneNumber, 128, NULL, NULL);
365 mbPhoneNumber[127] = 0;
366 if (s_cmgsUseQuotes)
367 snprintf(buffer, sizeof(buffer), "AT+CMGS=\"%s\"\r\n", mbPhoneNumber);
368 else
369 snprintf(buffer, sizeof(buffer), "AT+CMGS=%s\r\n", mbPhoneNumber);
370 #else
371 if (s_cmgsUseQuotes)
372 snprintf(buffer, sizeof(buffer), "AT+CMGS=\"%s\"\r\n", pszPhoneNumber);
373 else
374 snprintf(buffer, sizeof(buffer), "AT+CMGS=%s\r\n", pszPhoneNumber);
375 #endif
376 s_serial.write(buffer, (int)strlen(buffer)); // set number
377
378 char *mark;
379 if (s_serial.readToMark(buffer, sizeof(buffer), s_eosMarksSend, &mark) <= 0)
380 goto cleanup;
381 if ((mark == NULL) || (*mark != '>'))
382 {
383 NormalizeText(buffer);
384 nxlog_debug(5, _T("SMS: wrong response to AT+CMGS=\"%hs\" (%hs)"), pszPhoneNumber, mark);
385 goto cleanup;
386 }
387
388 #ifdef UNICODE
389 char mbText[161];
390 WideCharToMultiByte(CP_ACP, WC_DEFAULTCHAR | WC_COMPOSITECHECK, pszText, -1, mbText, 161, NULL, NULL);
391 mbText[160] = 0;
392 snprintf(buffer, sizeof(buffer), "%s\x1A", mbText);
393 #else
394 if (strlen(pszText) <= 160)
395 {
396 snprintf(buffer, sizeof(buffer), "%s\x1A", pszText);
397 }
398 else
399 {
400 strncpy(buffer, pszText, 160);
401 strcpy(&buffer[160], "\x1A");
402 }
403 #endif
404 s_serial.write(buffer, (int)strlen(buffer)); // send text, end with ^Z
405 }
406
407 s_serial.setTimeout(30000);
408 if (!ReadToOK(&s_serial))
409 goto cleanup;
410
411 nxlog_debug(5, _T("SMS: AT+CMGS + message body sent, got OK"));
412 success = true;
413
414 cleanup:
415 s_serial.setTimeout(2000);
416 s_serial.close();
417 nxlog_debug(5, _T("SMS: serial port closed"));
418 return success;
419 }
420
421 /**
422 * Driver unload handler
423 */
424 extern "C" void EXPORT SMSDriverUnload()
425 {
426 }
427
428 #ifdef _WIN32
429
430 /**
431 * DLL Entry point
432 */
433 BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
434 {
435 if (dwReason == DLL_PROCESS_ATTACH)
436 DisableThreadLibraryCalls(hInstance);
437 return TRUE;
438 }
439
440 #endif