View Javadoc
1   /*
2    * To change this template, choose Tools | Templates
3    * and open the template in the editor.
4    */
5   package net.sf.atmodem4j.core;
6   
7   /*
8    * #%L
9    * ATModem4J Core
10   * %%
11   * Copyright (C) 2009 - 2014 atmodem4j
12   * %%
13   * atmodem4j - Drivers for the AT modem - http://atmodem4j.sourceforge.net/
14   * Copyright (C) 2009-2014, atmodem4j.sf.net, and individual contributors as indicated
15   * by the @authors tag. See the copyright.txt in the distribution for a
16   * full listing of individual contributors.
17   * 
18   * This is free software; you can redistribute it and/or modify it
19   * under the terms of the GNU General Public License as
20   * published by the Free Software Foundation; either version 3 of
21   * the License, or (at your option) any later version.
22   * 
23   * This software is distributed in the hope that it will be useful,
24   * but WITHOUT ANY WARRANTY; without even the implied warranty of
25   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26   * Lesser General Public License for more details.
27   * 
28   * You should have received a copy of the GNU Lesser General Public
29   * License along with this software; if not, write to the Free
30   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
31   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
32   * #L%
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   * @author aploese
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       * @param gsmExtention the gsmExtention to set
74       */
75      @Override
76      public void setGsmExtention(GsmExtention gsmExtention) {
77          this.gsmExtention = gsmExtention;
78          gsmExtention.setModem(this);
79      }
80  
81      /**
82       * @param voiceExtention the voiceExtention to set
83       */
84      @Override
85      public void setVoiceExtention(VoiceExtention voiceExtention) {
86          this.voiceExtention = voiceExtention;
87          voiceExtention.setModem(this);
88      }
89  
90      /**
91       * @param faxExtention the faxExtention to set
92       */
93      @Override
94      public void setFaxExtention(FaxExtention faxExtention) {
95          this.faxExtention = faxExtention;
96          faxExtention.setModem(this);
97      }
98  
99      /**
100      * @return the gsmExtention
101      */
102     @Override
103     public GsmExtention getGsmExtention() {
104         return gsmExtention;
105     }
106 
107     /**
108      * @return the voiceExtention
109      */
110     @Override
111     public VoiceExtention getVoiceExtention() {
112         return voiceExtention;
113     }
114 
115     /**
116      * @return the faxExtention
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); // 30 s
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             //    sendCommandLine(commandline);
370         }
371     }
372 
373     @Override
374     public void setInitStrings(String... initStrings) {
375         this.initStrings = initStrings;
376     }
377 
378     /**
379      * @param i
380      * @return the initString
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      * @return the ringListener
420      */
421     public RingListener getRingListener() {
422         return ringListener;
423     }
424 
425     /**
426      * @param ringListener the ringListener to set
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      * @return the modemInputStream
495      */
496     public InputStream getModemInputStream() {
497         return modemInputStream;
498     }
499 
500     /**
501      * @param modemInputStream the modemInputStream to set
502      */
503     @Override
504     public void setModemInputStream(InputStream modemInputStream) {
505         this.modemInputStream = modemInputStream;
506         parser.setInputStream(modemInputStream);
507     }
508 
509     /**
510      * @return the modemOutputStream
511      */
512     public OutputStream getModemOutputStream() {
513         return modemOutputStream;
514     }
515 
516     /**
517      * @param modemOutputStream the modemOutputStream to set
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      * Use FIFO to store Events?
538      *
539      * @param i
540      * @return
541      * @throws java.lang.InterruptedException
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 }