5d24b602c7c6c7b7ed655fbc128adc96e8a34c47
[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 * Read input to OK
39 */
40 static 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 {
50 nxlog_debug(5, _T("SMS: ReadToOK: readToMark returned %d"), rc);
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
66 nxlog_debug(5, _T("SMS: non-OK response (%hs)"), mark);
67 #else
68 nxlog_debug(5, _T("SMS: non-OK response (%s)"), mark);
69 #endif
70 return false;
71 }
72 }
73 }
74
75 /**
76 * Initialize modem
77 */
78 static 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;
86 nxlog_debug(5, _T("SMS: ATZ sent, got OK"));
87
88 serial->write("ATE0\r\n", 6); // disable echo
89 if (!ReadToOK(serial))
90 return false;
91 nxlog_debug(5, _T("SMS: ATE0 sent, got OK"));
92
93 return true;
94 }
95
96 /**
97 * Initialize driver
98 *
99 * pszInitArgs format: portname,speed,databits,parity,stopbits,mode
100 */
101 extern "C" bool EXPORT SMSDriverInit(const TCHAR *pszInitArgs, Config *config)
102 {
103 TCHAR *portName;
104
105 if (pszInitArgs == NULL || *pszInitArgs == 0)
106 {
107 #ifdef _WIN32
108 portName = _tcsdup(_T("COM1:"));
109 #else
110 portName = _tcsdup(_T("/dev/ttyS0"));
111 #endif
112 }
113 else
114 {
115 portName = _tcsdup(pszInitArgs);
116 }
117
118 nxlog_debug(1, _T("Loading Generic SMS Driver (configuration: %s)"), pszInitArgs);
119
120 TCHAR *p;
121 const TCHAR *parityAsText;
122 int portSpeed = 9600;
123 int dataBits = 8;
124 int parity = NOPARITY;
125 int stopBits = ONESTOPBIT;
126
127 if ((p = _tcschr(portName, _T(','))) != NULL)
128 {
129 *p = 0; p++;
130 int tmp = _tcstol(p, NULL, 10);
131 if (tmp != 0)
132 {
133 portSpeed = tmp;
134
135 if ((p = _tcschr(p, _T(','))) != NULL)
136 {
137 *p = 0; p++;
138 tmp = _tcstol(p, NULL, 10);
139 if (tmp >= 5 && tmp <= 8)
140 {
141 dataBits = tmp;
142
143 // parity
144 if ((p = _tcschr(p, _T(','))) != NULL)
145 {
146 *p = 0; p++;
147 switch (tolower(*p))
148 {
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;
158 }
159
160 // stop bits
161 if ((p = _tcschr(p, _T(','))) != NULL)
162 {
163 *p = 0; p++;
164
165 if (*p == _T('2'))
166 {
167 stopBits = TWOSTOPBITS;
168 }
169
170 // Text or PDU mode
171 if ((p = _tcschr(p, _T(','))) != NULL)
172 {
173 *p = 0; p++;
174 if (*p == _T('T'))
175 {
176 s_operationMode = OM_TEXT;
177 }
178 else if (*p == _T('N'))
179 {
180 s_operationMode = OM_TEXT;
181 s_cmgsUseQuotes = false;
182 }
183 else if (*p == _T('P'))
184 {
185 s_operationMode = OM_PDU;
186 }
187 }
188 }
189 }
190 }
191 }
192 }
193 }
194
195 switch (parity)
196 {
197 case ODDPARITY:
198 parityAsText = _T("ODD");
199 break;
200 case EVENPARITY:
201 parityAsText = _T("EVEN");
202 break;
203 default:
204 parityAsText = _T("NONE");
205 break;
206 }
207 nxlog_debug(2, _T("SMS init: port=\"%s\", speed=%d, data=%d, parity=%s, stop=%d, pduMode=%s, numberInQuotes=%s"),
208 portName, portSpeed, dataBits, parityAsText, stopBits == TWOSTOPBITS ? 2 : 1,
209 (s_operationMode == OM_PDU) ? _T("true") : _T("false"),
210 s_cmgsUseQuotes ? _T("true") : _T("false"));
211
212 if (s_serial.open(portName))
213 {
214 nxlog_debug(5, _T("SMS: port opened"));
215 s_serial.setTimeout(2000);
216
217 if (!s_serial.set(portSpeed, dataBits, parity, stopBits))
218 {
219 nxlog_debug(0, _T("SMS: cannot configure serial port %s"), pszInitArgs);
220 goto cleanup;
221 }
222
223 if (!InitModem(&s_serial))
224 goto cleanup;
225
226 // enter PIN: AT+CPIN="xxxx"
227 // register network: AT+CREG1
228
229 s_serial.write("ATI3\r\n", 6); // read vendor id
230 char vendorId[1024];
231 if (!ReadToOK(&s_serial, vendorId))
232 goto cleanup;
233 nxlog_debug(5, _T("SMS init: ATI3 sent, got OK"));
234
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;
239 nxlog_debug(0, _T("SMS: GSM modem found on %s: %hs"), pszInitArgs, sptr);
240 }
241 else
242 {
243 nxlog_debug(0, _T("SMS: cannot open serial port %s"), pszInitArgs);
244 }
245
246 cleanup:
247 safe_free(portName);
248 s_serial.close();
249 return true; // return TRUE always to keep driver in memory
250 }
251
252 /**
253 * Send SMS
254 */
255 extern "C" bool EXPORT SMSDriverSend(const TCHAR *pszPhoneNumber, const TCHAR *pszText)
256 {
257 if ((pszPhoneNumber == NULL) || (pszText == NULL))
258 return false;
259
260 nxlog_debug(3, _T("SMS: send to {%s}: {%s}"), pszPhoneNumber, pszText);
261 if (!s_serial.restart())
262 {
263 nxlog_debug(5, _T("SMS: failed to open port"));
264 return false;
265 }
266
267 bool success = false;
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;
276 nxlog_debug(5, _T("SMS: AT+CMGF=0 sent, got OK"));
277
278 char pduBuffer[PDU_BUFFER_SIZE];
279 #ifdef UNICODE
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);
289 #else
290 SMSCreatePDUString(pszPhoneNumber, pszText, pduBuffer);
291 #endif
292
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));
296
297 char *mark;
298 if (s_serial.readToMark(buffer, sizeof(buffer), s_eosMarksSend, &mark) <= 0)
299 goto cleanup;
300 if ((mark == NULL) || (*mark != '>'))
301 {
302 nxlog_debug(5, _T("SMS: wrong response to AT+CMGS=%d (%hs)"), (int)strlen(pduBuffer) / 2 - 1, mark);
303 goto cleanup;
304 }
305
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;
314 nxlog_debug(5, _T("SMS: AT+CMGF=1 sent, got OK"));
315
316 char buffer[256];
317 #ifdef UNICODE
318 char mbPhoneNumber[128];
319 WideCharToMultiByte(CP_ACP, WC_DEFAULTCHAR | WC_COMPOSITECHECK, pszPhoneNumber, -1, mbPhoneNumber, 128, NULL, NULL);
320 mbPhoneNumber[127] = 0;
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);
325 #else
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);
330 #endif
331 s_serial.write(buffer, (int)strlen(buffer)); // set number
332
333 char *mark;
334 if (s_serial.readToMark(buffer, sizeof(buffer), s_eosMarksSend, &mark) <= 0)
335 goto cleanup;
336 if ((mark == NULL) || (*mark != '>'))
337 {
338 nxlog_debug(5, _T("SMS: wrong response to AT+CMGS=\"%hs\" (%hs)"), pszPhoneNumber, mark);
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 }
360
361 s_serial.setTimeout(30000);
362 if (!ReadToOK(&s_serial))
363 goto cleanup;
364
365 nxlog_debug(5, _T("SMS: AT+CMGS + message body sent, got OK"));
366 success = true;
367
368 cleanup:
369 s_serial.setTimeout(2000);
370 s_serial.close();
371 return success;
372 }
373
374 /**
375 * Driver unload handler
376 */
377 extern "C" void EXPORT SMSDriverUnload()
378 {
379 }
380
381 #ifdef _WIN32
382
383 /**
384 * DLL Entry point
385 */
386 BOOL 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