济南网页设计|自助建站济南网站建设|聊城网站建设济南网站制作|济南网站优化 http://www.mzwkj.com www.tuanqv.com

在Java中操作串口实现短信收发

采用串口操作进行短信收发,是比较常见的一种方式.比如,很多群发软件,用的就是这种方法.

1.配置comm.jar.

Comm.jar是Sub实现底层串口操作的API,调用了本地的DLL文件,因为Java本身不具备直接访问硬件设置的能力,都是通过调用本地方法来实现的.可以Java的官方网站下载.下载之后把其中Comm.jar包导入到工程的Classpath中,把另外两个非常重要的文件javax.comm.properties和win32com.dll考贝到你的工程目录下,即java.user下.

2.打开串口.

在打开串口前首先要加载Win32com.dll,因为我们没有把它放到JRE路径下,所以必须要自己显式的加载.

  String driverName = "com.sun.comm.Win32Driver";   CommDriver driver = null;

  try {    System.loadLibrary("win32com");     driver = (CommDriver) Class.forName(driverName).newInstance();    driver.initialize();   } catch (InstantiationException e1) {    logger.error("1:" + e1.getMessage());     } catch (IllegalAccessException e1) {    logger.error("2:" + e1.getMessage());     } catch (ClassNotFoundException e1) {    logger.error(e1.getMessage());    }

然后获取你指定的端口:

 SerialPort sPort = null;   CommPortIdentifier portID;   String owner = new String("modemn");   int keeptime = 5000;   Enumeration portList;   portList = CommPortIdentifier.getPortIdentifiers();

// 如果有多个端口   while (portList.hasMoreElements()) {    portID = (CommPortIdentifier) portList.nextElement();    if (portID.getName().equals(com))     try {      sPort = (SerialPort) portID.open(owner, keeptime);      break;     }// 打开一个串口     catch (PortInUseException e) {      logger.fatal(e.getMessage());      System.exit(1);     }

  }// while

成功打开端口之后,设置端口的相关参数,如波特率、数据位、奇偶校验位等.这个跟具体的设备有关,不过一般情况下波特率为9600,数据位为8,停止位为1,奇偶为0,流量控制为Off:

