在Java中操作串列埠實現簡訊收發

pingyuan發表於2007-05-22
在Java中操作串列埠實現簡訊收發 (轉載自:http://blog.csdn.net/sinboy/archive/2005/12/09/548158.aspx)[@more@]

採用串列埠操作進行簡訊收發,是比較常見的一種方式.比如,很多群發軟體,用的就是這種方法.

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=PDU DATA

其中Length為經過編碼之後的PDU data的長度減去18再除以2得到,至於為什麼這樣算,可以參考AT命令手冊。有些Modem支援直接把上面的做為一個完整的資料包傳送過去(比如:FALCOM系列的),有的就非常的笨(比如:西門子 Tc35i),它要求必須先發前面的At命令,等接收到回應“>”符號之後,再傳送PDU DATA,十分麻煩,一點去不為我們們開發人員著想:(,我就是在這吃的虧。按以前的習慣做,結果怎麼都發不出去。

步驟是這樣的:

首先取出經過PDU編碼過的資料,計算它的長度,生成At+cmgs=

然後傳送出去

接著偵聽COM埠,等待接收">"字元

接收“>”之後,接著傳送剩餘的部分

然後再偵聽,接收回應,看是否傳送成功。如果傳送成功的話,一般會接到這樣的資料:

資料本身:PDU DATA

傳送序號:+CMGS:22

成功標誌:OK

有時候也能接收OK,但是收不到+CMGS:22這樣的東西,還是沒有傳送成功,這兩個都必須有,才真正傳送成功。

5.接收資料

接收資料其實也是在serialEvent裡面處理。如果象上面的設定那樣,設成自動接收,那麼一但有短訊息來了之後,自動觸發事件,完整的資料格式大概是這樣的:

+CMT:

PDU Data

"nr"

最後是以回車換行結束

我們收到完整的資料之後,把PDU data取出來,再按PDU編碼進行解碼就行。

6.除錯

首先最好能用超級終端,因為它使用起來很方便,直接輸命令,直接看結果。

還有一些非常好的串列埠監測除錯軟體,能直接顯示出來你向串列埠都發了什麼資料,從串列埠接收到了什麼資料,傳送接收的對錯,一看就清楚了。我在調TC35i的時候就折騰了好幾天,就是不知道是什麼原因,最後有網友建議我用串列埠監測軟體,結果我沒有半個小時就把問題搞定了,不是它,我都要哭了。強烈推薦一款是AcessPort,中文,免費,非常好用。

7.關於Text方式

格式如下:

AT+CMGS="+8613912345678"Text content

如果是英文數字的話,直接傳送就行了,接收到的也是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 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 msg = new SubmitPack();
ArrayList destList = new ArrayList();
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 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;
}

/**
* 傳送短訊息是否成功.
*


* 判斷依據: 收到回應的訊息中有+CMGS:,緊接著是兩個換行回車(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("rn");
// if (index1 > index0 && index1 + 2 < data.length()) {
// String str=data.substring(index1+2);
// index2=str.indexOf("rn");
// if(index2>0 && index2 //
// 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("rn");
if (index1 != -1 && index1 + 2 < data.length()) {
data = data.substring(index1 + 2);

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

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7199667/viewspace-915881/,如需轉載,請註明出處,否則將追究法律責任。

相關文章