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