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

来源:互联网 发布:点对点软件 编辑:程序博客网 时间:2024/05/21 10:12
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()); 
  } 



然后获取你指定的端口: 



成功打开端口之后,设置端口的相关参数,如波特率、数据位、奇偶校验位等.这个跟具体的设备有关,不过一般情况下波特率为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 
             + " "; 
          smContent = coded 
            + (char) Integer.parseInt("1A", 
              16); 

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

         } else 
          cmd1 = "AT+CMGS="+86" 
            + dest 
            + "" " 
            + 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(" "); 
    // if (index1 > index0 && index1 + 2 < data.length()) { 
    // String str=data.substring(index1+2); 
    // index2=str.indexOf(" "); 
    // 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(" "); 
     if (index1 != -1 && index1 + 2 < data.length()) ...{ 
      data = data.substring(index1 + 2); 

      int index2 = data.indexOf(" "); 
      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