Java利用Rxtx实现串口通信

本人用的硬件设备是SIM900,前段时间学校老师让完成一个用定时器检测并发短信的模块。特来分享一下。
首先提醒下想用javax.comm工具包的人们,这个工具包在windows下没有对64位机器的jar包,只有32位的,之前也走过一段弯路。强烈建议使用Rxtx。
Rxtx开源包下载地址:http://users.frii.com/jarvi/rxtx/download.html
使用方法:
windows平台:
1、把rxtxParallel.dll、rxtxSerial.dll拷贝到:C:\WINDOWS\system32下。
2、如果是在开发的时候(JDK),需要把RXTXcomm.jar、rxtxParallel.dll、rxtxSerial.dll拷贝到..\jre...\lib\ext下;如:D:\Program Files\Java\jre1.6.0_02\lib\ext
3、把jar包导到项目中,这个不用说了..

先建一个Model类,叫CommonSms,代码如下:

package com.msm;
import java.util.Date;
public class CommonSms{
    /** id */
    private int id;
    /**type区分维修人员还是营业厅发送短信*/
    private int type;//区分给维修人员还是给营业厅发送短信
    /**短信内容*/
    private String smstext;
    /**短信发送方*/
    private String sender;//短信发送方
    /**短信接收发*/
    private String recver;//短信接收发
    /**时间*/
    private Date date;
    /**消息状态*/
    private String state;//消息状态
    /**对应的营业厅名称*/
    private String bhname;//对应的营业厅名称
    public String getBhname() {
        return bhname;
    }
    public void setBhname(String bhname) {
        this.bhname = bhname;
    }
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
    public String getSmstext() {
        return smstext;
    }
    public void setSmstext(String smstext) {
        this.smstext = smstext;
    }
    public Date getDate() {
        return date;
    }
    public void setDate(Date date) {
        this.date = date;
    }
    public int getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public int getType() {
        return type;
    }
    public void setType(int type) {
        this.type = type;
    }
    public String getSender() {
        return sender;
    }
    public void setSender(String sender) {
        this.sender = sender;
    }
    public String getRecver() {
        return recver;
    }
    public void setRecver(String recver) {
        this.recver = recver;
    }
}

第二个串口操作实现类Port,其中构造方法用来初始化硬件参数

package com.msm;

import gnu.io.*;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Random;


/***
 * 串口操作实现类
 */
public class Port {
    private CommPortIdentifier portId;
    private SerialPort serialPort;
    private OutputStreamWriter out;
    private InputStreamReader in;
    private String COMname;
    private static char symbol1 = 13;

    public String getCOMname() {
        return COMname;
    }
    public void setCOMname(String mname) {
        COMname = mname;
    }
    public CommPortIdentifier getPortId() {
        return portId;
    }

    public void setPortId(CommPortIdentifier portId) {
        this.portId = portId;
    }


    public SerialPort getSerialPort() {
        return serialPort;
    }

    public void setSerialPort(SerialPort serialPort) {
        this.serialPort = serialPort;
    }

    public OutputStreamWriter getOut() {
        return out;
    }

    public void setOut(OutputStreamWriter out) {
        this.out = out;
    }
    public InputStreamReader getIn() {
        return in;
    }

    public void setIn(InputStreamReader in) {
        this.in = in;
    }
    public boolean isused =true;

    public boolean isIsused() {
        return isused;
    }

