1
2
3
4
5 package net.sf.atmodem4j.core;
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.OutputStream;
38 import java.util.logging.Level;
39 import java.util.logging.Logger;
40 import net.sf.atmodem4j.core.fax.FaxExtention;
41 import net.sf.atmodem4j.core.gsm.GsmExtention;
42 import net.sf.atmodem4j.core.parser.Parser;
43 import net.sf.atmodem4j.core.parser.ResultCodeToken;
44 import net.sf.atmodem4j.core.voice.VoiceExtention;
45
46
47
48
49
50 public abstract class AbstractModem implements Modem {
51
52 private static final Logger log = Logger.getLogger("atmodem4j-core");
53 private InputStream modemInputStream;
54 private OutputStream modemOutputStream;
55 private DefaultConnection connection;
56 private final Parser parser;
57 private String lastEcho;
58 private ResultCodeToken lastResultCode;
59 private String[] initStrings = new String[]{"ATE1V1Q0"};
60 private final int defaultWaitTime = 2000;
61 private final int defaultTrys = 3;
62 private final int defaultDialWaitTime = 30000;
63 private CallState callState = CallState.Idle;
64 private CallHandler waitingOutgoingCall;
65 private CallHandler waitingIncommingCall;
66 private RingListener ringListener;
67 private GsmExtention gsmExtention;
68 private VoiceExtention voiceExtention;
69 private FaxExtention faxExtention;
70 private final Object promptLock = new Object();
71
72
73
74
75 @Override
76 public void setGsmExtention(GsmExtention gsmExtention) {
77 this.gsmExtention = gsmExtention;
78 gsmExtention.setModem(this);
79 }
80
81
82
83
84 @Override
85 public void setVoiceExtention(VoiceExtention voiceExtention) {
86 this.voiceExtention = voiceExtention;
87 voiceExtention.setModem(this);
88 }
89
90
91
92
93 @Override
94 public void setFaxExtention(FaxExtention faxExtention) {
95 this.faxExtention = faxExtention;
96 faxExtention.setModem(this);
97 }
98
99
100
101
102 @Override
103 public GsmExtention getGsmExtention() {
104 return gsmExtention;
105 }
106
107
108
109
110 @Override
111 public VoiceExtention getVoiceExtention() {
112 return voiceExtention;
113 }
114
115
116
117
118 @Override
119 public FaxExtention getFaxExtention() {
120 return faxExtention;
121 }
122
123 private void resetParser() {
124 parser.resetParser();
125 lastEcho = null;
126 lastResultCode = null;
127 }
128
129 @Override
130 public boolean sendCommandLineWithPrompt(String command, String data) throws
131 IOException, InterruptedException {
132 sendCommandLine(command);
133 waitForPrompt();
134 return sendRawData(data + Modem.EOF, defaultWaitTime * 10);
135 }
136
137 protected boolean waitForOK(int waitTime) throws InterruptedException {
138 long t = System.currentTimeMillis();
139 ResultCodeToken e = waitForResult(waitTime);
140 if (e == null || !e.isOk()) {
141 log.log(Level.WARNING, "Timeout waiting ({0} ms) for OK resCode=\"{1}\"", new Object[]{System.currentTimeMillis() - t, e != null ? e.getResultCode() : "e == null"});
142 } else {
143 log.log(Level.SEVERE, "Waited {0} ms for OK result: {1} ", new Object[]{System.currentTimeMillis() - t, e.getResultCode(), e.getResultCode()});
144 return e.isOk();
145 }
146 return (e == null) ? false : e.isOk();
147 }
148
149 private void waitForPrompt() throws InterruptedException {
150 long t = System.currentTimeMillis();
151 synchronized (promptLock) {
152 promptLock.wait(defaultWaitTime);
153 }
154 }
155
156 @Override
157 public int getDefaultTrys() {
158 return defaultTrys;
159 }
160
161 @Override
162 public int getDefaultWaitTime() {
163 return defaultWaitTime;
164 }
165
166 class DefaultConnection implements Connection {
167
168 private final InputStream connectionInputStream;
169 private final ConnectionOutputStream connectionOutputStream;
170
171 @Override
172 public Modem getModem() {
173 return AbstractModem.this;
174 }
175
176 class ConnectionOutputStream extends OutputStream {
177
178 @Override
179 public void close() {
180 disconnect();
181 }
182
183 @Override
184 public void write(int b) throws IOException {
185 if (isOnlineDataMode()) {
186 modemOutputStream.write(b);
187 } else {
188 throw new IOException("Stream closed");
189 }
190 }
191 }
192
193 public DefaultConnection() {
194 this.connectionInputStream = parser.getConnectionInputStream();
195 this.connectionOutputStream = new ConnectionOutputStream();
196 }
197
198 @Override
199 public InputStream getInputStream() {
200 return connectionInputStream;
201 }
202
203 @Override
204 public OutputStream getOutputStream() {
205 return connectionOutputStream;
206 }
207
208 @Override
209 public void disconnect() {
210 try {
211 hangUp();
212 } catch (IOException | InterruptedException ex) {
213 throw new RuntimeException(ex);
214 } finally {
215 try {
216 connectionInputStream.close();
217 } catch (IOException ex) {
218 throw new RuntimeException(ex);
219 }
220 }
221 }
222
223 @Override
224 public void exitDataMode() {
225 throw new UnsupportedOperationException("Not supported yet.");
226 }
227
228 @Override
229 public void reenterDataMode() {
230 throw new UnsupportedOperationException("Not supported yet.");
231 }
232
233 @Override
234 public boolean isConnected() {
235 return connection == this;
236 }
237
238 @Override
239 public boolean isOnlineDataMode() {
240 return parser.isOnlineDataMode();
241 }
242
243 @Override
244 public boolean isOnlineCommandMode() {
245 throw new UnsupportedOperationException("Not supported yet.");
246 }
247
248 @Override
249 protected void finalize() throws Throwable {
250 if (isConnected()) {
251 disconnect();
252 }
253 super.finalize();
254 }
255 }
256
257 public AbstractModem() {
258 parser = new Parser();
259 parser.setLineChars('\r', '\n');
260 parser.setModem(this);
261 }
262
263 @Override
264 public boolean isGsmExtention() {
265 return gsmExtention != null;
266 }
267
268 @Override
269 public boolean isVoiceExtention() {
270 return voiceExtention != null;
271 }
272
273 @Override
274 public boolean isFaxExtention() {
275 return faxExtention != null;
276 }
277
278 @Override
279 public void setSpeaker() {
280 throw new UnsupportedOperationException("Not yet implemented");
281 }
282
283 @Override
284 public boolean reset() throws IOException, InterruptedException {
285 return sendAndWaitForOK("ATZ");
286 }
287
288 @Override
289 public String getAutomaticAnswer() throws IOException, InterruptedException {
290 return sendAndEctractData("ATS0?")[0];
291 }
292
293 @Override
294 public String getCommandLineTerminationCharacter() throws IOException,
295 InterruptedException {
296 return sendAndEctractData("ATS3?")[0];
297 }
298
299 @Override
300 public String getResponseFormattingChar() throws IOException,
301 InterruptedException {
302 return sendAndEctractData("ATS4?")[0];
303 }
304
305 @Override
306 public String getCommandLineEditingChar() throws IOException,
307 InterruptedException {
308 return sendAndEctractData("ATS5?")[0];
309 }
310
311 @Override
312 public String getModulationSelection() throws IOException,
313 InterruptedException {
314 return sendAndEctractData("AT+MS?")[0];
315 }
316
317 @Override
318 public synchronized Connection dial(String number) throws IOException,
319 InterruptedException {
320 if (callState != CallState.Idle) {
321 throw new RuntimeException("Line busy: " + callState);
322 }
323 callState = CallState.DialPending;
324 for (int i = 0; i < defaultTrys; i++) {
325 if (callState != CallState.DialPending) {
326 break;
327 }
328 resetParser();
329 String commandline = "ATD " + number;
330 sendCommandLine(commandline);
331 Thread.sleep(defaultWaitTime);
332 String buffer = parser.getBuffer();
333 if (buffer == null || !buffer.contains(commandline + parser.getCR())) {
334 log.log(Level.SEVERE, "Retransmitt CMD! \"{0}\"", buffer);
335 sendCommandLine(commandline);
336 }
337 ResultCodeToken e = waitForResult(defaultDialWaitTime);
338 if (e == null) {
339 } else if (parser.isOnlineDataMode()) {
340 callState = CallState.callEstablished;
341 return connection;
342 }
343 }
344 throw new RuntimeException("NOT Connected");
345 }
346
347 @Override
348 public boolean isIdle() {
349 return callState == Modem.CallState.Idle;
350 }
351
352 @Override
353 public synchronized void dial(String number, CallHandler waitingOutgoingCall)
354 throws
355 IOException,
356 InterruptedException {
357 if (callState != Modem.CallState.Idle) {
358 throw new RuntimeException("Line busy: " + callState);
359 }
360 resetParser();
361 callState = CallState.DialPending;
362 this.waitingOutgoingCall = waitingOutgoingCall;
363 String commandline = "ATD " + number;
364 sendCommandLine(commandline);
365 Thread.sleep(defaultWaitTime);
366 String buffer = parser.getBuffer();
367 if (buffer == null || !buffer.contains(commandline + parser.getCR())) {
368 log.log(Level.SEVERE, "Retransmitt CMD! \"{0}\"", buffer);
369
370 }
371 }
372
373 @Override
374 public void setInitStrings(String... initStrings) {
375 this.initStrings = initStrings;
376 }
377
378
379
380
381
382 public String getInitString(int i) {
383 return initStrings[i];
384 }
385
386 public int getInitStringCount() {
387 return initStrings.length;
388 }
389
390 @Override
391 public boolean hangUp() throws IOException, InterruptedException {
392 boolean result;
393 Thread.sleep(1000);
394 if (parser.isOnlineDataMode()) {
395 parser.prepareOnlineHangup();
396 result = sendAndWaitForOK("+++ATH", defaultTrys, defaultWaitTime * 3);
397 } else {
398 result = sendAndWaitForOK("ATH");
399 }
400 if (result) {
401 connection = null;
402 callState = CallState.Idle;
403 }
404 return result;
405 }
406
407 @Override
408 public void reenterDataMode() {
409 throw new UnsupportedOperationException("Not yet implemented");
410 }
411
412 protected void sendCommandLine(String commandline) throws IOException {
413 log.log(Level.INFO, "Send: \"{0}\"", commandline);
414 modemOutputStream.write(commandline.getBytes());
415 modemOutputStream.write(parser.getCR());
416 }
417
418
419
420
421 public RingListener getRingListener() {
422 return ringListener;
423 }
424
425
426
427
428 @Override
429 public void setRingListener(RingListener ringListener) {
430 this.ringListener = ringListener;
431 }
432
433 private boolean sendRawData(String rawData, int timeout) throws IOException,
434 InterruptedException {
435 resetParser();
436 modemOutputStream.write(rawData.getBytes());
437 long t = System.currentTimeMillis();
438 return waitForOK(timeout);
439 }
440
441 @Override
442 public boolean sendAndWaitForOK(String cmdLine) throws IOException,
443 InterruptedException {
444 return sendAndWaitForOK(cmdLine, defaultTrys, defaultWaitTime);
445 }
446
447 @Override
448 public boolean sendAndWaitForOK(String cmdLine, int trys, int waitTime)
449 throws IOException, InterruptedException {
450 for (int i = 0; i < trys; i++) {
451 resetParser();
452 sendCommandLine(cmdLine);
453 if (waitForOK(waitTime)) {
454 return true;
455 }
456 }
457 throw new RuntimeException("Error during " + cmdLine);
458 }
459
460 @Override
461 public String[] sendAndEctractData(String cmdLine) throws IOException,
462 InterruptedException {
463 return sendAndEctractData(cmdLine, defaultTrys, defaultWaitTime);
464 }
465
466 @Override
467 public String[] sendAndEctractData(String cmdLine, int trys, int waitTime)
468 throws IOException, InterruptedException {
469 for (int i = 0; i < trys; i++) {
470 resetParser();
471 sendCommandLine(cmdLine);
472 ResultCodeToken e = waitForResult(waitTime);
473 if (e == null || !e.isOk()) {
474 } else {
475 return e.getData();
476 }
477 }
478 throw new RuntimeException("Error during " + cmdLine);
479 }
480
481 @Override
482 public boolean init() throws IOException, InterruptedException {
483 boolean result = false;
484 for (String s : initStrings) {
485 result = sendAndWaitForOK(s);
486 if (!result) {
487 return result;
488 }
489 }
490 return result;
491 }
492
493
494
495
496 public InputStream getModemInputStream() {
497 return modemInputStream;
498 }
499
500
501
502
503 @Override
504 public void setModemInputStream(InputStream modemInputStream) {
505 this.modemInputStream = modemInputStream;
506 parser.setInputStream(modemInputStream);
507 }
508
509
510
511
512 public OutputStream getModemOutputStream() {
513 return modemOutputStream;
514 }
515
516
517
518
519 @Override
520 public void setModemOutputStream(OutputStream modemOutputStream) {
521 this.modemOutputStream = modemOutputStream;
522 }
523
524 @Override
525 public synchronized void setIncommingCallHandler(
526 CallHandler waitingIncommingCall) throws
527 IOException,
528 InterruptedException {
529 this.waitingIncommingCall = waitingIncommingCall;
530 }
531
532 void exitDataMode() {
533 throw new UnsupportedOperationException("Not yet implemented");
534 }
535
536
537
538
539
540
541
542
543 private synchronized ResultCodeToken waitForResult(int waitTime) throws
544 InterruptedException {
545 long t = System.currentTimeMillis();
546 if (this.lastResultCode == null) {
547 this.wait(waitTime);
548 }
549 final ResultCodeToken result = lastResultCode;
550 lastResultCode = null;
551 if (result == null) {
552 log.log(Level.SEVERE, "{0} ms waited > NO RESULT: \"{1}\"", new Object[]{System.currentTimeMillis() - t, parser.getBuffer()});
553 }
554 return result;
555 }
556
557 @Override
558 public synchronized void parsedResultCode(Parser p, ResultCodeToken r) {
559 lastResultCode = r;
560 if (r.isConnect()) {
561 connection = new DefaultConnection();
562 if (callState == CallState.DialPending && waitingOutgoingCall
563 != null) {
564 waitingOutgoingCall.connect(this, connection);
565 } else if (callState == CallState.AnswerPending
566 && waitingIncommingCall != null) {
567 waitingIncommingCall.connect(this, connection);
568 }
569 notifyAll();
570 callState = CallState.callEstablished;
571 } else {
572 if (callState == CallState.DialPending && waitingOutgoingCall
573 != null) {
574 waitingOutgoingCall.parsedResultCode(this, r);
575 } else if (callState == CallState.AnswerPending
576 && waitingIncommingCall != null) {
577 waitingIncommingCall.parsedResultCode(this, r);
578 }
579 notifyAll();
580 callState = CallState.Idle;
581 }
582 }
583
584 @Override
585 public synchronized void parsedRing(Parser p) {
586 if (waitingIncommingCall == null && ringListener != null) {
587 waitingIncommingCall = ringListener.ring(this);
588 }
589 if (waitingIncommingCall != null && (callState == CallState.Idle
590 || callState == CallState.AnswerPending)) {
591 try {
592 if (waitingIncommingCall.ring(this)) {
593 sendCommandLine("ATA");
594 waitingIncommingCall.answered(this);
595 }
596 callState = CallState.AnswerPending;
597 } catch (IOException ex) {
598 log.severe("Error answering call");
599 }
600 }
601 notifyAll();
602 }
603
604 public void removeHandler(CallHandler handler) {
605 if (waitingIncommingCall == handler) {
606 waitingIncommingCall = null;
607 }
608 if (waitingOutgoingCall == handler) {
609 waitingOutgoingCall = null;
610 }
611 }
612
613 protected String extractLineData(String[] strings) {
614 return extractData(strings[0]);
615 }
616
617 @Override
618 public void garbageCollected(Parser p, String s) {
619 log.log(Level.FINE, "Garbage collected: {0}", s);
620 }
621
622 @Override
623 public void parsedPrompt(Parser p) {
624 synchronized (promptLock) {
625 promptLock.notifyAll();
626 }
627 }
628
629 @Override
630 public String extractData(String data) {
631 int start = data.indexOf(':') + 1;
632 for (int i = start; i < data.length(); i++) {
633 if (data.charAt(i) != ' ') {
634 start = i;
635 break;
636 }
637 }
638 int end = data.length() - 1;
639 for (int i = end; i > start; i--) {
640 if (data.charAt(i) != ' ') {
641 end = i + 1;
642 break;
643 }
644 }
645 try {
646 return data.substring(start, end);
647 } catch (StringIndexOutOfBoundsException ex) {
648 System.out.println(data + " start: " + start + " end: " + end);
649 return data;
650 }
651
652 }
653
654 @Override
655 public boolean init(String... initStrings) throws IOException, InterruptedException {
656 throw new UnsupportedOperationException("Not supported yet.");
657 }
658
659 @Override
660 public void removeIncommingCallHandler(CallHandler callHandler) {
661 throw new UnsupportedOperationException("Not supported yet.");
662 }
663
664 @Override
665 public String[] getInitStrings() {
666 throw new UnsupportedOperationException("Not supported yet.");
667 }
668
669 @Override
670 public boolean close() {
671 throw new UnsupportedOperationException("Not supported yet.");
672 }
673 }