Rev 62 | Rev 65 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
13 | rm5248 | 1 | #ifdef _WIN32 |
2 | #include <windows.h> |
||
3 | |||
55 | rm5248 | 4 | /* Make inline functions work */ |
5 | #define inline __inline |
||
6 | |||
13 | rm5248 | 7 | #define SPEED_SWITCH(SPD,io) case SPD: io.BaudRate = CBR_##SPD; break; |
23 | rm5248 | 8 | #define GET_SPEED_SWITCH(SPD,io) case CBR_##SPD: return SPD; |
13 | rm5248 | 9 | |
10 | #define GET_SERIAL_PORT_STRUCT( port, io_name ) DCB io_name = {0};\ |
||
11 | io_name.DCBlength = sizeof( io_name ); \ |
||
12 | if (!GetCommState( port, &io_name ) ) { \ |
||
27 | rm5248 | 13 | printf("bad get comm\n");\ |
13 | rm5248 | 14 | return -1;\ |
15 | } |
||
16 | #define SET_SERIAL_PORT_STRUCT( port, io_name ) if( !SetCommState( port, &io_name ) ){\ |
||
27 | rm5248 | 17 | printf("bad set comm\n");\ |
13 | rm5248 | 18 | return 0;\ |
19 | } |
||
8 | rm5248 | 20 | #else |
21 | #include <termio.h> |
||
22 | #include <termios.h> |
||
23 | #include <unistd.h> |
||
24 | #include <fcntl.h> |
||
14 | rm5248 | 25 | |
26 | #ifdef CRTSCTS |
||
27 | #define HW_FLOW CRTSCTS |
||
28 | #elif CNEW_RTSCTS |
||
29 | #define HW_FLOW CNEW_RTSCTS |
||
30 | #endif |
||
8 | rm5248 | 31 | |
20 | rm5248 | 32 | #define SPEED_SWITCH(SPD,io) case SPD: cfsetospeed( &io, B##SPD ); cfsetispeed( &io, B##SPD ); break; |
33 | #define GET_SPEED_SWITCH(SPD,io) case B##SPD: return SPD; |
||
13 | rm5248 | 34 | |
35 | |||
36 | #define GET_SERIAL_PORT_STRUCT( port, io_name ) struct termios io_name; \ |
||
37 | if( tcgetattr( port, &io_name ) < 0 ){ \ |
||
38 | return -1; \ |
||
39 | } |
||
40 | #define SET_SERIAL_PORT_STRUCT( port, io_name ) if( tcsetattr( port, TCSANOW, &io_name ) < 0 ){\ |
||
57 | rm5248 | 41 | return -1;\ |
13 | rm5248 | 42 | } |
8 | rm5248 | 43 | #endif |
13 | rm5248 | 44 | |
8 | rm5248 | 45 | #include <stdlib.h> |
46 | #include <errno.h> |
||
47 | #include <string.h> |
||
48 | |||
49 | // |
||
50 | // Local Includes |
||
51 | // |
||
52 | #include "com_rm5248_serial_SerialPort.h" |
||
18 | rm5248 | 53 | #include "com_rm5248_serial_SerialInputStream.h" |
54 | #include "com_rm5248_serial_SerialOutputStream.h" |
||
8 | rm5248 | 55 | |
56 | // |
||
57 | // Struct Definitions |
||
58 | // |
||
59 | struct port_descriptor{ |
||
13 | rm5248 | 60 | #ifdef _WIN32 |
8 | rm5248 | 61 | HANDLE port; |
62 | #else |
||
63 | int port; |
||
64 | #endif |
||
65 | }; |
||
66 | |||
67 | // |
||
68 | // Local Variables |
||
69 | // |
||
70 | struct port_descriptor** port_list = NULL; |
||
71 | int port_list_size; |
||
72 | |||
73 | // |
||
74 | // Helper Methods |
||
75 | // |
||
55 | rm5248 | 76 | static inline jint get_handle(JNIEnv * env, jobject obj){ |
8 | rm5248 | 77 | jfieldID fid; |
78 | jint array_pos; |
||
79 | jclass cls = (*env)->GetObjectClass( env, obj ); |
||
80 | |||
81 | fid = (*env)->GetFieldID( env, cls, "handle", "I" ); |
||
82 | if( fid == 0 ){ |
||
83 | return -1; |
||
84 | } |
||
85 | |||
18 | rm5248 | 86 | array_pos = (*env)->GetIntField( env, obj, fid ); |
8 | rm5248 | 87 | |
88 | return array_pos; |
||
89 | } |
||
90 | |||
55 | rm5248 | 91 | static inline jboolean get_bool( JNIEnv* env, jobject obj, const char* name ){ |
40 | rm5248 | 92 | jfieldID fid; |
93 | jboolean boolVal; |
||
94 | jclass cls = (*env)->GetObjectClass( env, obj ); |
||
95 | |||
96 | fid = (*env)->GetFieldID( env, cls, name, "Z" ); |
||
97 | if( fid == 0 ){ |
||
98 | return 0; //not really sure what error to set here... |
||
99 | } |
||
100 | |||
101 | boolVal = (*env)->GetBooleanField( env, obj, fid ); |
||
102 | |||
103 | return boolVal; |
||
104 | } |
||
105 | |||
55 | rm5248 | 106 | static inline int set_baud_rate( struct port_descriptor* desc, int baud_rate ){ |
13 | rm5248 | 107 | GET_SERIAL_PORT_STRUCT( desc->port, newio ); |
11 | rm5248 | 108 | |
109 | switch( baud_rate ){ |
||
13 | rm5248 | 110 | #ifndef _WIN32 |
111 | /* Note that Windows only supports speeds of 110 and above */ |
||
11 | rm5248 | 112 | SPEED_SWITCH(0,newio); |
113 | SPEED_SWITCH(50,newio); |
||
114 | SPEED_SWITCH(75,newio); |
||
13 | rm5248 | 115 | #endif |
11 | rm5248 | 116 | SPEED_SWITCH(110,newio); |
13 | rm5248 | 117 | #ifndef _WIN32 |
118 | /* Windows does not support speeds of 134, 150, or 200 */ |
||
11 | rm5248 | 119 | SPEED_SWITCH(134,newio); |
120 | SPEED_SWITCH(150,newio); |
||
121 | SPEED_SWITCH(200,newio); |
||
13 | rm5248 | 122 | #endif |
11 | rm5248 | 123 | SPEED_SWITCH(300,newio); |
124 | SPEED_SWITCH(600,newio); |
||
125 | SPEED_SWITCH(1200,newio); |
||
13 | rm5248 | 126 | #ifndef _WIN32 |
127 | /* Windows does not support 1800 */ |
||
11 | rm5248 | 128 | SPEED_SWITCH(1800,newio); |
13 | rm5248 | 129 | #endif |
11 | rm5248 | 130 | SPEED_SWITCH(2400,newio); |
131 | SPEED_SWITCH(4800,newio); |
||
132 | SPEED_SWITCH(9600,newio); |
||
133 | SPEED_SWITCH(19200,newio); |
||
134 | SPEED_SWITCH(38400,newio); |
||
135 | SPEED_SWITCH(115200,newio); |
||
136 | } |
||
137 | |||
13 | rm5248 | 138 | SET_SERIAL_PORT_STRUCT( desc->port, newio ); |
139 | |||
140 | return 1; |
||
141 | } |
||
142 | |||
55 | rm5248 | 143 | static inline int set_raw_input( struct port_descriptor* desc ){ |
14 | rm5248 | 144 | GET_SERIAL_PORT_STRUCT( desc->port, newio ); |
145 | |||
146 | #ifdef _WIN32 |
||
23 | rm5248 | 147 | newio.fBinary = TRUE; |
148 | newio.fParity = TRUE; |
||
149 | newio.fOutxCtsFlow = FALSE; |
||
150 | newio.fOutxDsrFlow = FALSE; |
||
151 | newio.fDtrControl = DTR_CONTROL_DISABLE; |
||
152 | newio.fDsrSensitivity = FALSE; |
||
153 | newio.fOutX = FALSE; |
||
154 | newio.fInX = FALSE; |
||
155 | newio.fNull = FALSE; |
||
156 | newio.fRtsControl = FALSE; |
||
157 | |||
158 | //Set the timeouts |
||
159 | { |
||
160 | COMMTIMEOUTS timeouts = {0}; |
||
27 | rm5248 | 161 | timeouts.ReadIntervalTimeout = MAXDWORD; |
23 | rm5248 | 162 | timeouts.ReadTotalTimeoutMultiplier = 0; |
163 | timeouts.ReadTotalTimeoutConstant = 0; |
||
27 | rm5248 | 164 | timeouts.WriteTotalTimeoutMultiplier = 0; |
165 | timeouts.WriteTotalTimeoutConstant = 0; |
||
23 | rm5248 | 166 | if( SetCommTimeouts( desc->port, &timeouts ) == 0 ){ |
167 | printf("bad timeout\n"); fflush(stdout);} |
||
168 | } |
||
14 | rm5248 | 169 | #else |
170 | newio.c_iflag |= IGNBRK; |
||
171 | newio.c_iflag &= ~BRKINT; |
||
172 | newio.c_iflag &= ~ICRNL; |
||
173 | newio.c_oflag = 0; |
||
174 | newio.c_lflag = 0; |
||
175 | newio.c_cc[VTIME] = 0; |
||
176 | newio.c_cc[VMIN] = 1; |
||
177 | #endif |
||
178 | |||
179 | SET_SERIAL_PORT_STRUCT( desc->port, newio ); |
||
180 | |||
181 | return 1; |
||
182 | } |
||
183 | |||
23 | rm5248 | 184 | /** |
185 | * @param data_bits The number of data bits |
||
186 | */ |
||
55 | rm5248 | 187 | static inline int set_data_bits( struct port_descriptor* desc, int data_bits ){ |
13 | rm5248 | 188 | GET_SERIAL_PORT_STRUCT( desc->port, newio ); |
189 | |||
190 | #ifdef _WIN32 |
||
191 | newio.ByteSize = data_bits; |
||
192 | #else |
||
14 | rm5248 | 193 | newio.c_cflag &= ~CSIZE; |
194 | if( data_bits == 8 ){ |
||
195 | newio.c_cflag |= CS8; |
||
196 | }else if( data_bits == 7 ){ |
||
197 | newio.c_cflag |= CS7; |
||
198 | }else if( data_bits == 6 ){ |
||
199 | newio.c_cflag |= CS6; |
||
200 | }else if( data_bits == 5 ){ |
||
201 | newio.c_cflag |= CS5; |
||
202 | } |
||
13 | rm5248 | 203 | #endif |
204 | |||
205 | SET_SERIAL_PORT_STRUCT( desc->port, newio ); |
||
206 | |||
207 | return 1; |
||
208 | } |
||
209 | |||
23 | rm5248 | 210 | /** |
211 | * @param stop_bits 1 for 1, 2 for 2 |
||
212 | */ |
||
55 | rm5248 | 213 | static inline int set_stop_bits( struct port_descriptor* desc, int stop_bits ){ |
13 | rm5248 | 214 | GET_SERIAL_PORT_STRUCT( desc->port, newio ); |
215 | |||
216 | #ifdef _WIN32 |
||
217 | if( stop_bits == 1 ){ |
||
218 | newio.StopBits = ONESTOPBIT; |
||
219 | }else if( stop_bits == 2 ){ |
||
220 | newio.StopBits = TWOSTOPBITS; |
||
11 | rm5248 | 221 | } |
13 | rm5248 | 222 | #else |
14 | rm5248 | 223 | if( stop_bits == 1 ){ |
224 | newio.c_cflag &= ~CSTOPB; |
||
225 | }else if( stop_bits == 2 ){ |
||
226 | newio.c_cflag |= CSTOPB; |
||
227 | } |
||
13 | rm5248 | 228 | #endif |
229 | |||
230 | SET_SERIAL_PORT_STRUCT( desc->port, newio ); |
||
11 | rm5248 | 231 | |
232 | return 1; |
||
13 | rm5248 | 233 | } |
11 | rm5248 | 234 | |
23 | rm5248 | 235 | /** |
236 | * @param parity 0 for no parity, 1 for odd parity, 2 for even parity |
||
237 | */ |
||
55 | rm5248 | 238 | static inline int set_parity( struct port_descriptor* desc, int parity ){ |
13 | rm5248 | 239 | GET_SERIAL_PORT_STRUCT( desc->port, newio ); |
240 | |||
241 | #ifdef _WIN32 |
||
242 | if( parity == 0 ){ |
||
243 | newio.Parity = NOPARITY; |
||
244 | }else if( parity == 1 ){ |
||
245 | newio.Parity = ODDPARITY; |
||
246 | }else if( parity == 2 ){ |
||
247 | newio.Parity = EVENPARITY; |
||
248 | } |
||
249 | #else |
||
62 | rm5248 | 250 | newio.c_iflag &= ~IGNPAR; |
251 | newio.c_cflag &= ~( PARODD | PARENB ); |
||
14 | rm5248 | 252 | if( parity == 0 ){ |
57 | rm5248 | 253 | newio.c_iflag |= IGNPAR; |
14 | rm5248 | 254 | }else if( parity == 1 ){ |
255 | newio.c_cflag |= PARODD; |
||
256 | }else if( parity == 2 ){ |
||
257 | newio.c_cflag |= PARENB; |
||
258 | } |
||
13 | rm5248 | 259 | #endif |
260 | |||
261 | SET_SERIAL_PORT_STRUCT( desc->port, newio ); |
||
262 | |||
263 | return 1; |
||
11 | rm5248 | 264 | } |
265 | |||
23 | rm5248 | 266 | /** |
267 | * @param flow_control 0 for none, 1 for hardware, 2 for software |
||
268 | */ |
||
55 | rm5248 | 269 | static inline int set_flow_control( struct port_descriptor* desc, int flow_control ){ |
13 | rm5248 | 270 | GET_SERIAL_PORT_STRUCT( desc->port, newio ); |
271 | |||
23 | rm5248 | 272 | #ifdef _WIN32 |
273 | if( flow_control == 0 ){ |
||
30 | rm5248 | 274 | newio.fOutxCtsFlow = FALSE; |
275 | newio.fRtsControl = FALSE; |
||
276 | newio.fOutX = FALSE; |
||
277 | newio.fInX = FALSE; |
||
23 | rm5248 | 278 | }else if( flow_control == 1 ){ |
30 | rm5248 | 279 | newio.fOutxCtsFlow = TRUE; |
280 | newio.fRtsControl = TRUE; |
||
281 | newio.fOutX = FALSE; |
||
282 | newio.fInX = FALSE; |
||
23 | rm5248 | 283 | }else if( flow_control == 2 ){ |
30 | rm5248 | 284 | newio.fOutxCtsFlow = FALSE; |
285 | newio.fRtsControl = FALSE; |
||
286 | newio.fOutX = TRUE; |
||
287 | newio.fInX = TRUE; |
||
23 | rm5248 | 288 | } |
13 | rm5248 | 289 | #else |
62 | rm5248 | 290 | newio.c_iflag &= ~( IXON | IXOFF | IXANY ); |
291 | newio.c_cflag &= ~HW_FLOW; |
||
14 | rm5248 | 292 | if( flow_control == 0 ){ |
57 | rm5248 | 293 | newio.c_iflag &= ~( IXON | IXOFF | IXANY ); |
14 | rm5248 | 294 | }else if( flow_control == 1 ){ |
295 | newio.c_cflag |= HW_FLOW; |
||
296 | }else if( flow_control == 2 ){ |
||
57 | rm5248 | 297 | newio.c_iflag |= ( IXON | IXOFF | IXANY ); |
14 | rm5248 | 298 | } |
13 | rm5248 | 299 | #endif |
300 | |||
301 | SET_SERIAL_PORT_STRUCT( desc->port, newio ); |
||
302 | |||
303 | return 1; |
||
304 | } |
||
305 | |||
55 | rm5248 | 306 | static inline void throw_io_exception( JNIEnv * env, int errorNumber ){ |
27 | rm5248 | 307 | #ifdef _WIN32 |
308 | LPTSTR error_text = NULL; |
||
309 | jclass exception_class; |
||
310 | (*env)->ExceptionDescribe( env ); |
||
311 | (*env)->ExceptionClear( env ); |
||
312 | exception_class = (*env)->FindClass(env, "java/io/IOException"); |
||
313 | |||
314 | FormatMessage( |
||
315 | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, |
||
316 | NULL, |
||
317 | errorNumber, |
||
318 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
||
319 | (LPTSTR)&error_text, |
||
320 | 0, |
||
321 | NULL ); |
||
322 | (*env)->ThrowNew(env, exception_class, error_text ); |
||
323 | LocalFree( error_text ); |
||
324 | #else |
||
325 | jclass exception_class; |
||
326 | (*env)->ExceptionDescribe( env ); |
||
327 | (*env)->ExceptionClear( env ); |
||
328 | exception_class = (*env)->FindClass(env, "java/io/IOException"); |
||
329 | (*env)->ThrowNew(env, exception_class, strerror( errorNumber ) ); |
||
330 | #endif /* _WIN32 */ |
||
331 | } |
||
332 | |||
55 | rm5248 | 333 | static inline void throw_io_exception_message( JNIEnv * env, const char* message ){ |
27 | rm5248 | 334 | jclass exception_class; |
335 | (*env)->ExceptionDescribe( env ); |
||
336 | (*env)->ExceptionClear( env ); |
||
337 | exception_class = (*env)->FindClass(env, "java/io/IOException"); |
||
338 | (*env)->ThrowNew(env, exception_class, message ); |
||
339 | } |
||
340 | |||
55 | rm5248 | 341 | static inline struct port_descriptor* get_port_descriptor( JNIEnv* env, jobject obj ){ |
342 | int array_pos; |
||
343 | struct port_descriptor* desc; |
||
344 | |||
345 | array_pos = get_handle( env, obj ); |
||
346 | if( array_pos < 0 || array_pos > port_list_size ){ |
||
347 | throw_io_exception_message( env, "Unable to get handle" ); |
||
348 | return NULL; |
||
349 | } |
||
350 | desc = port_list[ array_pos ]; |
||
351 | if( desc == NULL ){ |
||
352 | throw_io_exception_message( env, "Unable to get descriptor" ); |
||
353 | return NULL; |
||
354 | } |
||
355 | |||
356 | return desc; |
||
357 | } |
||
358 | |||
8 | rm5248 | 359 | // |
360 | // JNI Methods |
||
361 | // |
||
362 | |||
363 | /* |
||
364 | * Class: com_rm5248_serial_SerialPort |
||
365 | * Method: openPort |
||
11 | rm5248 | 366 | * Signature: (Ljava/lang/String;IIIII)I |
8 | rm5248 | 367 | */ |
368 | JNIEXPORT jint JNICALL Java_com_rm5248_serial_SerialPort_openPort |
||
11 | rm5248 | 369 | (JNIEnv * env, jobject obj, jstring port, jint baudRate, jint dataBits, jint stopBits, jint parity, jint flowControl){ |
8 | rm5248 | 370 | struct port_descriptor* new_port; |
371 | int list_pos; |
||
372 | int found = 0; |
||
373 | const char* port_to_open; |
||
374 | jboolean iscopy; |
||
375 | |||
376 | port_to_open = (*env)->GetStringUTFChars( env, port, &iscopy ); |
||
377 | |||
378 | if( port_list == NULL ){ |
||
379 | port_list = malloc( sizeof( struct port_descriptor* ) * 10 ); |
||
380 | port_list_size = 10; |
||
381 | for( list_pos = 0; list_pos < port_list_size; ++list_pos ){ |
||
382 | port_list[ list_pos ] = NULL; |
||
383 | } |
||
384 | } |
||
385 | |||
18 | rm5248 | 386 | //Search thru the port_list, find the first one that is NULL |
8 | rm5248 | 387 | for( list_pos = 0; list_pos < port_list_size; ++list_pos ){ |
18 | rm5248 | 388 | if( port_list[ list_pos ] == NULL ){ |
8 | rm5248 | 389 | found = 1; |
390 | break; |
||
391 | } |
||
392 | } |
||
393 | |||
394 | if( !found ){ |
||
395 | //no free slots. Expand our array by 10 elements |
||
396 | struct port_descriptor** tmpPortDesc; |
||
397 | tmpPortDesc = malloc( sizeof( struct port_descriptor* ) * ( port_list_size + 10 ) ); |
||
398 | |||
399 | //put all elements into the new array |
||
400 | for( list_pos = 0; list_pos < port_list_size; ++list_pos ){ |
||
401 | tmpPortDesc[ list_pos ] = port_list[ list_pos ]; |
||
402 | } |
||
403 | ++list_pos; //put the place to insert the new record one past the old records |
||
404 | |||
405 | port_list_size += 10; |
||
406 | |||
407 | //free the old array, set it to the new one |
||
408 | free( port_list ); |
||
409 | port_list = tmpPortDesc; |
||
410 | } |
||
411 | |||
412 | //at this point, list_pos points to a NULL location in our array |
||
413 | new_port = malloc( sizeof( struct port_descriptor ) ); |
||
414 | |||
415 | //Now, let's get to the actual opening of our port |
||
13 | rm5248 | 416 | #ifdef _WIN32 |
27 | rm5248 | 417 | new_port->port = CreateFile( port_to_open, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0 ); |
13 | rm5248 | 418 | if( new_port->port == INVALID_HANDLE_VALUE ){ |
419 | if( GetLastError() == ERROR_FILE_NOT_FOUND ){ |
||
420 | LPTSTR error_text = NULL; |
||
421 | jclass exception_class; |
||
422 | |||
423 | FormatMessage( |
||
424 | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, |
||
425 | NULL, |
||
426 | GetLastError(), |
||
427 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
||
428 | (LPTSTR)&error_text, |
||
429 | 0, |
||
430 | NULL ); |
||
431 | |||
432 | (*env)->ExceptionDescribe( env ); |
||
433 | (*env)->ExceptionClear( env ); |
||
434 | exception_class = (*env)->FindClass(env, "com/rm5248/serial/NoSuchPortException"); |
||
435 | (*env)->ThrowNew(env, exception_class, error_text ); |
||
436 | free( new_port ); |
||
437 | LocalFree( error_text ); |
||
438 | return -1; |
||
439 | } |
||
440 | } |
||
27 | rm5248 | 441 | |
54 | rm5248 | 442 | { |
27 | rm5248 | 443 | //Let's check to see if this is a serial port |
444 | DCB io_name = {0}; |
||
445 | io_name.DCBlength = sizeof( io_name ); |
||
446 | if (!GetCommState( new_port->port, &io_name ) ) { |
||
55 | rm5248 | 447 | LPTSTR error_text = NULL; |
27 | rm5248 | 448 | jclass exception_class; |
449 | (*env)->ExceptionDescribe( env ); |
||
450 | (*env)->ExceptionClear( env ); |
||
451 | exception_class = (*env)->FindClass(env, "com/rm5248/serial/NotASerialPortException"); |
||
452 | (*env)->ThrowNew(env, exception_class, "You are attempting to open something which is not a serial port" ); |
||
453 | free( new_port ); |
||
454 | return -1; |
||
455 | } |
||
456 | } |
||
457 | |||
8 | rm5248 | 458 | #else |
459 | new_port->port = open( port_to_open, O_RDWR ); |
||
460 | if( new_port->port < 0 && errno == ENOENT ){ |
||
461 | //That's not a valid serial port, error out |
||
462 | jclass exception_class; |
||
463 | (*env)->ExceptionDescribe( env ); |
||
464 | (*env)->ExceptionClear( env ); |
||
465 | exception_class = (*env)->FindClass(env, "com/rm5248/serial/NoSuchPortException"); |
||
466 | (*env)->ThrowNew(env, exception_class, strerror( errno ) ); |
||
467 | free( new_port ); |
||
13 | rm5248 | 468 | return -1; |
8 | rm5248 | 469 | }else if( new_port->port < 0 ){ |
470 | jclass exception_class; |
||
471 | (*env)->ExceptionDescribe( env ); |
||
472 | (*env)->ExceptionClear( env ); |
||
473 | exception_class = (*env)->FindClass(env, "com/rm5248/serial/NoSuchPortException"); |
||
474 | (*env)->ThrowNew(env, exception_class, strerror( errno ) ); |
||
475 | free( new_port ); |
||
13 | rm5248 | 476 | return -1; |
8 | rm5248 | 477 | } |
27 | rm5248 | 478 | |
479 | { |
||
480 | struct termios io_name; |
||
28 | rm5248 | 481 | if( tcgetattr( new_port->port, &io_name ) < 0 ){ |
32 | rm5248 | 482 | if( errno == ENOTTY ){ |
27 | rm5248 | 483 | //This is apparently not a serial port |
484 | jclass exception_class; |
||
485 | (*env)->ExceptionDescribe( env ); |
||
486 | (*env)->ExceptionClear( env ); |
||
487 | exception_class = (*env)->FindClass(env, "com/rm5248/serial/NotASerialPortException"); |
||
488 | (*env)->ThrowNew(env, exception_class, "You are attempting to open something which is not a serial port" ); |
||
489 | free( new_port ); |
||
490 | return -1; |
||
491 | } |
||
492 | } |
||
493 | } |
||
8 | rm5248 | 494 | #endif /* __WIN32 */ |
495 | |||
496 | |||
27 | rm5248 | 497 | |
11 | rm5248 | 498 | //Set the baud rate |
57 | rm5248 | 499 | if( set_baud_rate( new_port, baudRate ) < 0 ){ |
500 | throw_io_exception_message( env, "Unable to set baud rate" ); |
||
501 | return 0; |
||
502 | } |
||
27 | rm5248 | 503 | set_raw_input( new_port ); |
504 | //Set the data bits( character size ) |
||
505 | set_data_bits( new_port, dataBits ); |
||
506 | //Set the stop bits |
||
507 | set_stop_bits( new_port, stopBits ); |
||
508 | //Set the parity |
||
509 | set_parity( new_port, parity ); |
||
510 | //Set the flow control |
||
511 | set_flow_control( new_port, flowControl ); |
||
512 | |||
513 | //Only set the new_port to be in our array as the last instruction |
||
514 | //If there are any errors, we will have returned long before this |
||
515 | port_list[ list_pos ] = new_port; |
||
516 | |||
517 | return list_pos; |
||
518 | } |
||
519 | |||
520 | /* |
||
521 | * Class: com_rm5248_serial_SerialPort |
||
522 | * Method: openPort |
||
523 | * Signature: (Ljava/lang/String;)I |
||
524 | */ |
||
525 | JNIEXPORT jint JNICALL Java_com_rm5248_serial_SerialPort_openPort__Ljava_lang_String_2 |
||
526 | (JNIEnv * env, jobject obj, jstring port){ |
||
527 | struct port_descriptor* new_port; |
||
528 | int list_pos; |
||
529 | int found = 0; |
||
530 | const char* port_to_open; |
||
531 | jboolean iscopy; |
||
532 | |||
533 | port_to_open = (*env)->GetStringUTFChars( env, port, &iscopy ); |
||
534 | |||
535 | if( port_list == NULL ){ |
||
536 | port_list = malloc( sizeof( struct port_descriptor* ) * 10 ); |
||
537 | port_list_size = 10; |
||
538 | for( list_pos = 0; list_pos < port_list_size; ++list_pos ){ |
||
539 | port_list[ list_pos ] = NULL; |
||
540 | } |
||
541 | } |
||
542 | |||
543 | //Search thru the port_list, find the first one that is NULL |
||
544 | for( list_pos = 0; list_pos < port_list_size; ++list_pos ){ |
||
545 | if( port_list[ list_pos ] == NULL ){ |
||
546 | found = 1; |
||
547 | break; |
||
548 | } |
||
549 | } |
||
550 | |||
551 | if( !found ){ |
||
552 | //no free slots. Expand our array by 10 elements |
||
553 | struct port_descriptor** tmpPortDesc; |
||
554 | tmpPortDesc = malloc( sizeof( struct port_descriptor* ) * ( port_list_size + 10 ) ); |
||
555 | |||
556 | //put all elements into the new array |
||
557 | for( list_pos = 0; list_pos < port_list_size; ++list_pos ){ |
||
558 | tmpPortDesc[ list_pos ] = port_list[ list_pos ]; |
||
559 | } |
||
560 | ++list_pos; //put the place to insert the new record one past the old records |
||
561 | |||
562 | port_list_size += 10; |
||
563 | |||
564 | //free the old array, set it to the new one |
||
565 | free( port_list ); |
||
566 | port_list = tmpPortDesc; |
||
567 | } |
||
568 | |||
569 | //at this point, list_pos points to a NULL location in our array |
||
570 | new_port = malloc( sizeof( struct port_descriptor ) ); |
||
571 | |||
572 | //Now, let's get to the actual opening of our port |
||
573 | #ifdef _WIN32 |
||
574 | new_port->port = CreateFile( port_to_open, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0 ); |
||
575 | if( new_port->port == INVALID_HANDLE_VALUE ){ |
||
576 | if( GetLastError() == ERROR_FILE_NOT_FOUND ){ |
||
577 | LPTSTR error_text = NULL; |
||
578 | jclass exception_class; |
||
579 | |||
580 | FormatMessage( |
||
581 | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, |
||
582 | NULL, |
||
583 | GetLastError(), |
||
584 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
||
585 | (LPTSTR)&error_text, |
||
586 | 0, |
||
587 | NULL ); |
||
588 | |||
589 | (*env)->ExceptionDescribe( env ); |
||
590 | (*env)->ExceptionClear( env ); |
||
591 | exception_class = (*env)->FindClass(env, "com/rm5248/serial/NoSuchPortException"); |
||
592 | (*env)->ThrowNew(env, exception_class, error_text ); |
||
593 | free( new_port ); |
||
594 | LocalFree( error_text ); |
||
595 | return -1; |
||
596 | } |
||
597 | } |
||
598 | |||
599 | { |
||
600 | //Let's check to see if this is a serial port |
||
601 | DCB io_name = {0}; |
||
602 | io_name.DCBlength = sizeof( io_name ); |
||
603 | if (!GetCommState( new_port->port, &io_name ) ) { |
||
55 | rm5248 | 604 | LPTSTR error_text = NULL; |
27 | rm5248 | 605 | jclass exception_class; |
606 | (*env)->ExceptionDescribe( env ); |
||
607 | (*env)->ExceptionClear( env ); |
||
608 | exception_class = (*env)->FindClass(env, "com/rm5248/serial/NotASerialPortException"); |
||
609 | (*env)->ThrowNew(env, exception_class, "You are attempting to open something which is not a serial port" ); |
||
610 | free( new_port ); |
||
611 | return -1; |
||
612 | } |
||
613 | } |
||
614 | #else |
||
615 | new_port->port = open( port_to_open, O_RDWR ); |
||
616 | if( new_port->port < 0 && errno == ENOENT ){ |
||
617 | //That's not a valid serial port, error out |
||
13 | rm5248 | 618 | jclass exception_class; |
619 | (*env)->ExceptionDescribe( env ); |
||
620 | (*env)->ExceptionClear( env ); |
||
27 | rm5248 | 621 | exception_class = (*env)->FindClass(env, "com/rm5248/serial/NoSuchPortException"); |
622 | (*env)->ThrowNew(env, exception_class, strerror( errno ) ); |
||
13 | rm5248 | 623 | free( new_port ); |
27 | rm5248 | 624 | return -1; |
625 | }else if( new_port->port < 0 ){ |
||
13 | rm5248 | 626 | jclass exception_class; |
627 | (*env)->ExceptionDescribe( env ); |
||
628 | (*env)->ExceptionClear( env ); |
||
27 | rm5248 | 629 | exception_class = (*env)->FindClass(env, "com/rm5248/serial/NoSuchPortException"); |
630 | (*env)->ThrowNew(env, exception_class, strerror( errno ) ); |
||
13 | rm5248 | 631 | free( new_port ); |
632 | return -1; |
||
633 | } |
||
27 | rm5248 | 634 | |
635 | { |
||
636 | struct termios io_name; |
||
28 | rm5248 | 637 | if( tcgetattr( new_port->port, &io_name ) < 0 ){ |
32 | rm5248 | 638 | if( errno == ENOTTY ){ |
27 | rm5248 | 639 | //This is apparently not a serial port |
640 | jclass exception_class; |
||
641 | (*env)->ExceptionDescribe( env ); |
||
642 | (*env)->ExceptionClear( env ); |
||
643 | exception_class = (*env)->FindClass(env, "com/rm5248/serial/NotASerialPortException"); |
||
644 | (*env)->ThrowNew(env, exception_class, "You are attempting to open something which is not a serial port" ); |
||
645 | free( new_port ); |
||
646 | return -1; |
||
647 | } |
||
648 | } |
||
649 | } |
||
650 | #endif /* __WIN32 */ |
||
11 | rm5248 | 651 | |
27 | rm5248 | 652 | |
13 | rm5248 | 653 | //Only set the new_port to be in our array as the last instruction |
654 | //If there are any errors, we will have returned long before this |
||
655 | port_list[ list_pos ] = new_port; |
||
27 | rm5248 | 656 | |
8 | rm5248 | 657 | return list_pos; |
658 | } |
||
659 | |||
660 | /* |
||
661 | * Class: com_rm5248_serial_SerialPort |
||
27 | rm5248 | 662 | * Method: doClose |
663 | * Signature: ()V |
||
664 | */ |
||
665 | JNIEXPORT void JNICALL Java_com_rm5248_serial_SerialPort_doClose |
||
666 | (JNIEnv * env, jobject obj){ |
||
55 | rm5248 | 667 | //Note: We can't use get_serial_port_struct here, beacuse we need to set |
668 | //the position in the array to NULL |
||
669 | int array_pos; |
||
27 | rm5248 | 670 | struct port_descriptor* desc; |
55 | rm5248 | 671 | |
672 | array_pos = get_handle( env, obj ); |
||
673 | if( array_pos < 0 || array_pos > port_list_size ){ |
||
674 | throw_io_exception_message( env, "Unable to get handle" ); |
||
675 | return; |
||
676 | } |
||
677 | desc = port_list[ array_pos ]; |
||
678 | if( desc == NULL ){ |
||
679 | throw_io_exception_message( env, "Unable to get descriptor" ); |
||
680 | return; |
||
27 | rm5248 | 681 | } |
682 | |||
683 | close( desc->port ); |
||
684 | free( desc ); |
||
685 | port_list[ array_pos ] = NULL; |
||
686 | } |
||
687 | |||
688 | /* |
||
689 | * Class: com_rm5248_serial_SerialPort |
||
8 | rm5248 | 690 | * Method: setBaudRate |
691 | * Signature: (I)Z |
||
692 | */ |
||
693 | JNIEXPORT jboolean JNICALL Java_com_rm5248_serial_SerialPort_setBaudRate |
||
694 | (JNIEnv * env, jobject obj, jint baud_rate ){ |
||
695 | struct port_descriptor* desc; |
||
696 | |||
55 | rm5248 | 697 | desc = get_port_descriptor( env, obj ); |
57 | rm5248 | 698 | |
55 | rm5248 | 699 | if( desc == NULL ){ |
700 | return 0; |
||
701 | } |
||
8 | rm5248 | 702 | |
11 | rm5248 | 703 | return set_baud_rate( desc, baud_rate ); |
8 | rm5248 | 704 | } |
705 | |||
706 | /* |
||
707 | * Class: com_rm5248_serial_SerialPort |
||
20 | rm5248 | 708 | * Method: getBaudRateInternal |
709 | * Signature: ()I |
||
710 | */ |
||
711 | JNIEXPORT jint JNICALL Java_com_rm5248_serial_SerialPort_getBaudRateInternal |
||
712 | (JNIEnv * env, jobject obj){ |
||
713 | struct port_descriptor* desc; |
||
714 | |||
55 | rm5248 | 715 | desc = get_port_descriptor( env, obj ); |
716 | if( desc == NULL ){ |
||
717 | return 0; |
||
718 | } |
||
20 | rm5248 | 719 | |
720 | //Now, let's get the baud rate information |
||
721 | { |
||
722 | GET_SERIAL_PORT_STRUCT( desc->port, newio ); |
||
723 | #ifdef _WIN32 |
||
23 | rm5248 | 724 | GetCommState( desc->port, &newio ); |
725 | switch( newio.BaudRate ){ |
||
20 | rm5248 | 726 | #else |
727 | switch( cfgetispeed( &newio ) ){ |
||
728 | GET_SPEED_SWITCH( 0, newio ); |
||
729 | GET_SPEED_SWITCH( 50, newio ); |
||
730 | GET_SPEED_SWITCH( 75, newio ); |
||
23 | rm5248 | 731 | #endif /* _WIN32 */ |
20 | rm5248 | 732 | GET_SPEED_SWITCH( 110, newio ); |
23 | rm5248 | 733 | #ifndef _WIN32 |
20 | rm5248 | 734 | GET_SPEED_SWITCH( 134, newio ); |
735 | GET_SPEED_SWITCH( 150, newio ); |
||
736 | GET_SPEED_SWITCH( 200, newio ); |
||
23 | rm5248 | 737 | #endif /* _WIN32 */ |
20 | rm5248 | 738 | GET_SPEED_SWITCH( 300, newio ); |
739 | GET_SPEED_SWITCH( 600, newio ); |
||
740 | GET_SPEED_SWITCH( 1200, newio ); |
||
23 | rm5248 | 741 | #ifndef _WIN32 |
20 | rm5248 | 742 | GET_SPEED_SWITCH( 1800, newio ); |
23 | rm5248 | 743 | #endif /* _WIN32 */ |
20 | rm5248 | 744 | GET_SPEED_SWITCH( 2400, newio ); |
745 | GET_SPEED_SWITCH( 4800, newio ); |
||
746 | GET_SPEED_SWITCH( 9600, newio ); |
||
747 | GET_SPEED_SWITCH( 19200, newio ); |
||
748 | GET_SPEED_SWITCH( 38400, newio ); |
||
749 | GET_SPEED_SWITCH( 115200, newio ); |
||
750 | default: |
||
751 | return 0; |
||
752 | } /* end switch */ |
||
753 | } |
||
754 | |||
755 | } |
||
756 | |||
757 | /* |
||
758 | * Class: com_rm5248_serial_SerialPort |
||
8 | rm5248 | 759 | * Method: setStopBits |
760 | * Signature: (I)Z |
||
761 | */ |
||
762 | JNIEXPORT jboolean JNICALL Java_com_rm5248_serial_SerialPort_setStopBits |
||
763 | (JNIEnv * env, jobject obj, jint bits){ |
||
13 | rm5248 | 764 | struct port_descriptor* desc; |
765 | |||
55 | rm5248 | 766 | desc = get_port_descriptor( env, obj ); |
13 | rm5248 | 767 | if( desc == NULL ){ |
768 | return 0; |
||
769 | } |
||
8 | rm5248 | 770 | |
13 | rm5248 | 771 | return set_stop_bits( desc, bits ); |
8 | rm5248 | 772 | } |
773 | |||
774 | /* |
||
775 | * Class: com_rm5248_serial_SerialPort |
||
20 | rm5248 | 776 | * Method: getStopBitsInternal |
777 | * Signature: ()I |
||
778 | */ |
||
779 | JNIEXPORT jint JNICALL Java_com_rm5248_serial_SerialPort_getStopBitsInternal |
||
780 | (JNIEnv * env, jobject obj){ |
||
781 | struct port_descriptor* desc; |
||
782 | |||
55 | rm5248 | 783 | desc = get_port_descriptor( env, obj ); |
20 | rm5248 | 784 | if( desc == NULL ){ |
785 | return 0; |
||
786 | } |
||
787 | |||
788 | { |
||
789 | GET_SERIAL_PORT_STRUCT( desc->port, newio ); |
||
790 | #ifdef _WIN32 |
||
23 | rm5248 | 791 | if( newio.StopBits == 1 ){ |
792 | return 1; |
||
793 | }else if( newio.StopBits == 2 ){ |
||
794 | return 2; |
||
795 | }else{ |
||
796 | return -1; |
||
797 | } |
||
20 | rm5248 | 798 | #else |
799 | if( newio.c_cflag & CSTOPB ){ |
||
800 | return 2; |
||
801 | }else{ |
||
802 | return 1; |
||
803 | } |
||
804 | #endif |
||
805 | } |
||
806 | } |
||
807 | /* |
||
808 | * Class: com_rm5248_serial_SerialPort |
||
8 | rm5248 | 809 | * Method: setCharSize |
810 | * Signature: (I)Z |
||
811 | */ |
||
812 | JNIEXPORT jboolean JNICALL Java_com_rm5248_serial_SerialPort_setCharSize |
||
813 | (JNIEnv * env, jobject obj, jint size){ |
||
13 | rm5248 | 814 | struct port_descriptor* desc; |
8 | rm5248 | 815 | |
55 | rm5248 | 816 | desc = get_port_descriptor( env, obj ); |
13 | rm5248 | 817 | if( desc == NULL ){ |
818 | return 0; |
||
819 | } |
||
820 | |||
821 | return set_data_bits( desc, size ); |
||
8 | rm5248 | 822 | } |
823 | |||
824 | /* |
||
11 | rm5248 | 825 | * Class: com_rm5248_serial_SerialPort |
20 | rm5248 | 826 | * Method: getCharSizeInternal |
827 | * Signature: ()I |
||
828 | */ |
||
829 | JNIEXPORT jint JNICALL Java_com_rm5248_serial_SerialPort_getCharSizeInternal |
||
830 | (JNIEnv * env, jobject obj){ |
||
831 | struct port_descriptor* desc; |
||
832 | |||
55 | rm5248 | 833 | desc = get_port_descriptor( env, obj ); |
20 | rm5248 | 834 | if( desc == NULL ){ |
835 | return 0; |
||
836 | } |
||
837 | |||
838 | //Now get the char size |
||
839 | { |
||
840 | GET_SERIAL_PORT_STRUCT( desc->port, newio ); |
||
841 | |||
842 | #ifdef _WIN32 |
||
23 | rm5248 | 843 | return newio.ByteSize; |
20 | rm5248 | 844 | #else |
845 | if( ( newio.c_cflag | CS8 ) == CS8 ){ |
||
846 | return 8; |
||
847 | }else if( ( newio.c_cflag | CS7 ) == CS7 ){ |
||
848 | return 7; |
||
849 | }else if( ( newio.c_cflag | CS6 ) == CS6 ){ |
||
850 | return 6; |
||
851 | }else if( ( newio.c_cflag | CS5 ) == CS5 ){ |
||
852 | return 5; |
||
853 | }else{ |
||
854 | return 0; |
||
855 | } |
||
856 | #endif |
||
857 | } |
||
858 | |||
859 | } |
||
860 | |||
861 | /* |
||
862 | * Class: com_rm5248_serial_SerialPort |
||
11 | rm5248 | 863 | * Method: setParity |
864 | * Signature: (I)Z |
||
865 | */ |
||
866 | JNIEXPORT jboolean JNICALL Java_com_rm5248_serial_SerialPort_setParity |
||
867 | (JNIEnv * env, jobject obj, jint parity){ |
||
13 | rm5248 | 868 | struct port_descriptor* desc; |
11 | rm5248 | 869 | |
55 | rm5248 | 870 | desc = get_port_descriptor( env, obj ); |
13 | rm5248 | 871 | if( desc == NULL ){ |
872 | return 0; |
||
30 | rm5248 | 873 | } |
13 | rm5248 | 874 | |
875 | return set_parity( desc, parity ); |
||
11 | rm5248 | 876 | } |
877 | |||
20 | rm5248 | 878 | /* |
879 | * Class: com_rm5248_serial_SerialPort |
||
880 | * Method: getParityInternal |
||
881 | * Signature: ()I |
||
882 | */ |
||
883 | JNIEXPORT jint JNICALL Java_com_rm5248_serial_SerialPort_getParityInternal |
||
884 | (JNIEnv * env, jobject obj){ |
||
885 | struct port_descriptor* desc; |
||
886 | |||
55 | rm5248 | 887 | desc = get_port_descriptor( env, obj ); |
20 | rm5248 | 888 | if( desc == NULL ){ |
889 | return 0; |
||
890 | } |
||
891 | |||
892 | { |
||
893 | GET_SERIAL_PORT_STRUCT( desc->port, newio ); |
||
894 | #ifdef _WIN32 |
||
23 | rm5248 | 895 | if( newio.Parity == NOPARITY ){ |
896 | return 0; |
||
897 | }else if( newio.Parity == ODDPARITY ){ |
||
898 | return 1; |
||
899 | }else if( newio.Parity == EVENPARITY ){ |
||
900 | return 2; |
||
901 | }else{ |
||
902 | return -1; |
||
903 | } |
||
20 | rm5248 | 904 | #else |
905 | if( !( newio.c_cflag & PARENB ) ){ |
||
906 | //No parity |
||
907 | return 0; |
||
908 | }else if( newio.c_cflag & PARODD ){ |
||
909 | //Odd parity |
||
910 | return 1; |
||
911 | }else if( !( newio.c_cflag & PARODD ) ){ |
||
912 | //Even parity |
||
913 | return 2; |
||
914 | }else{ |
||
915 | return -1; |
||
916 | } |
||
917 | #endif |
||
918 | } |
||
919 | |||
920 | } |
||
921 | |||
27 | rm5248 | 922 | /* |
923 | * Class: com_rm5248_serial_SerialPort |
||
924 | * Method: setFlowControl |
||
925 | * Signature: (I)Z |
||
926 | */ |
||
927 | JNIEXPORT jboolean JNICALL Java_com_rm5248_serial_SerialPort_setFlowControl |
||
928 | (JNIEnv * env, jobject obj, jint flow){ |
||
30 | rm5248 | 929 | struct port_descriptor* desc; |
930 | |||
55 | rm5248 | 931 | desc = get_port_descriptor( env, obj ); |
30 | rm5248 | 932 | if( desc == NULL ){ |
933 | return 0; |
||
934 | } |
||
935 | |||
31 | rm5248 | 936 | return set_flow_control( desc, flow ); |
27 | rm5248 | 937 | } |
938 | |||
939 | /* |
||
940 | * Class: com_rm5248_serial_SerialPort |
||
941 | * Method: getFlowControlInternal |
||
942 | * Signature: ()I |
||
943 | */ |
||
944 | JNIEXPORT jint JNICALL Java_com_rm5248_serial_SerialPort_getFlowControlInternal |
||
945 | (JNIEnv * env, jobject obj){ |
||
946 | struct port_descriptor* desc; |
||
947 | |||
55 | rm5248 | 948 | desc = get_port_descriptor( env, obj ); |
30 | rm5248 | 949 | if( desc == NULL ){ |
950 | return 0; |
||
951 | } |
||
952 | |||
27 | rm5248 | 953 | { |
954 | GET_SERIAL_PORT_STRUCT( desc->port, newio ); |
||
955 | #ifdef _WIN32 |
||
30 | rm5248 | 956 | if( newio.fOutX == TRUE && newio.fInX == TRUE ){ |
957 | return 2; |
||
958 | }else if( newio.fRtsControl == TRUE && newio.fOutxCtsFlow == TRUE ){ |
||
959 | return 1; |
||
960 | }else{ |
||
961 | return 0; |
||
962 | } |
||
27 | rm5248 | 963 | #else |
32 | rm5248 | 964 | if( newio.c_cflag & ~( IXON ) && |
965 | newio.c_cflag & ~( IXOFF ) && |
||
966 | newio.c_cflag & ~( IXANY ) ){ |
||
967 | return 0; |
||
968 | }else if( newio.c_cflag & HW_FLOW ){ |
||
969 | return 1; |
||
970 | }else if( newio.c_cflag & ( IXON ) && |
||
971 | newio.c_cflag & ( IXOFF ) && |
||
972 | newio.c_cflag & ( IXANY ) ){ |
||
973 | return 2; |
||
974 | } |
||
27 | rm5248 | 975 | #endif /* _WIN32 */ |
976 | } |
||
32 | rm5248 | 977 | |
978 | return -1; |
||
27 | rm5248 | 979 | } |
980 | |||
31 | rm5248 | 981 | /* |
982 | * Class: com_rm5248_serial_SerialPort |
||
983 | * Method: getSerialLineStateInternalNonblocking |
||
984 | * Signature: ()I |
||
985 | */ |
||
986 | JNIEXPORT jint JNICALL Java_com_rm5248_serial_SerialPort_getSerialLineStateInternalNonblocking |
||
987 | (JNIEnv * env, jobject obj ){ |
||
34 | rm5248 | 988 | struct port_descriptor* desc; |
35 | rm5248 | 989 | jint ret_val; |
31 | rm5248 | 990 | |
55 | rm5248 | 991 | desc = get_port_descriptor( env, obj ); |
34 | rm5248 | 992 | if( desc == NULL ){ |
993 | return 0; |
||
994 | } |
||
995 | |||
36 | rm5248 | 996 | ret_val = 0; |
997 | |||
998 | { |
||
35 | rm5248 | 999 | #ifdef _WIN32 |
36 | rm5248 | 1000 | DWORD get_val; |
1001 | if( GetCommModemStatus( desc->port, &get_val ) == 0 ){ |
||
1002 | throw_io_exception( env, GetLastError() ); |
||
1003 | return -1; |
||
1004 | } |
||
1005 | |||
1006 | if( get_val & MS_CTS_ON ){ |
||
1007 | // CTS |
||
1008 | ret_val |= ( 0x01 << 1 ); |
||
1009 | } |
||
1010 | |||
1011 | if( get_val & MS_DSR_ON ){ |
||
1012 | // Data Set Ready |
||
1013 | ret_val |= ( 0x01 << 2 ); |
||
1014 | } |
||
1015 | |||
1016 | if( get_val & MS_RING_ON ){ |
||
1017 | // Ring Indicator |
||
1018 | ret_val |= ( 0x01 << 5 ); |
||
1019 | } |
||
35 | rm5248 | 1020 | #else |
36 | rm5248 | 1021 | int get_val; |
35 | rm5248 | 1022 | if( ioctl( desc->port, TIOCMGET, &get_val ) < 0 ){ |
1023 | throw_io_exception( env, errno ); |
||
1024 | return -1; |
||
1025 | } |
||
1026 | |||
1027 | if( get_val & TIOCM_CD ){ |
||
1028 | // Carrier detect |
||
1029 | ret_val |= 0x01; |
||
1030 | } |
||
1031 | |||
1032 | if( get_val & TIOCM_CTS ){ |
||
1033 | // CTS |
||
1034 | ret_val |= ( 0x01 << 1 ); |
||
1035 | } |
||
1036 | |||
1037 | if( get_val & TIOCM_DSR ){ |
||
1038 | // Data Set Ready |
||
1039 | ret_val |= ( 0x01 << 2 ); |
||
1040 | } |
||
1041 | |||
1042 | if( get_val & TIOCM_DTR ){ |
||
1043 | // Data Terminal Ready |
||
1044 | ret_val |= ( 0x01 << 3 ); |
||
1045 | } |
||
1046 | |||
1047 | if( get_val & TIOCM_RTS ){ |
||
1048 | // Request To Send |
||
1049 | ret_val |= ( 0x01 << 4 ); |
||
1050 | } |
||
1051 | |||
1052 | if( get_val & TIOCM_RI ){ |
||
1053 | // Ring Indicator |
||
1054 | ret_val |= ( 0x01 << 5 ); |
||
1055 | } |
||
1056 | #endif |
||
1057 | } |
||
1058 | |||
1059 | return ret_val; |
||
31 | rm5248 | 1060 | } |
1061 | |||
63 | rm5248 | 1062 | |
40 | rm5248 | 1063 | /* |
1064 | * Class: com_rm5248_serial_SerialPort |
||
1065 | * Method: setSerialLineStateInternal |
||
1066 | * Signature: (Lcom/rm5248/serial/SerialLineState;)I |
||
1067 | */ |
||
1068 | JNIEXPORT jint JNICALL Java_com_rm5248_serial_SerialPort_setSerialLineStateInternal |
||
1069 | (JNIEnv * env, jobject obj, jobject serial){ |
||
1070 | struct port_descriptor* desc; |
||
1071 | jint ret_val; |
||
1072 | |||
55 | rm5248 | 1073 | desc = get_port_descriptor( env, obj ); |
40 | rm5248 | 1074 | if( desc == NULL ){ |
1075 | return 0; |
||
1076 | } |
||
1077 | |||
1078 | ret_val = 0; |
||
1079 | |||
1080 | #ifdef _WIN32 |
||
1081 | if( get_bool( env, serial, "dataTerminalReady" ) ){ |
||
1082 | if( !EscapeCommFunction( desc->port, SETDTR ) ){ |
||
1083 | throw_io_exception_message( env, "Could not set DTR" ); |
||
1084 | return -1; |
||
1085 | } |
||
1086 | }else{ |
||
1087 | if( !EscapeCommFunction( desc->port, CLRDTR ) ){ |
||
1088 | throw_io_exception_message( env, "Could not set DTR" ); |
||
1089 | return -1; |
||
1090 | } |
||
1091 | } |
||
1092 | |||
1093 | if( get_bool( env, serial, "requestToSend" ) ){ |
||
1094 | if( !EscapeCommFunction( desc->port, SETRTS ) ){ |
||
1095 | throw_io_exception_message( env, "Could not set RTS" ); |
||
1096 | return -1; |
||
1097 | } |
||
1098 | }else{ |
||
1099 | if( !EscapeCommFunction( desc->port, CLRRTS ) ){ |
||
1100 | throw_io_exception_message( env, "Could not set RTS" ); |
||
1101 | return -1; |
||
1102 | } |
||
1103 | } |
||
1104 | #else |
||
62 | rm5248 | 1105 | int toSet = 0; |
40 | rm5248 | 1106 | |
62 | rm5248 | 1107 | if( ioctl( desc->port, TIOCMGET, &toSet ) < 0 ){ |
1108 | throw_io_exception_message( env, "Could not get port settings" ); |
||
1109 | return -1; |
||
40 | rm5248 | 1110 | } |
1111 | |||
1112 | if( get_bool( env, serial, "dataTerminalReady" ) ){ |
||
62 | rm5248 | 1113 | toSet |= TIOCM_DTR; |
40 | rm5248 | 1114 | }else{ |
62 | rm5248 | 1115 | toSet &= ~TIOCM_DTR; |
40 | rm5248 | 1116 | } |
1117 | |||
62 | rm5248 | 1118 | if( get_bool( env, serial, "requestToSend" ) ){ |
1119 | toSet |= TIOCM_RTS; |
||
40 | rm5248 | 1120 | }else{ |
62 | rm5248 | 1121 | toSet &= ~TIOCM_RTS; |
40 | rm5248 | 1122 | } |
1123 | |||
62 | rm5248 | 1124 | if( ioctl( desc->port, TIOCMSET, &toSet ) < 0 ){ |
1125 | throw_io_exception_message( env, "Could not set port settings" ); |
||
40 | rm5248 | 1126 | } |
1127 | #endif |
||
1128 | |||
1129 | return ret_val; |
||
1130 | } |
||
1131 | |||
38 | rm5248 | 1132 | // |
1133 | // ------------------------------------------------------------------------ |
||
1134 | // ------------------Input/Output methods below here----------------------- |
||
1135 | // ------------------------------------------------------------------------ |
||
1136 | // |
||
1137 | |||
55 | rm5248 | 1138 | |
31 | rm5248 | 1139 | /* |
38 | rm5248 | 1140 | * Class: com_rm5248_serial_SerialInputStream |
1141 | * Method: readByte |
||
31 | rm5248 | 1142 | * Signature: ()I |
1143 | */ |
||
38 | rm5248 | 1144 | JNIEXPORT jint JNICALL Java_com_rm5248_serial_SerialInputStream_readByte |
31 | rm5248 | 1145 | (JNIEnv * env, jobject obj){ |
38 | rm5248 | 1146 | int stat; |
1147 | int ret_val; |
||
34 | rm5248 | 1148 | struct port_descriptor* desc; |
38 | rm5248 | 1149 | int get_val = 0; |
1150 | #ifdef _WIN32 |
||
1151 | DWORD ret = 0; |
||
1152 | OVERLAPPED overlap = {0}; |
||
1153 | int current_available = 0; |
||
1154 | #endif |
||
31 | rm5248 | 1155 | |
55 | rm5248 | 1156 | desc = get_port_descriptor( env, obj ); |
34 | rm5248 | 1157 | if( desc == NULL ){ |
55 | rm5248 | 1158 | return 0; |
34 | rm5248 | 1159 | } |
1160 | |||
35 | rm5248 | 1161 | ret_val = 0; |
38 | rm5248 | 1162 | |
1163 | #ifdef _WIN32 |
||
35 | rm5248 | 1164 | { |
38 | rm5248 | 1165 | DWORD comErrors = {0}; |
1166 | COMSTAT portStatus = {0}; |
||
1167 | if( !ClearCommError( desc->port, &comErrors, &portStatus ) ){ |
||
1168 | //return value zero = fail |
||
1169 | throw_io_exception( env, GetLastError() ); |
||
1170 | return -1; |
||
1171 | }else{ |
||
1172 | current_available = portStatus.cbInQue; |
||
1173 | } |
||
1174 | } |
||
1175 | |||
1176 | if( !current_available ){ |
||
1177 | //If nothing is currently available, wait until we get an event of some kind. |
||
1178 | //This could be the serial lines changing state, or it could be some data |
||
1179 | //coming into the system. |
||
36 | rm5248 | 1180 | overlap.hEvent = CreateEvent( 0, TRUE, 0, 0 ); |
38 | rm5248 | 1181 | SetCommMask( desc->port, EV_RXCHAR | EV_CTS | EV_DSR | EV_RING ); |
1182 | WaitCommEvent( desc->port, &ret, &overlap ); |
||
1183 | WaitForSingleObject( overlap.hEvent, INFINITE ); |
||
1184 | }else{ |
||
1185 | //Data is available; set the RXCHAR mask so we try to read from the port |
||
1186 | ret = EV_RXCHAR; |
||
1187 | } |
||
36 | rm5248 | 1188 | |
38 | rm5248 | 1189 | if( ret & EV_RXCHAR ){ |
1190 | if( !ReadFile( desc->port, &ret_val, 1, &stat, &overlap) ){ |
||
36 | rm5248 | 1191 | throw_io_exception( env, GetLastError() ); |
1192 | return -1; |
||
1193 | } |
||
1194 | |||
38 | rm5248 | 1195 | //This is a valid byte, set our valid bit |
1196 | ret_val |= ( 0x01 << 15 ); |
||
1197 | } |
||
1198 | |||
1199 | //Always get the com lines no matter what |
||
1200 | if( GetCommModemStatus( desc->port, &get_val ) == 0 ){ |
||
1201 | throw_io_exception( env, GetLastError() ); |
||
1202 | return -1; |
||
1203 | } |
||
36 | rm5248 | 1204 | |
38 | rm5248 | 1205 | if( get_val & MS_CTS_ON ){ |
1206 | // CTS |
||
1207 | ret_val |= ( 0x01 << 10 ); |
||
1208 | } |
||
36 | rm5248 | 1209 | |
38 | rm5248 | 1210 | if( get_val & MS_DSR_ON ){ |
1211 | // Data Set Ready |
||
1212 | ret_val |= ( 0x01 << 11 ); |
||
1213 | } |
||
36 | rm5248 | 1214 | |
38 | rm5248 | 1215 | if( get_val & MS_RING_ON ){ |
1216 | // Ring Indicator |
||
1217 | ret_val |= ( 0x01 << 14 ); |
||
1218 | } |
||
1219 | |||
35 | rm5248 | 1220 | #else |
63 | rm5248 | 1221 | |
1222 | //Okay, this here is going to be a bit ugly. |
||
1223 | //The problem here is that Linux/POSIX don't specify that TIOCMIWAIT |
||
1224 | //has to exist. Also, the fact that if we use TIOCMIWAIT we don't |
||
1225 | //timeout or anything. What would be very convenient in this case |
||
1226 | //would be to have select() return when the state changed, but |
||
1227 | //it's not possible. :( |
||
1228 | //Ironically, this is one case in which the Windows serial API |
||
1229 | //is better than the POSIX way. |
||
38 | rm5248 | 1230 | fd_set fdset; |
63 | rm5248 | 1231 | struct timeval timeout; |
1232 | int originalState; |
||
1233 | int selectStatus; |
||
35 | rm5248 | 1234 | |
63 | rm5248 | 1235 | //first get the original state of the serial port lines |
1236 | if( ioctl( desc->port, TIOCMGET, &originalState ) < 0 ){ |
||
38 | rm5248 | 1237 | throw_io_exception( env, errno ); |
1238 | return -1; |
||
1239 | } |
||
1240 | |||
63 | rm5248 | 1241 | while( 1 ){ |
1242 | FD_ZERO( &fdset ); |
||
1243 | FD_SET( desc->port, &fdset ); |
||
1244 | timeout.tv_sec = 0; |
||
1245 | timeout.tv_usec = 1000; // 1,000 microseconds = 100ms |
||
1246 | |||
1247 | selectStatus = select( desc->port + 1, &fdset, NULL, NULL, &timeout ); |
||
1248 | if( selectStatus < 0 ){ |
||
35 | rm5248 | 1249 | throw_io_exception( env, errno ); |
1250 | return -1; |
||
1251 | } |
||
63 | rm5248 | 1252 | |
1253 | if( selectStatus == 0 ){ |
||
1254 | //This was a timeout |
||
1255 | if( ioctl( desc->port, TIOCMGET, &get_val ) < 0 ){ |
||
1256 | throw_io_exception( env, errno ); |
||
1257 | return -1; |
||
1258 | } |
||
1259 | |||
1260 | if( get_val == originalState ){ |
||
1261 | //The state of the lines have not changed, |
||
1262 | //continue on until something changes |
||
1263 | continue; |
||
1264 | } |
||
1265 | |||
1266 | } |
||
1267 | |||
1268 | if( FD_ISSET( desc->port, &fdset ) ){ |
||
1269 | stat = read( desc->port, &ret_val, 1 ); |
||
1270 | if( stat < 0 ){ |
||
1271 | //throw new exception |
||
1272 | throw_io_exception( env, errno ); |
||
1273 | return -1; |
||
1274 | } |
||
38 | rm5248 | 1275 | |
63 | rm5248 | 1276 | //This is a valid byte, set our valid bit |
1277 | ret_val |= ( 0x01 << 15 ); |
||
1278 | } |
||
1279 | |||
1280 | //We get to this point if we either 1. have data or |
||
1281 | //2. our state has changed |
||
1282 | break; |
||
38 | rm5248 | 1283 | } |
35 | rm5248 | 1284 | |
38 | rm5248 | 1285 | //Now that we have read in the character, let's get the serial port line state. |
1286 | //If it has changed, we will fire an event in Java. |
||
1287 | //Now, because we only read one byte at a time, we will use the lower 8 bytes to |
||
1288 | //return the character that we read. The other bytes will be used to return |
||
1289 | //information on our serial port state. |
||
1290 | if( ioctl( desc->port, TIOCMGET, &get_val ) < 0 ){ |
||
1291 | throw_io_exception( env, errno ); |
||
1292 | return -1; |
||
1293 | } |
||
35 | rm5248 | 1294 | |
38 | rm5248 | 1295 | if( get_val & TIOCM_CD ){ |
1296 | // Carrier detect |
||
1297 | ret_val |= ( 0x01 << 9 ); |
||
1298 | } |
||
35 | rm5248 | 1299 | |
38 | rm5248 | 1300 | if( get_val & TIOCM_CTS ){ |
1301 | // CTS |
||
1302 | ret_val |= ( 0x01 << 10 ); |
||
35 | rm5248 | 1303 | } |
31 | rm5248 | 1304 | |
38 | rm5248 | 1305 | if( get_val & TIOCM_DSR ){ |
1306 | // Data Set Ready |
||
1307 | ret_val |= ( 0x01 << 11 ); |
||
8 | rm5248 | 1308 | } |
1309 | |||
38 | rm5248 | 1310 | if( get_val & TIOCM_DTR ){ |
1311 | // Data Terminal Ready |
||
1312 | ret_val |= ( 0x01 << 12 ); |
||
8 | rm5248 | 1313 | } |
27 | rm5248 | 1314 | |
38 | rm5248 | 1315 | if( get_val & TIOCM_RTS ){ |
1316 | // Request To Send |
||
1317 | ret_val |= ( 0x01 << 13 ); |
||
23 | rm5248 | 1318 | } |
38 | rm5248 | 1319 | |
1320 | if( get_val & TIOCM_RI ){ |
||
1321 | // Ring Indicator |
||
1322 | ret_val |= ( 0x01 << 14 ); |
||
8 | rm5248 | 1323 | } |
38 | rm5248 | 1324 | |
8 | rm5248 | 1325 | #endif |
38 | rm5248 | 1326 | |
8 | rm5248 | 1327 | return ret_val; |
1328 | } |
||
1329 | |||
1330 | /* |
||
18 | rm5248 | 1331 | * Class: com_rm5248_serial_SerialInputStream |
8 | rm5248 | 1332 | * Method: getAvailable |
1333 | * Signature: ()I |
||
1334 | */ |
||
18 | rm5248 | 1335 | JNIEXPORT jint JNICALL Java_com_rm5248_serial_SerialInputStream_getAvailable |
8 | rm5248 | 1336 | (JNIEnv * env, jobject obj){ |
1337 | jint ret_val; |
||
1338 | struct port_descriptor* desc; |
||
1339 | |||
55 | rm5248 | 1340 | desc = get_port_descriptor( env, obj ); |
8 | rm5248 | 1341 | if( desc == NULL ){ |
55 | rm5248 | 1342 | return 0; |
8 | rm5248 | 1343 | } |
1344 | |||
13 | rm5248 | 1345 | #ifdef _WIN32 |
23 | rm5248 | 1346 | { |
38 | rm5248 | 1347 | DWORD comErrors = {0}; |
1348 | COMSTAT portStatus = {0}; |
||
1349 | if( !ClearCommError( desc->port, &comErrors, &portStatus ) ){ |
||
23 | rm5248 | 1350 | //return value zero = fail |
27 | rm5248 | 1351 | throw_io_exception( env, GetLastError() ); |
23 | rm5248 | 1352 | return -1; |
1353 | }else{ |
||
38 | rm5248 | 1354 | ret_val = portStatus.cbInQue; |
23 | rm5248 | 1355 | } |
1356 | } |
||
8 | rm5248 | 1357 | #else |
1358 | if( ioctl( desc->port, FIONREAD, &ret_val ) < 0 ){ |
||
1359 | //throw new exception |
||
27 | rm5248 | 1360 | throw_io_exception( env, errno ); |
13 | rm5248 | 1361 | return -1; |
8 | rm5248 | 1362 | } |
1363 | #endif |
||
1364 | return ret_val; |
||
1365 | } |
||
1366 | |||
55 | rm5248 | 1367 | |
1368 | |||
8 | rm5248 | 1369 | /* |
18 | rm5248 | 1370 | * Class: com_rm5248_serial_SerialOutputStream |
8 | rm5248 | 1371 | * Method: writeByte |
1372 | * Signature: (I)V |
||
1373 | */ |
||
18 | rm5248 | 1374 | JNIEXPORT void JNICALL Java_com_rm5248_serial_SerialOutputStream_writeByte |
8 | rm5248 | 1375 | (JNIEnv * env, jobject obj, jint byte){ |
1376 | struct port_descriptor* desc; |
||
1377 | char byte_write; |
||
13 | rm5248 | 1378 | #ifdef _WIN32 |
1379 | DWORD bytes_written; |
||
27 | rm5248 | 1380 | OVERLAPPED overlap; |
13 | rm5248 | 1381 | #else |
1382 | int bytes_written; |
||
1383 | #endif |
||
8 | rm5248 | 1384 | |
1385 | byte_write = byte; |
||
1386 | |||
55 | rm5248 | 1387 | desc = get_port_descriptor( env, obj ); |
8 | rm5248 | 1388 | if( desc == NULL ){ |
1389 | return; |
||
1390 | } |
||
1391 | |||
13 | rm5248 | 1392 | #ifdef _WIN32 |
27 | rm5248 | 1393 | memset( &overlap, 0, sizeof( overlap ) ); |
1394 | overlap.hEvent = CreateEvent( 0, TRUE, 0, 0 ); |
||
1395 | if( !WriteFile( desc->port, &byte_write, sizeof( byte_write ), &bytes_written, &overlap ) ){ |
||
1396 | if( GetLastError() == ERROR_IO_PENDING ){ |
||
55 | rm5248 | 1397 | //Probably not an error, we're just doing this in an async fasion |
1398 | if( WaitForSingleObject( overlap.hEvent, INFINITE ) == WAIT_FAILED ){ |
||
1399 | throw_io_exception( env, GetLastError() ); |
||
1400 | return; |
||
1401 | } |
||
27 | rm5248 | 1402 | }else{ |
1403 | throw_io_exception( env, GetLastError() ); |
||
1404 | return; |
||
1405 | } |
||
13 | rm5248 | 1406 | } |
8 | rm5248 | 1407 | #else |
13 | rm5248 | 1408 | bytes_written = write( desc->port, &byte_write, sizeof( byte_write ) ); |
1409 | if( bytes_written < 0 ){ |
||
8 | rm5248 | 1410 | //throw new exception |
32 | rm5248 | 1411 | throw_io_exception( env, errno ); |
8 | rm5248 | 1412 | return; |
1413 | } |
||
1414 | #endif |
||
1415 | } |
||
1416 | |||
40 | rm5248 | 1417 | /* |
1418 | * Class: com_rm5248_serial_SerialOutputStream |
||
1419 | * Method: writeByteArray |
||
1420 | * Signature: ([B)V |
||
1421 | */ |
||
1422 | JNIEXPORT void JNICALL Java_com_rm5248_serial_SerialOutputStream_writeByteArray |
||
1423 | (JNIEnv * env, jobject obj, jbyteArray arr){ |
||
1424 | jbyte* data; |
||
1425 | jint len; |
||
1426 | struct port_descriptor* desc; |
||
1427 | #ifdef _WIN32 |
||
1428 | DWORD bytes_written; |
||
1429 | OVERLAPPED overlap; |
||
1430 | #else |
||
1431 | int bytes_written; |
||
61 | rm5248 | 1432 | #endif /* _WIN32 */ |
40 | rm5248 | 1433 | |
55 | rm5248 | 1434 | desc = get_port_descriptor( env, obj ); |
40 | rm5248 | 1435 | if( desc == NULL ){ |
1436 | return; |
||
1437 | } |
||
1438 | |||
1439 | len = (*env)->GetArrayLength( env, arr ); |
||
1440 | data = (*env)->GetByteArrayElements(env, arr, 0); |
||
1441 | |||
1442 | #ifdef _WIN32 |
||
1443 | memset( &overlap, 0, sizeof( overlap ) ); |
||
1444 | overlap.hEvent = CreateEvent( 0, TRUE, 0, 0 ); |
||
1445 | if( !WriteFile( desc->port, data, len, &bytes_written, &overlap ) ){ |
||
1446 | if( GetLastError() == ERROR_IO_PENDING ){ |
||
55 | rm5248 | 1447 | //Probably not an error, we're just doing this in an async fasion |
1448 | if( WaitForSingleObject( overlap.hEvent, INFINITE ) == WAIT_FAILED ){ |
||
1449 | throw_io_exception( env, GetLastError() ); |
||
1450 | return; |
||
1451 | } |
||
40 | rm5248 | 1452 | }else{ |
55 | rm5248 | 1453 | throw_io_exception( env, GetLastError() ); |
40 | rm5248 | 1454 | } |
1455 | } |
||
55 | rm5248 | 1456 | |
40 | rm5248 | 1457 | #else |
1458 | bytes_written = write( desc->port, data, len ); |
||
1459 | if( bytes_written < 0 ){ |
||
61 | rm5248 | 1460 | throw_io_exception( env, errno ); |
40 | rm5248 | 1461 | } |
61 | rm5248 | 1462 | #endif /* _WIN32 */ |
40 | rm5248 | 1463 | |
1464 | (*env)->ReleaseByteArrayElements(env, arr, data, 0); |
||
1465 | } |
||
1466 | |||
61 | rm5248 | 1467 | |
1468 | // |
||
1469 | // ------------------------------------------------------------------------ |
||
1470 | // ---------------------Static methods below here-------------------------- |
||
1471 | // ------------------------------------------------------------------------ |
||
1472 | // |
||
1473 | |||
1474 | /* |
||
1475 | * Class: com_rm5248_serial_SerialPort |
||
1476 | * Method: getMajorNativeVersion |
||
1477 | * Signature: ()I |
||
1478 | */ |
||
1479 | JNIEXPORT jint JNICALL Java_com_rm5248_serial_SerialPort_getMajorNativeVersion |
||
1480 | (JNIEnv * env, jclass cls){ |
||
1481 | return 0; |
||
1482 | } |
||
1483 | |||
1484 | /* |
||
1485 | * Class: com_rm5248_serial_SerialPort |
||
1486 | * Method: getMinorNativeVersion |
||
1487 | * Signature: ()I |
||
1488 | */ |
||
1489 | JNIEXPORT jint JNICALL Java_com_rm5248_serial_SerialPort_getMinorNativeVersion |
||
1490 | (JNIEnv * env, jclass cls){ |
||
1491 | return 2; |
||
1492 | } |
||
1493 |