    public void setIsused(boolean isused) {
        this.isused = isused;
    }
    /**
     * 打开com口
     * @param portName
     * @return
     */
    public  Port(String portName) {
        try {
            portId = CommPortIdentifier.getPortIdentifier(portName);
            if (portId == null) {
                System.out.println("port is null");
            }
            try {
                serialPort = (SerialPort) portId.open(getrechargeablePassword(),20000);
            } catch (PortInUseException e) {
                System.gc();
                e.printStackTrace();
            }
            // 下面是得到用于和COM口通讯的输入、输出流。
            try {
                in = new InputStreamReader(serialPort.getInputStream());
                out = new OutputStreamWriter(serialPort.getOutputStream());
            } catch (IOException e) {
                System.gc();
                System.out.println("IOException");
            }
            // 下面是初始化COM口的传输参数,如传输速率:9600等。
            try {
                serialPort.setSerialPortParams(115200, SerialPort.DATABITS_8,
                        SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
                setCOMname(portId.getName());
                setIsused(true);
            } catch (UnsupportedCommOperationException e) {
                e.printStackTrace();
                System.gc();
            }

        } catch (NoSuchPortException e) {
            e.printStackTrace();
            System.gc();
        }
    }
    //获取密码
    public static String getrechargeablePassword() {
        Random random = new Random();
        char[] codeSequence = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
                'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
                'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6',
                '7', '8', '9', 'a', 'c', 'b', 'd', 'f', 'e', 'g', 'h', 'j',
                'i', 'l', 'k', 'n', 'm', 'o', 'p', 'q', 'r', 's', 't', 'u',
                'w', 'v' };
        String strRand = "";
        for (int i = 0; i < 18; i++) {
            strRand = strRand
                    + String.valueOf(codeSequence[random.nextInt(59)]);
        }
        return strRand;
    }
    /**
     * 检查SIM是否存在
     * @return
     */
    public  boolean chakanPort() {
        try {
            String  atCommand = "AT+ccid";
            String  strReturn = sendAT(atCommand);
            if (strReturn.indexOf("OK", 0) != -1) {
                return true;
            }
            return false;
        } catch (Exception ex) {
            System.gc();
            ex.printStackTrace();
            return false;
        }
    }
    /**
     * 关闭COM口
     * @return boolean
     */
    public void close() {
        try {
            in.close();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        serialPort.close();
        System.gc();
        setIsused(false);
    }

    /**
     * 向串口中写入字符串命令
     * @param s 字符串命令
     * @throws Exception 异常
     */
    public  void writeln(String s) throws Exception {
        out.write(s);
        out.write('\r');
        out.flush();
    }

    /**
     * 读取COM命令的返回字符串
     * @return 结果字符串
     * @throws Exception
     */
    public  String read() throws Exception {
        int n, i;
        char c;
        String answer = "";
        for (i = 0; i < 100; i++) {
            while (in.ready()) {
                n = in.read();
                if (n != -1) {
                    c = (char) n;
                    answer = answer + c;
                    Thread.sleep(1);
                } else
                    break;
            }
            if (answer.indexOf("OK") != -1) {
                break;
            }
            Thread.sleep(100);
        }
        return answer;
    }

    /**
     * 向串口发送AT指令
     * @param atcommand 指令内容
     * @return 指令返回结果
     * @throws java.rmi.RemoteException
     */
    public String sendAT(String atcommand) throws java.rmi.RemoteException {
        String s = "";
        try {
            Thread.sleep(150);
            writeln(atcommand);
            Thread.sleep(100);
            s = read();
            Thread.sleep(200);
        } catch (Exception e) {
            System.gc();
            System.out.println("ERROR: send AT command failed; " + "Command: "
                    + atcommand + "; Answer: " + s + "  " + e);
        }
        return s;
    }
}

还有一个工具类,用来转换字符串和对电话号经行编码。
字符串的转化不说了,说一下电话号的编码。串口的通信的消息模式分为两种,一种text模式,一种pdu模式,经测试,text模式是发不了中文短信的,所以我们要用pdu模式,而pdu模式和text对应的AT指令不同,对电话号的编码也不同,不了解AT指令可以自行百度,不知道也没事,本程序后面也给了完整的编码代码,pdu的手机话编码有它自己的格式,是一个以0011000D9168开头后经奇数和偶数位换位等复杂编码实现的。具体可以看代码,其实也是很简单的。
以下是工具类源代码:

package com.msm;

import java.util.ArrayList;
import java.util.List;
import java.text.SimpleDateFormat;
import java.util.Date;

/***
 * 指令字符串操作类
 *
 */
public class StringUtil {
    /**
     * 使用Sms 的RecvSms(int index)的方法时,使用该方法解析MODEM返回的字符串
     * 根据MODEM返回的字符串,解析成一个CommonSms对象
     * @param str 串口返回的读取短信结果字符串
     * @param index 短信索引
     * @return
     */
    public static CommonSms analyseSMS(String str, int index) {
        CommonSms commonSms = new CommonSms();
        String mesContent;
        String[] s = str.split("\"");
        int len = s.length;
        commonSms.setId(index);
        mesContent = s[len - 1];
        if (mesContent.indexOf("OK") != -1) {
            mesContent = mesContent.substring(0, mesContent.indexOf("OK"));
        }
        mesContent = mesContent.trim();
        commonSms.setSmstext(analyseStr(mesContent));
       // commonSms.setSmstext(Unicode2GBK(analyseStr(mesContent)));

        // 短信有中文时使用
        //mes.setMessage(Unicode2GBK(analyseStr(mesContent)));
        SimpleDateFormat df = new SimpleDateFormat("yy/MM/dd hh:mm:ss");
        String datestring = s[len - 2].substring(0, s[len - 2].length() - 3)
                .replace(',', ' ');// 短信猫时间格式09/09/09 20:18:01+32
        Date date = null;
        try {
            date = df.parse(datestring);
            System.out.println(date.toLocaleString());
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
        commonSms.setDate(date);
        if (s[1].equals("REC READ")) {
            commonSms.setState("已读");
        } else {
            commonSms.setState("未读");
        }
        commonSms.setSender(s[3]);

        return commonSms;
    }

    /**
     * 使用Sms 的RecvSmsList()方法时,通过该方法解析MODEM返回来的字符串
     * 根据MODEM返回的字符串,解析成一个CommonSms的集合对象
     * @param str MODEM返回的字符串
     * @return
     */
    public static List<CommonSms> analyseArraySMS(String str) {
        List<CommonSms> mesList = new ArrayList<CommonSms>();
        CommonSms cs;
        String[] messages;
        String temp;
        String[] t;
        if (str.indexOf("CMGL: ") == -1)
            return null;
        str = str.substring(0, str.indexOf("OK")).trim();
        messages = str.split("\n");
        if (messages.length < 2)
            return null;
        for (int i = 1; i < messages.length; i++) {
            cs = new CommonSms();
            messages[i] = messages[i]
                    .substring(messages[i].indexOf("CMGL: ") + 6);
            t = messages[i].split(",");
            if (t.length > 5) {
                cs.setId(Integer.parseInt(t[0].trim()));
                temp = t[1].substring(t[1].indexOf('"') + 1,
                        t[1].lastIndexOf('"')).trim();
                if (temp.equals("REC READ")) {
                    cs.setState("已读");
                } else {
                    cs.setState("未读");
                }
                cs.setSender((t[2].substring(t[2].indexOf('"') + 1, t[2]
                        .lastIndexOf('"')).trim()));
                SimpleDateFormat df = new SimpleDateFormat("yy/MM/dd hh:mm:ss");
                String datestring = t[4].substring(t[4].indexOf('"') + 1) + " "
                        + t[5].substring(0, t[5].indexOf('"'));// 短信猫时间格式09/09/09
                // 20:18:01+32
                Date date = null;
                try {
                    date = df.parse(datestring);
                } catch (Exception ex) {
                    System.out.println(ex.getMessage());
                }
                cs.setDate(date);
                i++;
                cs.setSmstext(analyseStr(messages[i].trim()));
                mesList.add(cs);
            }
        }
        return mesList;
    }

    /**
     * 将PDU编码的十六进制字符串 如“4F60597DFF01” 转换成unicode "\u4F60\u597D\uFF01"
     * @param str 要转化的字符串
     * @return 转换后的十六进制字符串
     */
    public static String analyseStr(String str) {
        StringBuffer sb = new StringBuffer();
        if (!(str.length() % 4 == 0))
            return str;
        for (int i = 0; i < str.length(); i++) {
            if (i == 0 || i % 4 == 0) {
                sb.append("\\u");
            }
            sb.append(str.charAt(i));
        }
        System.out.println(Unicode2GBK(sb.toString()));
        return Unicode2GBK(sb.toString());
        //return sb.toString();
    }

    /**
     * 将unicode编码 "\u4F60\u597D\uFF01" 转换成中文 "你好!"
     * @param dataStr 要转化的字符串
     * @return 转换后的中文字符串
     */
    public static String Unicode2GBK(String dataStr) {
        int index = 0;
        StringBuffer buffer = new StringBuffer();
        while (index < dataStr.length()) {
            if (!"\\u".equals(dataStr.substring(index, index + 2))) {
                buffer.append(dataStr.charAt(index));
                index++;
                continue;
            }
            String charStr = "";
            charStr = dataStr.substring(index + 2, index + 6);
            char letter = 0;
            try{letter = (char) Integer.parseInt(charStr, 16);}catch (Exception e) {}
            buffer.append(letter);
            index += 6;
        }
        return buffer.toString();
    }

    /**
     * 将中文字符串转换成Unicode
     * @param str 要转换的中文字符串
     * @return 转换后的Unicode
     */
    public static String GBK2Unicode(String str) {

        StringBuffer result = new StringBuffer();

        for (int i = 0; i < str.length(); i++) {
            char chr1 = (char) str.charAt(i);
            if (!isNeedConvert(chr1)) {
                result.append(chr1);
                continue;
            }
            try{result.append("\\u" + Integer.toHexString((int) chr1));}catch (Exception e) {}

        }

        return result.toString();

    }
    /**
     * 在中文字符串转换成Unicode方法中判断是否需要转换
     * @param para 要转化的字符
     * @return boolean
     */
    public static boolean isNeedConvert(char para) {
        return ((para & (0x00FF)) != para);

    }

    /**
     * 使用Sms 的 SendSms()方法发送短信时,调用此方法将其短信内容转换成十六进制
     * @param msg 短信内容
     * @return 转换后的十六进制短信
     */
    public static final String encodeHex(String msg) {
        byte[] bytes = null;
        try {
            bytes = msg.getBytes("GBK");
        } catch (java.io.UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        StringBuffer buff = new StringBuffer(bytes.length * 4);
        String b = "";
        char a;
        int n = 0;
        int m = 0;
        for (int i = 0; i < bytes.length; i++) {
            try{b = Integer.toHexString(bytes[i]);}catch (Exception e) {}
            if (bytes[i] > 0) {
                buff.append("00");
                buff.append(b);
                n = n + 1;
            } else {
                a = msg.charAt((i - n) / 2 + n);
                m = a;
                try{b = Integer.toHexString(m);}catch (Exception e) {}
                buff.append(b.substring(0, 4));
                i = i + 1;
            }
        }
        return buff.toString();
    }
    //以下几个函数实现对电话号经行编码
    public static String getLength(String msg){
        int l=msg.length()/2;
        String s=Integer.toHexString(l);
        if(s.length()==1){
            s="0"+s;
        }
        return s;
    }
    public static String phonetoString(String phone){
        StringBuffer p=new StringBuffer(phone+"f");
        char t,t1;
        for(int i=0;i<p.length();i+=2){
            t=p.charAt(i);
            t1=p.charAt(i+1);
            p.setCharAt(i,t1);
            p.setCharAt(i+1,t);
        }
        return p.toString();
    }
    public static int total(String l){
        int num=Integer.parseInt(l,16);
        num=num+15;
        return num;
    }
}

最后是主类,也包含了一个测试类,其中第一个函数atCommand里存放的就是一条条AT指令,在初始化条件正确的情况下,此代码可以直接运行

package com.msm;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

import java.util.ArrayList;
import java.util.List;

import static com.msm.StringUtil.getLength;
import static com.msm.StringUtil.phonetoString;
import static com.msm.StringUtil.total;

/***
 * 短信猫操作类
 * 包括短信猫的打开、关闭、读取等操作
 */
public class Sms{
    private CommonSms commonsms;
    private static char symbol1 = 13;
    private static String strReturn = "", atCommand = "";

    public boolean SendSms(Port myport) {
        if(!myport.isIsused())
        {
            System.out.println("COM通讯端口未正常打开!");
            return false;
        }
        setMessageMode(myport,0);
        // 空格
        char symbol2 = 34;
        // ctrl~z 发送指令
        char symbol3 = 26;
        try {
            atCommand = "AT+IPR=115200" + String.valueOf(symbol1);
            strReturn = myport.sendAT(atCommand);
            System.out.println(strReturn);
            atCommand = "AT+CMGF=0" + String.valueOf(symbol1);
            strReturn = myport.sendAT(atCommand);
            System.out.println(strReturn);
            atCommand = "AT+CSCS=\"UCS2\"" + String.valueOf(symbol1);
            strReturn = myport.sendAT(atCommand);
            System.out.println(strReturn);
            atCommand = "AT+CSCA?"
                    + String.valueOf(symbol1);
            strReturn = myport.sendAT(atCommand);
            System.out.println(strReturn);
            String at = StringUtil.encodeHex(commonsms.getSmstext().trim());
            String length=getLength(at);
            String phone=phonetoString(commonsms.getRecver());
            int tol=total(length);
            if (strReturn.indexOf("OK", 0) != -1) {
                atCommand = "AT+CMGS="+tol
                        + String.valueOf(symbol1);
                strReturn = myport.sendAT(atCommand);
                System.out.println(strReturn);
                atCommand="0011000D9168"+phone+"000801"+ length + at+ String.valueOf(symbol3);
                strReturn = myport.sendAT(atCommand);
                System.out.println(strReturn);
                if (strReturn.indexOf("OK") != -1
                        && strReturn.indexOf("+CMGS") != -1) {
                    System.out.println("短信发送成功...");
                    return true;
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("短信发送失败...");
            return false;
        }
        System.out.println("短信发送失败...");
        return false;
    }
    /**
     * 设置消息模式
     * @param op
     * 0-pdu 1-text(默认1 文本方式 )
     * @return
     */
    public boolean setMessageMode(Port myport,int op) {
        try {
            String atCommand = "AT+CMGF=" + String.valueOf(op)
                    + String.valueOf(symbol1);
            String  strReturn = myport.sendAT(atCommand);
            if (strReturn.indexOf("OK", 0) != -1) {
                System.out.println("*************文本方式设置成功************");
                return true;
            }
            return false;
        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }

    /**
     * 读取所有短信
     * @return CommonSms集合
     */
    public List<CommonSms> RecvSmsList(Port myport) {
        if(!myport.isIsused())
        {
            System.out.println("System Message:  COM通讯端口未正常打开!");
            return null;
        }
        List<CommonSms> listMes = new ArrayList<CommonSms>();
        try {
            atCommand = "AT+CMGL=\"ALL\"";
            strReturn = myport.sendAT(atCommand);
            listMes = StringUtil.analyseArraySMS(strReturn);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return listMes;
    }


    /**
     * 删除短信
     * @param index 短信存储的位置
     * @return boolean
     */

    public boolean DeleteSMS(int index,Port myport) {
        if(!myport.isIsused()){
            System.out.println("System Message:  COM通讯端口未正常打开!");
            return false;
        }
        try {
            atCommand = "AT+CMGD=" + index;
            strReturn = myport.sendAT(atCommand);
            if (strReturn.indexOf("OK") != -1) {
                System.out.println("System Message:  成功删除存储位置为" + index
                        + "的短信......");
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return true;
    }

    /**
     * 删除短信猫中所有短信
     * @return boolean
     */
    public boolean DeleteAllSMS(Port myport)
    {
        List list=RecvSmsList(myport);
        boolean ret=true;
        if(list!=null&&!list.equals("")&&list.size()>0)
        {
            for(int i=0;i<list.size();i++)
            {
                CommonSms tempcomsms=(CommonSms)list.get(i);
                if(!DeleteSMS(tempcomsms.getId(),myport))
                {
                    ret=false;
                }
            }
        }
        return ret;
    }
    public CommonSms getCommonsms() {
        return commonsms;
    }

    public void setCommonsms(CommonSms commonsms) {
        this.commonsms = commonsms;
    }
    /**
     * 号码,内容,发送短信息
     * @param phone
     * @param countstring
     * @throws Exception
     */
    public static void sendmsn(String phone,String countstring){
        Sms s = new Sms();
        // 发送测试
        CommonSms cs=new CommonSms();
        cs.setRecver(phone);
        cs.setSmstext(countstring);
        s.setCommonsms(cs);
        Port myort=new Port("COM3");
        System.out.println(myort.isIsused()+"     "+myort.getCOMname());
        s.SendSms(myort);
        // s.RecvSmsList(myort);
        //s.DeleteAllSMS(myort);
        myort.close();
    }

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 1; i++) {
            sendmsn("18324601102","啦啦啦");
        }
    }
}

总体上来说,串口通信这部分还是比较复杂,尤其是pdu方式的消息模式,自己也搜索了许多资料,最后也是在老师的帮助下完成的。有问题请留言。

Comments
Write a Comment