if (sPort != null) {    logger.debug("serial name is :" + sPort.getName());    try {     // 设置串口的参数     sPort.setSerialPortParams(9600,// 波特率       SerialPort.DATABITS_8,// 数据位数       SerialPort.STOPBITS_1, // 停止位       SerialPort.PARITY_NONE);// 奇偶位    } catch (UnsupportedCommOperationException e) {     e.printStackTrace();     logger.error(e.getMessage());    }   }

3.对端口进行初始化

 对进行数据接收或发送之前,还要先进行一些参数的设置。重要的有:

AT+cmgf=0(设置Modem收发采用Pdu方式,1为Text方式。有些Modem可能正好相反,具体参考Modem的At指令说明)

At+cnmi=2,2,0,0,0(设置Modem自动接收,AT指令说明书给的定义是新的短消息指示说明,就是说说有了新的短消息,怎么给你提示。这个设置是有消息将自动显示,无需进行读卡操作。看到有很网上的例子都是1,1,这样还要通过读卡操作才能得到短消息,十分不方便,还降低了SIM卡的使用寿命)

At+csmp=17,167,0,240(设置短消息文本模式参数。其中17是指SMS-SUBMIT的十进制整数表达形式,即提交;167指是有效期的整数表达形式;0指的是协议标识的十进制整数表示形式。前三个参数都该命令的默认值。最后一240指是编码方案,在Text方式下发送英文和Pdu模式下一般设置成240.如果要在Text模式下发送中文,有多Modem要设成8)

对端口所作的上述初始化工作,可以在超终终端里直接设置。但最好是把它写在程序里,在程序启动之后就进行此工作,避免手工操作的麻烦。

对Modem进行初始化,就必须把上述命令输出到Modem的端口上,还要看它的反回值是不是OK。要想得到返回值,就要对COM端口进行侦听了。所以初始化的工作有三步:

第一,侦听端口     sPort.addEventListener(this);     sPort.notifyOnDataAvailable(true);

第二,建立输入输出流,把初始化命令输出到Modem的COM端口

// 用配置参数初始化MODEM    msg = conf.initParam();    if (msg != null) {     if (conf.modemMode() != null && conf.modemMode().equals("0"))       if (isPduMode)         msg = "at+cmgf=0;" + msg;     else        msg = "at+cmgf=1;" + msg;        sendMsg(msg.getBytes(), sPort);        sendOKFlag = true;    }

 

// 把短消息通过数据猫发送出去  private void sendMsg(byte[] msg, SerialPort sPort) {

  DataOutputStream pw;   if (msg != null && sPort != null)    try {

    pw = new DataOutputStream(sPort.getOutputStream());     pw.write(msg);

    pw.flush();     pw.close();     logger.debug("msg has been send from Modemn:");

   } catch (IOException e) {     logger.error(e.getMessage());     e.printStackTrace();    }  }

// 处理侦听到的串口事件  public synchronized void serialEvent(SerialPortEvent ev) {

  DataInputStream in;   int c = 0;   StringBuffer sb = null;   // 如果有串口事件发生   if (ev.getEventType() == SerialPortEvent.DATA_AVAILABLE) {

   try {     in = new DataInputStream(sPort.getInputStream());     sb = new StringBuffer();     while ((c = in.read()) != -1) {      sb.append((char) c);

     System.out.println(sb);      if (handleRecData(sb)) {       logger.debug("从Modem接收到的数据" + sb);       sb = new StringBuffer();

     }

    }

   }// try    catch (IOException e) {     logger.error(e.getMessage());     e.printStackTrace();    }   }  }

serialEvent事件就是刚才添加侦听之后要工作的部分。如果写过界面程序的人,对这个会比较熟悉。一但Modem回复数据,此事件就会触发。我们在发送完初始化命令之后,就从此事件中接收数据,看能不能收到OK。如果收到,就初始化成功。

4.发送数据

成功进行初始化之后,就可以进行正常的数据收发了。我们在此使用PDU的方式做说明,Text方式一是很多Modem或手机都不支持,二是也比较简单,在最后再提。

首先我们可以把要发送的数据放到发送缓冲列表中,对数据一条一条的发送。因为Modem不支持异步(我自己的经验),我们只能发完一条之后,再发第二条。发送的原理同发送初始化命令一样,但是要注意的一点是发送内容首先要进行PDU编码,发送的格式为:

AT+CMGS=<Length><CR>PDU DATA<CTRL-z>

其中Length为经过编码之后的PDU data的长度减去18再除以2得到,至于为什么这样算,可以参考AT命令手册。有些Modem支持直接把上面的做为一个完整的数据包发送过去(比如:FALCOM系列的),有的就非常的笨(比如:西门子 Tc35i),它要求必须先发前面的At命令,等接收到回应“>”符号之后,再发送PDU DATA,十分麻烦,一点去不为咱们开发人员着想:(,我就是在这吃的亏。按以前的习惯做,结果怎么都发不出去。

步骤是这样的:

首先取出经过PDU编码过的数据,计算它的长度,生成At+cmgs=<length><cr>

然后发送出去

接着侦听COM端口,等待接收">"字符

接收“>”之后,接着发送剩余的部分

然后再侦听,接收回应,看是否发送成功。如果发送成功的话,一般会接到这样的数据:

数据本身:PDU DATA<CTRL-z>

发送序号:+CMGS:22

成功标志:OK

有时候也能接收OK,但是收不到+CMGS:22这样的东西,还是没有发送成功,这两个都必须有,才真正发送成功。

5.接收数据

接收数据其实也是在serialEvent里面处理。如果象上面的设置那样,设成自动接收,那么一但有短消息来了之后,自动触发事件,完整的数据格式大概是这样的:

+CMT:<data><time><index>

PDU Data

"/n/r"

最后是以回车换行结束

我们收到完整的数据之后,把PDU data取出来,再按PDU编码进行解码就行。

6.调试

首先最好能用超级终端,因为它使用起来很方便,直接输命令,直接看结果。

还有一些非常好的串口监测调试软件,能直接显示出来你向串口都发了什么数据,从串口接收到了什么数据,发送接收的对错,一看就清楚了。我在调TC35i的时候就折腾了好几天,就是不知道是什么原因,最后有网友建议我用串口监测软件,结果我没有半个小时就把问题搞定了,不是它,我都要哭了。强烈推荐一款是AcessPort,中文,免费,非常好用。

7.关于Text方式

格式如下:

AT+CMGS="+8613912345678"<CR>Text content<ctrl-z>

如果是英文数字的话,直接发送就行了,接收到的也是Ascii码,无需编码

如果是中文的话,要先进行Unicode编码,接收也一样,收到之后要进行Unicode转gb的转换

另外先把配置设置好

8.参考源代码:

package com.gftech.dcs.commu;

import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Enumeration; import java.util.TooManyListenersException;

import javax.comm.CommDriver; import javax.comm.CommPortIdentifier; import javax.comm.PortInUseException; import javax.comm.SerialPort; import javax.comm.SerialPortEvent; import javax.comm.SerialPortEventListener; import javax.comm.UnsupportedCommOperationException;

import org.apache.log4j.Logger; import org.apache.log4j.xml.DOMConfigurator;

import com.gftech.common.PduPack; import com.gftech.smp.DeliverPack; import com.gftech.smp.SubmitPack;

/**  * @author sinboy  *  * 无线MODEM适配器。 用来从MODEM发送短消息,以及从MODEM接收短消息  */ public class ModemAdapter extends Thread implements SerialPortEventListener {  private static ModemAdapter modem;

 // 发送是否已成功完成  private boolean sendOKFlag;

 private int errCount;

 // 发送模式是否是PDU方式  private boolean isPduMode;

 private String smContent;

 private ArrayList<SubmitPack> sendBuffer;

 // 要打开使用的串口  private SerialPort sPort;

 static Logger logger = Logger.getLogger(ModemAdapter.class);

 private ModemAdapter() {   DOMConfigurator.configure(MyFinal.LOG4J_CONF_FILE);

  isPduMode = false;   errCount = 0;

  logger.debug("Add a test data");   sendBuffer = new ArrayList<SubmitPack>();   SubmitPack msg = new SubmitPack();   ArrayList<String> destList = new ArrayList<String>();   destList.add("136××××××××");   msg.setSm("你好,张新波");   msg.setDestAddr(destList);   add(msg);

  start();

 }

 public static ModemAdapter getInstance() {   if (modem == null)    modem = new ModemAdapter();   return modem;  }

 // 得到计算机的串口  private SerialPort getSerialPort(String com) {   SerialPort sPort = null;   CommPortIdentifier portID;   String owner = new String("modemn");   int keeptime = 5000;   Enumeration portList;   portList = CommPortIdentifier.getPortIdentifiers();

  String driverName = "com.sun.comm.Win32Driver";   CommDriver driver = null;

  try {    System.loadLibrary("win32com");    logger.debug("Win32Com Library Loaded");

   driver = (CommDriver) Class.forName(driverName).newInstance();    driver.initialize();    logger.debug("Win32Driver Initialized");   } catch (InstantiationException e1) {    logger.error("1:" + e1.getMessage());    e1.printStackTrace();   } catch (IllegalAccessException e1) {    logger.error("2:" + e1.getMessage());    e1.printStackTrace();   } catch (ClassNotFoundException e1) {    logger.error(e1.getMessage());    e1.printStackTrace();   }   // 如果有多个端口   while (portList.hasMoreElements()) {    portID = (CommPortIdentifier) portList.nextElement();    if (portID.getName().equals(com))     try {      sPort = (SerialPort) portID.open(owner, keeptime);      break;     }// 打开一个串口     catch (PortInUseException e) {      logger.fatal(e.getMessage());      System.exit(1);     }

  }// while

  if (sPort != null) {    logger.debug("serial name is :" + sPort.getName());    try {     // 设置串口的参数     sPort.setSerialPortParams(9600,// 波特率       SerialPort.DATABITS_8,// 数据位数       SerialPort.STOPBITS_1, // 停止位       SerialPort.PARITY_NONE);// 奇偶位    } catch (UnsupportedCommOperationException e) {     e.printStackTrace();     logger.error(e.getMessage());    }   }

  return sPort;  }

 private boolean init() {   boolean result = false;   Config conf = new Config();   String comName = conf.comName();   sPort = getSerialPort(comName);   String msg = null;

  if (sPort != null) {    listenSerialPort(sPort);    // 用配置参数初始化MODEM    msg = conf.initParam();    if (msg != null) {     if (conf.modemMode() != null && conf.modemMode().equals("0"))      isPduMode = true;

    if (isPduMode)      msg = "at+cmgf=0;" + msg;     else      msg = "at+cmgf=1;" + msg;      sendMsg(msg, sPort);      sendOKFlag = true;    }   }

  for (int i = 0; i < 100; i++) {    try {     Thread.sleep(1000);

    if (sendOKFlag) {      logger.debug("初始化MODEM成功!");      return true;     }    } catch (InterruptedException e) {     e.printStackTrace();    }   }

  return result;  }

 // 把短消息通过数据猫发送出去  private void sendMsg(String msg, SerialPort sPort) {

  PrintWriter pw;   if (msg != null && sPort != null)    try {

    pw = new PrintWriter(sPort.getOutputStream());     pw.println(msg);

    pw.flush();     pw.close();     logger.debug("msg has been send from Modemn:");     logger.debug(msg);    } catch (IOException e) {     logger.error(e.getMessage());     e.printStackTrace();    }  }

 // 把短消息通过数据猫发送出去  private void sendMsg(byte[] msg, SerialPort sPort) {

  DataOutputStream pw;   if (msg != null && sPort != null)    try {

    pw = new DataOutputStream(sPort.getOutputStream());     pw.write(msg);

    pw.flush();     pw.close();     logger.debug("msg has been send from Modemn:");

   } catch (IOException e) {     logger.error(e.getMessage());     e.printStackTrace();    }  }

 private void listenSerialPort(SerialPort sPort) {

  if (sPort != null)    try {

    sPort.addEventListener(this);     sPort.notifyOnDataAvailable(true);    } catch (TooManyListenersException e) {     logger.error(e.getMessage());     e.printStackTrace();    }

 }

 public void run() {   int waitCount = 0;   String cmd1 = null;

  SubmitPack msg;   String dest;   String content;

  if (init()) {

   while (true) {     try {      sleep(10);     } catch (InterruptedException e) {      e.printStackTrace();     }     msg = getOneMsg();     if (msg != null) {      ArrayList<String> destList = msg.getDestAddr();      for (int i = 0; destList != null && i < destList.size(); i++) {       dest = (String) destList.get(i);       content = msg.getSm();

      if (content != null) {        while (true) {         if (sendOKFlag == true) {          if (isPduMode) {           Config conf = new Config();           PduPack pack = new PduPack();           pack.setAddr(dest);           pack.setMsgContent(content);           pack.setSmsc(conf.smsc());           String coded = pack.getCodedResult();           if (coded != null             && coded.length() > 18)            cmd1 = "AT+CMGS="              + (coded.length() - 18) / 2              + "/r";           smContent = coded             + (char) Integer.parseInt("1A",               16);

          // cmd1+=smContent;           sendMsg(cmd1.getBytes(), sPort);           cmd1 = null;

         } else           cmd1 = "AT+CMGS=/"+86"             + dest             + "/"/r"             + content             + (char) Integer.parseInt("1a",               16) + "z";          if (cmd1 != null) {           logger.debug("Cmd:" + cmd1);           sendMsg(cmd1, sPort);           sendOKFlag = false;           logger.debug("isSendOK=false");          }          break;         } else          try {           sleep(100);           if (waitCount > 300) {            sendOKFlag = true;            waitCount = 0;           } else            waitCount++;          } catch (InterruptedException e) {          }        }       }      }     }

   }// while   } else {    logger.fatal("无法成功初始化MODEM,请检查设备");    System.exit(0);   }

 }

 // 处理侦听到的串口事件  public synchronized void serialEvent(SerialPortEvent ev) {

  DataInputStream in;   int c = 0;   StringBuffer sb = null;   // 如果有串口事件发生   if (ev.getEventType() == SerialPortEvent.DATA_AVAILABLE) {

   try {     in = new DataInputStream(sPort.getInputStream());     sb = new StringBuffer();     while ((c = in.read()) != -1) {      sb.append((char) c);

     System.out.println(sb);      if (handleRecData(sb)) {       logger.debug("从Modem接收到的数据" + sb);       sb = new StringBuffer();

     }

    }

   }// try    catch (IOException e) {     logger.error(e.getMessage());     e.printStackTrace();    }   }  }

 /**   * 判断接收到的数据是否最后是以"OK"结束的   *   * @param data   * @return   */  private boolean isRecOK(String data) {   final String OK_FLAG = "OK";   int index1 = 0;

  if (data != null) {    index1 = data.indexOf(OK_FLAG);

   if (index1 >= 0 && index1 + 4 <= data.length()) {     String t = data.substring(index1 + 2);     byte[] b = t.getBytes();     if (b.length >= 2) {      if (b[0] == 0x0D && b[1] == 0x0A)       return true;     }    }   }

  return false;  }

 /**   * 发送短消息是否成功.   * <p>   * 判断依据: 收到回应的消息中有+CMGS:<space><number>,紧接着是两个换行回车(0x0D,0x0A,0x0D,0x0A),   * 然后是OK,最后是一个回车换行(0x0D,0x0A)   *   * @param data   * @return   */  private boolean isSendOK(String data) {   final String FLAG = "+CMGS:";   int index = -1;   int index2 = -1;

  if (data != null) {    index = data.indexOf(FLAG);    if (index > 0) {     index += 6;     if (index < data.length()) {      String temp = data.substring(index);      index = 0;      byte[] b = temp.getBytes();      for (int i = 0; i < b.length; i++) {       if (b[i] == 0x0D) {        index2 = i;        break;       }      }

     if (index2 < temp.length() && index2 > index + 1) {       String t1 = temp.substring(index + 1, index2);

      try {        int seqid = Integer.parseInt(t1);        logger.debug("seqID:" + seqid);

       if (index2 + 8 == temp.length()) {         // 两个回车换行符         if (b[index2] == 0x0D && b[++index2] == 0x0A           && b[++index2] == 0x0D           && b[++index2] == 0x0A) {          if (b[++index2] == 0x4F            && b[++index2] == 0x4B) {// OK           if (b[++index2] == 0x0D             && b[++index2] == 0x0A) {// 一个回车换行            return true;           }          }         }        }       } catch (NumberFormatException e) {        e.printStackTrace();        return false;       }      }     }    }   }

  return false;  }

 /**   * 判断接收到的字符串最后是否是以"ERROR"结束的   *   * @param data   * @return   */  private boolean isRecError(String data) {

  final String FLAG = "ERROR";

  int index1 = 0;

  if (data != null) {    index1 = data.indexOf(FLAG);

   if (index1 >= 0 && index1 + 7 <= data.length()) {     String t = data.substring(index1 + 5);     byte[] b = t.getBytes();     if (b.length >= 2) {      if (b[0] == 0x0D && b[1] == 0x0A)       return true;     }    }   }

  return false;  }

 /**   * 是否接收到手机发来的完整数据,上传的数据是以"+CMT:"开头   *   * @param data   * @return   */  private boolean isRecData(String data) {   final String BEGIN_FLAG = "+CMT:";   int index0 = -1;   int index1 = -1;   int index2 = -1;

  if (data != null) {    index0 = data.indexOf(BEGIN_FLAG);    if (index0 >= 0 && index0 < data.length()) {     // data=data.substring(index0);     // index1 = data.indexOf("/r/n");     // if (index1 > index0 && index1 + 2 < data.length()) {     // String str=data.substring(index1+2);     // index2=str.indexOf("/r/n");     // if(index2>0 && index2<str.length()){     //            // return true;     // }     // }

    return true;

   }   }   return false;  }

 private boolean handleRecData(StringBuffer sb) {   String data = null;

  if (sb != null) {    data = sb.toString();    if (isRecOK(data)) {     sendOKFlag = true;     return true;    } else if (isRecError(data)) {     errCount++;     if (errCount > 3) {      sendOKFlag = true;      errCount = 0;     }

    return true;    } else if (sb.indexOf(">") != -1 && smContent != null) {     sendMsg(smContent.getBytes(), sPort);     smContent = null;    }

   else {     int index0 = data.lastIndexOf("+CMT:");

    if (index0 >= 0 && index0 < data.length()) {      data = data.substring(index0);

     int index1 = data.indexOf("/r/n");      if (index1 != -1 && index1 + 2 < data.length()) {       data = data.substring(index1 + 2);

      int index2 = data.indexOf("/r/n");       if (index2 > 1 && index2 < data.length()) {        data = data.substring(0, index2);        if (data != null && data.length() > 0) {         PduPack pack = new PduPack(data);         String srcAddr = pack.getAddr();         String content = pack.getMsgContent();         String destAddr = "012345";

        if (srcAddr != null && content != null) {          logger.debug("srcAddr:"+srcAddr);          logger.debug("content:"+content);                    Config conf = new Config();          destAddr = conf.cmppSSN();          DeliverPack deliver = new DeliverPack(            srcAddr, destAddr, content);          ServerAdapter server = ServerAdapter            .getInstance();          server.addDeliverPack(deliver);

         return true;         }        }       }      }     }

   }

  }   return false;  }

   private SubmitPack getOneMsg() {   SubmitPack result = null;

  if (sendBuffer != null && sendBuffer.size() > 0)    result = (SubmitPack) sendBuffer.remove(0);   return result;  }

 public void add(SubmitPack msg) {   sendBuffer.add(msg);  }

}

9.结束语

在如下环境调试通过:

Eclipse3.1,Jdk1.5,西门子 Tc35i Modem

关于PDU编码的详细说明,请参考bhw98的专栏

关于PDU编码实现的Java源代码,我会在另外的文章中介绍。

10.参考资料

 *《AT Command set-Siemens Celluler Engines》

*bhw98的专栏  http://blog.csdn.net/bhw98/archive/2003/03/27/19666.aspx 

posted on 2008-06-27 13:01  路大侠  阅读(524)  评论(0)    收藏  举报

导航

济南户外拓展|企业户外拓展 http://www.tuanqv.com