分享

X.Y.E.A.H .net平台手机管理软件开发

 bluechina 2008-02-29

.net平台手机管理软件开发

(一) 简介

通过几个月零零碎碎地学习各方面知识之后在这个大二的寒假笔者终于用VB.Net写出了西门子手机的辅助软件——Siemens Support Tool。虽然我没有最终的完成这个软件的所有设计,但核心的功能已经开发完成,界面也基本到位,我的学习的目的也就达到了。在2月12日正式停止开发的以后,我想到把寒假20几天的辛苦历程作一个总结,为以后重温这部分知识起到一定的作用,也为广大编程爱好者提供一些帮助和启发。笔者才疏学浅,编程也是零碎时间自学过来的,所以有缺陷和谬误请大家斧正。

(二) 为何要设计?设计目的?

用过西门子手机的都知道西门子手机在人性化设计方面做得比较好,但是让笔者失望的是电脑端上使用的官方软件SDS,其缺点是操作不方便,速度比较慢,感觉人性化设计不到位。后来官方推出了用于65系列的Mobile Phone Manager,界面很好看,但安装后大于120MB的容量及其较慢的速度又让人大跌眼镜。

GhostMobile(简称GM)是我用过的国产非官方软件里面比较好的一款,但经常出现传输中一直等待的情况。估计作者并没有超时设计。另外GM文件传输速度很慢,短信管理不方便,后来也由于作者使用了新的手机,也就放弃了GM。后来又找到一款Siemens Mobile Control,简称SiMoCo,是国外的非官方软件。800多kb的身躯及其速度、功能方面都超过了官方及GM,令人刮目相看,一度成为我最喜欢的软件。但用后发现一些问题,软件过于专业,选项太多,对中文的支持不好。

所以最终的目的就是做一款能够实现文件传输、短信、便签、任务、重要记事管理的软件。

(三) 准备工作

2004的暑假我已经做了一部分,实现了基本的文件传输和短信功能,当时取名叫作M55 File Transfer Tool。后来在东北手机网上公开了,有一些GM无法连接的手机我的软件都可以连接,所以到现在为止有些网友还在使用我的这个软件。但由于知识不够,做得不是很理想,经常出现问题。

短信部分是官方网站下载的ATC_Command_Set_For_L55_Platform,详细地讲述了55平台上的AT指令集。其实SMS部分的AT指令各大手机厂商都是通用的,已基本上属于同一的指令集了。但是发现文件传输是OBEX却不是那么简单。

官方并没有任何开发文档说明数据传输使用的是什么协议,我用Serial Monitor监视到了数据传输的过程,全是HEX代码,不知道是什么意思。那段时间一度陷入迷茫状态,不知道如何下手。后来在google搜索,又在CSDN里发了一些帖子求救,但却没有一个能够明确说明的。有一个网友的留言给了我一些线索,他说可能是蓝牙协议里面的部分。

这条线索给我了极大的鼓舞,因为后来,顺藤摸瓜找到了红外线传输协议,意外地发现了IrOBEX的描述协议竟然和监视到的HEX代码的结构一样。随后经过仔细的研究发现就是OBEX协议,此协议可以作为上层协议用在红外线协议、蓝牙协议等。此过程大约经历了2个多月。其实现在看来这个问题简单了,手机的工厂模式的串口监视里面就会显示当前使用的协议。当数据传输开始时,会自动从GIPSY变为OBEX。但那个时候哪知道呢?

跨越了OBEX协议的障碍以后我写了一个OBEX-Multithread类,写得很垃圾,把十六进制转换成字符串,然后再转回来在发送。中间使用了string作字符串操作,速度很慢,测试以后只能勉强超过GM的传输速度。

后来借着Serial Monitor监视GM读取手机通讯薄的原理,发现通讯薄是在\telecom\pb目录里面,但是这个目录在手机里面是隐藏的,无法直接访问。由于原来写得OBEX库很糟糕,只能对应文件传输,对于这个特殊文件夹里面的文件都无法操作。修改了之后效果不好,遂放弃了OBEX-Multithread。

由于学习的原因,中途也只得停下来准备期末考试和六级。中途无聊的时候研究IrMC里面的vCard、vNote、vCalc格式,基本弄懂了如何同步通讯薄、便签、日历。2005年1月14日,放假回家了就正式开始动工,把所有的东西都重新写,对我来说,这是一个巨大的挑战。

(四) AT指令简介

AT指令在当代手机通讯中起着重要的作用,能够通过AT指令控制手机的许多行为,包括拨叫号码、按键控制、传真、GPRS等。西门子M55手机为我提供了很多的AT指令,网络上关于AT指令的资料也很多,我这里提取一些比较重要的做个简单解释。其他的手机也基本上通用,更详细的资料请查阅手机生产商的资料。

欲使用AT命令,可以安装微软的超级终端程序,选择好端口连接速度以后就可以正常使用了。

AT指令用法

1、 测试命令(Test Command)
在AT指令后面加上“=?”即构成测试命令。
例如“AT+CSCS=?”会列举出所有支持的字符集。

2、 读取命令(Read Command)
在AT指令后面加上“?”即构成读取命令。
例如“AT+CSCS?”会列举出当前设置。

3、 执行命令(Execute Command)
一般而言在AT指令后加上“=”及命令参数即可。有些命令例如AT+CMGR命令没有参数,直接就可以执行。

注:并不是所有的AT指令都支持1和2。

常用基本AT指令

命令 作用
AT 测试连接是否正确
ATE0 关闭回显。程序初始化AT部分首先关闭回显。
ATE1 打开回显。使用超级终端测试命令时打开。
AT+CGMI 得到厂商信息
AT+CGMR 得到手机版本号
AT+CGSN 得到手机序列号(IMEI)
AT+CIMI 得到手机IMSI号码
AT+CSCS 获取、设置手机当前字符集。可设置为GSM或UCS2
AT+CBC 获取手机电量
AT+CCLK 获取设置手机时钟
AT+CNUM 机身号码。分为线路一和线路二
AT+CSQ 当前信号
AT+COPS 网络营运商
AT+CSCA 短信中心号码

以上这些指令都用于与手机连接的时候初始化用。取得手机IMEI及IMSI可以给使程序支持更多的手机连接并且保持数据独立。

短信部分

命令 作用
AT+CPMS 选择短信储存地点。可选择ME(SIM卡)和MT(机身)
AT+CMGL 列出指定状态的短信息的PDU代码
AT+CMGR 列出指定序号的短信息PDU代码
AT+CMGS 发送短信
AT+CMGD 删除指定的短信
AT+CMGF 短信格式。分为Text模式和PDU模式
AT+CNMI 设置新短消息通知电脑端
AT+CSCA 短信中心

以上命令是短消息部分最经常使用的命令。具体条目及使用方法会在后面讲解。

(五)  OBEX介绍

一、什么是OBEX,它有什么用途?

OBEX全称为Object Exchange,中文对象交换,所以称之为对象交换协议。它在此软件当中有着核心地位,文件传输和IrMC同步都会使用到它。

OBEX协议构建在IrDA架构的上层.

OBEX协议通过简单的使用“PUT”和“GET”命令实现在不同的设备、不同的平台之间方便、高效的交换信息。支持的设备广泛,例如PC,PDA,电话,摄像头,自动答录机,计算器,数据采集器,手表等等。

OBEX协议定义了一种柔性的概念——objects。也即是对象。这些对象可以包括文件,诊断信息,电子商务卡片,银行的存款等等。Objects在这里没有高级的技术含义,而是视你的应用而定。

OBEX协议小到可作“命令和控制”功能,例如对电视机,录像机等的操作。大道可以做很复杂的操作,例如数据库的事务处理和同步。

OBEX能够具有以下几个特点:

1、  友好的应用——可实现快速开发。

2、  紧缩——可用在资源有限的小型设备上。

3、  跨平台

4、  柔性的数据支持。

5、  方便的作为其他Internet传输协议的上层协议。

6、  可扩展性——提供了对未来需求的扩充支持而不影响以存在的实现。例如可扩展安全,数据压缩等。

7、  可测试可调试。

更为具体的关于OBEX的介绍请查阅IrOBEX协议。

二、OBEX对象模型

关于Headers

对象模型回答了对象是如何在OBEX协议描述的。这个模型必须包括被传输的对象和对对象的描述。为了做到这点,OBEX定义了Headers的概念。

一个Header反映了对象的一个方面,例如名字、长度、描述文字或者对象本身。例如,一个文件对象demo.txt会包含它的名字,一个类型标示为“text”,长度和文件本身。

Headers的构成

Headers简单的由和组成,简称为和。

HI由一个字节组成,指出了Header包含的内容以及它的格式。HV包含了一个或者多个字节,其结构由HI所决定。

所有的Header都是可选的,取决于设备的类型和事务的种类。你可以使用所有的Header,或者一些,或者没有。ID可以使Header可解析以及与传输顺序无关,也可以使不支持的Header被忽略掉。

HI又可以分为两部分,高2位和低6位。高2位确定了HI的编码方式(见表二),低6位确定了HI的意义(见表三)。两个表都是我从IrOBEX中的表摘抄并部分翻译过来的。

表二

HI的第8和第7位

意义

00(0×00)

以Null(0×00)结尾的的Unicode文字。2个字节的无符号整数长度前缀。

01(0×40)

Byte块,2个字节的无符号整数前缀。

10(0×80)

1Byte容量。

11(0xC0)

4Byte容量,以高位先传输为原则。

表三

HI

Header名称

描述

0xC0

Count

连接中用于指名对象的数量。

0×01

Name

对象的名字。一般为文件名。

0×42

Type

对象的类型。例如text,html,binary,manufacture specific

0×44

0xC4

Time

时间戳。ISO 8601版本
时间戳。4Byte版本(用于兼容)

0×05

Description

对对象的文本描述

0×46

Target

操作的目的服务名

0×47

HTTP

一个HTTP1.x头

0×48

Body

对象的一部分

0×49

End of body

对象的最后一部分

0×4A

Who

OBEX Application标识,用于表明是否是同一个应用。

0xCB

Connection ID

用于OBEX多路连接的标识

0×4C

App.Parameters

扩展的应用层请求和回复信息

0×4D

Auth.Challenge

Authentication digest-challenge

0×4E

Auth.Response

Authentication digest-response

0×4F

Object Class

对象的OBEX对象类

0×10 to 0×2F

Reserved

保留

0×30 to 0×3F

User defined

用户自定义的。

 

 

关于常用Header的更详尽的解释,更详尽信息请参考IrOBEX

1、Name
              Name是一个用来描述对象名称的Header,由以Null(0×00)结尾的Unicode字符串组成。例如:DEMO.TXT

2、Length
Length描述了对象的大小,由4个字节组成。如果Length事先知道,这个Header应该被用到。这样可以让接受者迅速的知道需要分配多少空间,可使处理更为迅速。但这也不是必须的,有些情况下长度无法确认,但设备可以通过End-Body Header知道什么时候结束。

3、Time

Time描述了对象的最后修改的时间。使用ISO8601格式。

本地时间格式:YYYYMMDDTHHMMSS

UTC时间格式:YYYYMMDDTHHMMSSZ

格式中的T可以方便的区分日期和时间。UTC时间使用Z作为标记。建议使用UTC时间。
4、Body、End-of-Body

       Body Header由HI、一个2Byte长度的描述和整个的对象本身。End-of-Body组成和Body组成一样,但标识了这是对象的最后一部分。如果对象本来就很小,就直接使用End-of-Body。

 

三、请求(Request)和回应(Response

OBEX使用Request和Response作为最基本的操作。请求的每个Request必然有一个Response,否则可认为Request失败。

Request由一个或多个的Packet(包)组成,每个包的结构如下表

Request数据包结构

Byte 0

Byte 1,2

Byte 3 to n

操作码(opcode)

Packet Length(包长度)

Headers或请求信息

 

由于每个Request可能有多个Packet,opcode的最高位称为Final bit。如果被设置为1,那么说明这是Request的最后一个Packet。例如:当用PUT操作发送一个大文件时,会有几个Packet作为一个Request。那么只有最后一个Packet的FinalBit设置为1。

 

Response也由一个或多个Packet组成,每个包的结构如下表

Response数据包结构

Byte 0

Byte 1,2

Byte 3 to n

Response Code(返回值)

Response Length(回应长度)

ResponseData回应的数据

 

同样的ResponseCode的最高位也叫做FinalBit。ResponseData可能包含对象和Header,或者其它信息。

下表列出了了常见的opcode和responseCode,更详尽的请参考IrOBEX 1.2文档。

opcode

Opcode(w/high bit set)

定义

意义

0×80 *

Connect

连接

0×81 *

Disconnect

断开连接

0×02(0×82)

Put

发送一个对象

0×03(0×83)

Get

取得一个对象

0×04(0×84)

Reserved

保留的

0×85 *

SetPath

设置路径

0xFF *

Abort

取消当前的操作

0×06到0×0F

Reserved

作为扩展保留

0×10到0×1F

User definable

用户自定义的

*总是设置FinalBit

 

ResponseCode

ResponseCode

定义

0×10(0×90)

Continue(继续)

0×20(0xA0)

OK,Success

0×40(0xC0)

Bad Request(服务端不明白Request)

0×41(0xC1)

Unauthorized(未授权的)

0×43(0xC3)

Fobidden(禁止——服务器明白Request,但拒绝)

0×44(0xC4)

Not Found(未找到)

 

四、说明。

1、 Connect(连接)

此操作初始化会话然后设置参数。其Request格式为

Byte 0

Byte 1,2

Byte 3

Byte 4

Byte 5,6

Byte 7 to n

0×80

包长度

OBEX版本

标志

最大OBEX包长度

可选Header

注:OBEX版本现在为1.0。

Response格式为:

Byte 0

Byte 1,2

Byte 3

Byte 4

Byte 5,6

Byte 7 to n

ResponseCode

包长度

OBEX版本

标志

最大OBEX包长度

可选Header

对于更多关于Connect的说明请参考IrOBEX

2、 Disconnect(断开当前会话)

此操作断开OBEX会话。例如断开当前FolderListing Service,然后使用Connect连接到IrMC Sync Service实现同步通讯薄等功能。

Disconnect格式为:

Byte 0

Bytes 1,2

Bytes 3 to n

0×81

包长度

可选Header

成功的断开会返回0Xa0,拒绝会返回0xD3

3、 Put操作

Put操作从客户端发送一个对象到服务端。一般至少含有Name和Length两个Header。对于文件而言有可能还有Date/Time Header。

Put操作的格式如下:

Byte 0

Bytes 1,2

Bytes 3 to n

0×02(当FinalBit设置时为0×82)

PacketLength包长度

一组Header

回应格式如下:

Byte 0

Bytes 1,2

Bytes 3 to n

ResponseCode(要求继续为0×90;成功为0xA0)

包长度

可选Header

关于Put操作的更详细的用法将在文件传输部分作解释。

4、 Get操作

Get操作从服务端返回一个对象。

Get操作的格式如下:

Byte 0

Bytes 1,2

Bytes 3 to n

0×03

包长度

一组Header

回应格式如下:

Byte 0

Bytes 1,2

Bytes 3 to n

Response Code

包长度

可选Header

关于Get操作更详细的用法将在文件传输部分作解释。

5、 Abort操作

Abort操作中断一个多包操作(例如发送一个大文件)。Abort操作可以包含描述中断原因的Description Header。

Abort操作的格式如下:

Byte 0

Bytes 1,2

Bytes 3 to n

0xFF

包长度

可选Header

Abort对应的应该是一个成功的操作(0xA0),指明这个操作已被接收并且服务端已经重新与客户端同步。如果返回的另外的值,客户端应该Disconnect。

6、 SetPath

SetPath操作用于切换对方的路径。通常使用一个Name Header用于指定对方路径名称。如果为空,则返回默认目录,通常为根目录

SetPath操作格式如下:

(六)  OBEX应用——文件传输部分

在手机数据传输方面基本OBEX应用分为

l         文件传输

l         IrMC同步

文件传输又可以细分为以下基本操作

l         初始化连接

l         断开连接

l         设置路径

l         取得目录信息

l         创建目录

l         上传下载文件

l         删除文件或空目录

在笔者的软件当中设计了OBEX这个类,里面包含了以上所有的基本操作。另外针对M55的服务端的特殊性又设计了更名、取得磁盘空间信息、移动、拷贝文件的功能。具体请参考源代码。

下面具体讲述各个操作的细节。

l         初始化连接

初始化连接包括了使手机进入OBEX状态再到发送Connect指令的一系列过程。具体流程参考下图。

ATÆAT^SQWE=0ÆAT^SQWE=3ÆConnectÆ连接到Folder-Listing Service

其中AT^SQWE=0和AT^SQWE=3是西门子特有的隐藏的AT指令,甚至在官方的AT指令集里面都没有提到。其作用是初始化手机到OBEX模式。

发送Connect指令收到手机回复以后确定Max Packet Length等参数。最后连接到Folder-Listing Service进行文件操作。如果需要IrMC同步的话,在Connect后直接连接到IrMC Sync Service即可,手机立刻进入同步模式,所有的应用程序退出。

在笔者的程序中当中首先使用AT指令确定手机当前的工作,如果超时,尝试发送+++并等待1秒钟以便手机从不正常的OBEX状态中退出。然后在此发送AT,成功后即进行文件操作,否则引发一个错误。

l         断开连接

这里的断开连接是指从OBEX模式退出到AT状态中。在AT指令中,连续发送三个0×2B然后等待一秒钟以上即可退出数据模式进入常规AT模式。

l         设置路径

程序中使用SetPath操作设置路径。需要注意的是,可以设计两种风格的过程:一种使用绝对路径,另一种使用相对路径。

对于手机而言,经笔者实践证明,使用绝对路径要比使用相对路径方便,而且更准确,但效率上稍逊,特别是在多层目录的情况下。由于手机没有能够返回当前路径的方法,所以相对路径总难以控制,只有通过程序控制,极容易出现错误。所以建议使用绝对路径。

如果使用绝对路径,那么路径名将呈现\path1\path2这种形式。首先回到根目录,然后一级一级的到达目的。在笔者的OBEX类当中,可以看到BacktoRoot这个过程。其作用就是把当前程序路径切换到根目录以免引起混乱。

l         取得目录信息

要取得一个目录的文件信息可以用发送Get指令。该Get指令需要一个TypeHeader,其值为x-obex/folder-listing

随后服务端会返回一个xml文件,包含了整个目录的信息。例如子目录名、文件名、大小、最后修改时间。再通过xml解析后就会得到目录信息。

l         创建目录

当SetPath的Flags的Bit1设为1并且无法找到目录时,服务端会创建这个目录并进入。

请注意,再次使用SetPath并将Flags的Bit0设为1返回上层目录,否则容易引起混乱。

l         上传下载文件

使用Put和Get命令可以实现上传和下载文件。注意所有的文件操作都在SetPath所指定的目录下进行。

使用Put命令实现上传时,至少需要提供NameHeader,BodyHeader,一般还要提供LengthHeader,DateTimeHeader。大文件传输需要正确处理BodyHeader,不能超出了服务端最大所能接收的Packet的大小,否则会出现错误。

需要注意的是如果当前文件存在,那么Put命令不会覆盖已有的文件而是追加,导致错误。传输文件之前需要首先删除同名文件。

使用Get命令实现下载时,只需要提供NameHeader即可。具体例子参考上一节。

l         删除文件或空目录

使用Put命令,其NameHeader指定为文件名或目录名并将BodyHeader设置为空即可。

非空目录无法删除,会返回错误。

 

(七)  IrMC简介

要实现通讯薄、日历、便签的同步,需要用到IrDA协议里面的IrMC部分。

IrMC全称为Ir Mobile Communications。它定义了利用IR无线传输的设备之间通讯的规则。IrMC协议文档详细叙述了IrMC的方方面面,可以在我的主页(http://dream-world.)下载到PDF版本。

下面主要讲述我在开发当中所用到的部分以及实现方法。更为详细的资料请参考IrMC协议。

Phonebook

在手机软件桌面端通讯薄的管理是整个软件的必备功能之一,利用其信息可以方便的和Outlook等软件实现同步,实现更强大的功能。

通讯薄的管理分为读取、删除、增添、修改。通过这几个功能的组合可以实现更为强大的同步功能。下面分条概述。

l         读取

n         读取所有的Entry
使用OBEX的GET命令取得\telecom\pb.vcf文件。
得到的的文件是一个vCard文件,里面包含了所有的通讯薄。其格式为vCard格式。关于vCard的简介及编码器解码器见后。下面给出一个范例:
BEGIN:VCARD
VERSION:2.1
X-IRMC-LUID:1017646
X-ESI-CATEGORIES;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E5=AE=B6=E4=BA=BA
N:test
ADR:;;Street;city;;610000;country
ORG:company
TEL;HOME:123456
TEL;WORK:123456
TEL;CELL:123456
TEL;FAX:123456
TEL;FAX;HOME:123456
EMAIL;INTERNET:a@a.ao
EMAIL;HOME;INTERNET:b@g
URL:http
BDAY:1985-04-23
END:VCARD
也可以通过X-IRMC-LUID所给的LUID号码直接取得相应的vCard。

1.         读取指定LUID的Entry
使用OBEX的GET命令取得\telecom\pb\luid\xxxx.vcf,其中xxxx代表了LUID号码。
得到的依然是一个vCard文件,只不过只包含特定LUID号码的vCard信息。可以简单的通过Outlook查看vCard所包含的信息。

l         删除
欲删除一个Entry

1.         连接到IrMC_Sync_Service(发送0×80, 0×0, 0×13, 0×10, 0×0, 0×40, 0×6, 0×46, 0×0, 0xC, 0×49, 0×52, 0×4D, 0×43, 0×2D, 0×53, 0×59, 0×4E, 0×43)使手机处于同步状态。

2.         得到ChangeCount

ChangeCount是手机里面储存修改次数的一个数值,主要用于同步。具体相关资料请参考IrMC协议。

3.         构建数据包。
Opcode:PUT
NameHeader:xxxx.vcf(xxxx为欲删除的entry的LUID)
AppParam:0×11,ChangCount字符串形式长度(Integer),ChangeCount的ANSI形式

4.         使用OBEX的PUT命令,传输一个xxxx.vcf(xxxx指LUID)空文件到\telecom\pb\luid覆盖即可。

5.         发送0×81, 0×0, 0×3断开IrMC_Sync_service

l         添加
欲添加一个文件:

1.         连接到IrMC_Sync_Service

2.         得到ChangeCount

3.         构建数据包
Opcode:PUT
NameHeader:.vcf
AppHeader:0×11,ChangeCount字符串长度(Interger),ChangCount的ANSI形式
Body或者End-of-Body Header:vcf文件内容

4.         OBEX的PUT命令上传到\telecom\pb\.vcf文件即可。
注意:文件名取名为“.vcf”。

5.         发送0×81, 0×0, 0×3断开IrMC_Sync_service

l         修改
修改过程与添加过程相似,只是将文件名改为欲修改的vCard的LUID.vcf就行了。

 

Notes

       便签是大多数手机都提供的功能,能够方便的记录简短的信息。在我的M55手机上能够储存150Byte的信息,也就是150个英文或者75个汉字。软件通过管理便签可以与Outlook等软件同步,实现更高级的功能。

       管理Notes的方法和Phonebook类似。得到全部Notes的vNote只需要获取\telecom\nt.vnt即可。删除、添加、修改只需要把\telecom\pb\luid改为\telecom\nt\luid即可。在此不再赘述。

 

       Calendar
日历功能提供了事件提醒功能,分为重要记事(vEvent)和任务(vTodo)。通过管理日历,同样可以实现和Outlook同步,实现电脑和手机的同步。

       管理Calendar的方法和Phonebook类似。得到全部vCalendar只需要获取\telecom\cal.vcs。删除、添加、修改只需要把\telecom\pb\luid改为\telecom\cal\luid即可。在此不再赘述。

       但值得注意的是vCalendar的结构

BEGIN:VCALENDAR

VERSION:1.0

BEGIN:VEVENT

….

END:VEVENT

BEGIN:VTODO

END:VTODO

END:VCALENDAR

完整的vCalender包含了至少一个vEvent或者一个vTodo,因此在添加、修改vEvent、vTodo时要将其补充为一个完整的vCalender结构,否则服务端会拒绝操作。

 

遇到的问题:

在实际操作中,遇到问题最多的地方在Phonebook部分。我的手机第一次同步的时候经常出现数据库被锁的情况,用SiMoCo读取也是一样,说明是手机拒绝写操作。这时候关闭手机再重新启动就好了。至于原因,我还没有搞清楚,希望能有高人指点

(八)  vCard、vNote、vCalender格式简介

vCard称为电子商务卡片,主要用于记录通讯薄的联系人信息等,方面不同设备之间的数据交换。自笔者的M55手机中,可以发送一条短信到对方,其中包含了vCard格式的联系人信息,西门子其他型号的手机可以接收解码存储。另外通过手机红外线传输到电脑上的联系人也是用的vCard格式。如果安装了Outlook,则可以直接打开vCard并看到其包含的信息。下面主要简要介绍一下vCard格式,其他vNote、vCalender格式和vCard相近,就不再赘述。更详细的资料请参考vCard Specification,在笔者主页有相关下载。

关于vCard、vNote、vCalender的.Net简单编码解码器请参阅SIEMENS SUPPORT TOOL源代码中的IrMC部分。

vCard Object(vCard对象)

一个vCard数据流可以包含一个或者多个vCard Object。在数据流中一个vCard Object定义为以“BEGIN:VCARD”开始并以“END:VCARD”结束的数据。如果只有到达了数据流尾都没有出现“END:VCARD”,则整个vCard Object包含从“BEGIN:VCARD”开始到数据流结束的地方。

       vCard Property(vCard属性)

       vCard是一个或多个Property的集合。一个Property是唯一命名的值。一系列的Property可以在vCard中成为一组。

vCard Property的格式如下:

PropertyName[‘;’ PropertyParameters]’:’PropertyValue

1、  PropertyName及PropertyParameters不区分大小写。

2、  PropertyParameters可选,可以为零个或多个,与ProperyName以分号相隔,与PropertyValue以冒号相隔。

3、  vCard可以分多行呈现。由于在这个软件里面应用得不多,所以笔者也没有钻研具体实现方法。可以参考vCard Specification。

例如TEL;HOME;+86111222333其PropertyName为TEL,PropertyParameters为HOME,PropertyValue为+86111222333。

Encoding(编码)

vCard默认的编码方式是7-Bit。默认编码方式可以使用ENCODING属性参数(Property parameter)改变。其值为可以为BASE64;QUOTED-PRINTABLE;8BIT。这个参数可以用在任何的Property里。

例如:

X-ESI-CATEGORIES;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E5=AE=B6=E4=BA=BA

下面简要说明QUOTED-PRINTABLE编码方式,更为详细的资料请参考相关文档:

ASCII可显示字符基本保持不变。Unicode字符或者UTF8编码字符使用等号加其对应16进制代码表示。例如上述CHARSET为UTF8的字符=E5=AE=B6=E4=BA=BA对应的UTF8编码0xE5,0xAE,0xB6代表中文“家”,其他的代表“人”。另外如果其中有可显示ASCII码,保持原样输出。

例如ENCODING=QUOTED-PRINTABLE:Home=E5=AE=B6People=E4=BA=BA

解码后为“Home家People人”。

Character Set(字符集)

默认的字符集是ASCII。可以通过CHARSET参数改变默认的字符集。其参数可取的值为所有IANA(Internet Assigned Numbers Authority)注册的字符集。这个参数可以用于任何Property,但某些Property并不起作用。

例如:

X-ESI-CATEGORIES;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E5=AE=B6=E4=BA=BA

vCard例子:

BEGIN:VCARD

VERSION:2.1

X-IRMC-LUID:1017646

X-ESI-CATEGORIES;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E5=AE=B6=E4=BA=BA

N:test

ADR:;;Street;city;;610000;country

ORG:company

TEL;HOME:123456

TEL;WORK:123456

TEL;CELL:123456

TEL;FAX:123456

TEL;FAX;HOME:123456

EMAIL;INTERNET:a@a.ao

EMAIL;HOME;INTERNET:b@g

URL:http

BDAY:1985-04-23

END:VCARD

(九)  短信部分——PDU简介及其格式

PDU是大多数手机短信通讯的核心,仅有少数手机只支持Text模式(例如笔者的MOTO C330)。PDU模式比起Text模式可以提供能为强大的功能,但其编码较Text模式困难。无论哪种模式,我们都可以通过AT指令控制终端实现短信的发送、接收、删除等管理。下面主要介绍PDU的构成及编码解码。

PDU的构成

PDU是由一串由“0-9”及“A-F”组成。表面上看起来就是一组16进制的数所组成的。

下面举一个发送和接收的例子。

1、  手机发送的一个PDU串:

0891683108200805F011190D91683188902848F40008FF108FD9662F4E0067616D4B8BD577ED4FE

对比3GPP协议得到:(二进制代码从左到右依次为高位->低位)

短信中心地址字段

08    地址长度:8个字节,包括其后的91

91    地址类型:10010001
Bit7:1。始终为1
Bits 6,5,4:Type-of-Number(号码类型):001,代表Internation Number。也即是号码前加“+”。注意:对某些比较特殊的号码,例如手机与小灵通的互通时,这里不能设置为001,而要设置成000,代表号码前没有“+”,否则无法接收。

       Bits 3,2,1:Numbering-plan-identification:一般默认为0001,表示电话号码类型的。

683108200805F0   短信中心号码:一个字节内反转,8613800280500,如果长度为奇数则需要加“F”补齐

       FirstOctet字段

11       包含TP-MTI(2bit),TP-RD(1bit),TP-VPF(2bit),TP-RP(1bit),TP-UDHI(1bit),TP-SRR(1bit)

二进制表示形式:0 0 0 10 0 01

TP-MTI:01

       TP-Message-Type-Indicator(消息类型指示符)

Bit1,0:01      指示为SMS-SUBMIT类型
TP-RD:0

TP-Reject-Duplicates(是否拒绝相同重复消息)

Bit2:0          指示短消息中心接受未转发的具有相同TP-MR的消息。

TP-VPF:10

       TP-Validity-Period-Format(有效期格式)

       Bit4,3:10      指示使用相对格式。

TP-SRR:0

       TP-Status-Report-Request

       Bit5:0          指示不使用状态报告。

TP-UDHI:0
       TP-User-Data-Header-Indicator(用户数据头标示)
Bit6:0          指示这是一个SMS消息,没有用户数据头。EMS消息需要设置。

TP-RP:0
       TP-Reply-Path(回复路径)
Bit7:0          指示没有设置回复路径。

消息参考值TP-MR

19    TP-Message-Reference

       对方号码字段

              0D91683188902848F4

              其结构与短信中心号码字段部分类似,不再赘述。

       协议标识TP-PID

              00    TP-Protocol-Identifier(上层协议指示),一般设置为00,表示普通GSM,点对点
       编码方法TP-DCS

              08    TP-Data-Coding-Scheme(数据编码设置),指示TP-UD的编码方式。08代表Unicode方式。00为7Bit编码

       有效期TP-VP

FF   TP-Validity-Period(有效期)。FF表示最大。

       用户数据长度TP-UDL

10    TP-User-Data-Length(用户数据长度)

                     0×10长度。注意不同编码下用户长度定义不同。

       用户数据TP-UD

8FD9662F4E0067616D4B8BD577ED4FE    TP-User-Data
中文“这是一条测试短信”的Unicode编码

 

2、  手机接收的PDU串

0891683108200805F0040D91683188902848F4000850208151754500108FD9662F4E0067616D4B8BD577ED4FE1

短信中心地址字段

        0891683108200805F0:+861380280500

FirstOctet

       04

               其二进制代码:00000100

               TP-MTI:00

               TP-MMS(TP-More-Message-to-Send):1 短信中心没有更多的消息发送

               TP-SRI:0

               TP-UDHI:0

               TP-RP:0

发送方号码

        0D91683188902848F4:+8613880982844

协议标识
              00    TP-DCS  点对点

编码方式

        08    TP-DCS  Unicode编码

短信中心时间戳
50208151754500 TP-SCTS   字节反转05/02/18 15:57:45  最后的00代表时区,这里为0

用户数据长度

        10    TP-DHL

用户数据

        8FD9662F4E0067616D4B8BD577ED4FE1         TP-UD

       中文“这是一条测试短信”的Unicode编码

(十)  短信部分——VB.NET解码PDU

早在2004年1月份我就开始初步的研究PDU的编码解码原理,对于PDU也有比较深刻的认识。随后按照3GPP协议写了一个PDU Decoder,后来写成PDU Decoder文章发表在CodeProject上面,有几个好心的外国网友给我指出了一些BUG,现在成了一个比较完善的Decoder。具体的源码在http://blog.csdn.net/hesicong/archive/2004/09/24/115356.aspx 。本文讲解编码器的构成以及我所使用的解码方法及技巧。

解码器的构成

NameSpace SMS

                      Decoder

MustInheritClass     SMSBase

Class                             EMS_RECEIVED

Class                             EMS_SUBMIT

Class                             SMS_RECEIVED

Class                             SMS_STATUS_REPORT

Class                             SMS_SUBMIT

Class                             PDUDecoder

SMSBase部分

SMSBase类是必须继承类,它包含了PDU的基本结构以及一些相关辅助函数,是最基本的类,其他的类都是从SMSBase继承的。通过SMSBase的Shared函数GetSMSType可以得到PDU的类型,从而确定使用的Class。

SMSBase包含了所有短信类型所共有的基本信息部分以及一个指示短信类型的枚举SMSType,继承的类扩展其特有的基本信息部分。

            Public SCAddressLength As Byte  ‘Service Center Address length

            Public SCAddressType As Byte    ‘Service Center Type[See GSM 03.40]

            Public SCAddressValue As String ‘Service Center nuber

            Public FirstOctet As Byte       ‘See GSM 03.40

 

            Public TP_PID As Byte

            Public TP_DCS As Byte

            Public TP_UDL As Byte

            Public TP_UD As String

            Public Text As String

            Public Type As SMSType

            Public UserData As String

 

            Public Enum SMSType

                SMS_RECEIVED = 0

                SMS_STATUS_REPORT = 2

                SMS_SUBMIT = 1

                EMS_RECEIVED = 64 ‘It is “Reserved” on my phone??

                EMS_SUBMIT = 65

   End Enum

 

SMSBase中定义了一个必须重写的过程GetOrignalData,其参数为PDUCode,目的是为了得到PDU的基本信息。不同的短信类型具有不同的解码过程,所以作为一个必须重写的函数。

Public MustOverride Sub GetOrignalData(ByVal PDUCode As String)

 

SMSBase中还有一系列的辅助函数,具体实现方法见源代码:

处理PDU代码的:

处理PDU代码我运用了自称为“按需裁减”的技巧,就是把需要的数据提取出来解码,然后从原PDUCode中删除这一部分,在传递给下一个函数处理。这样就不用考略具体的偏移量,简化了操作,增强了适应性。为了能够减少返回处理过的PDUCode麻烦,我使用了ByRef,执行过程以后PDUCode就自动被裁减了。

‘Get a byte from PDU string

Shared Function GetByte(ByRef PDUCode As String) As Byte

‘Get a string of certain length

Shared Function GetString(ByRef PDUCode As String, ByVal Length As Integer) As String

‘Get date from SCTS format

Shared Function GetDate(ByRef SCTS As String) As Date

         ‘Swap two bit

         Shared Function Swap(ByRef TwoBitStr As String) As String

         ‘Get phone address

         Shared Function GetAddress(ByRef Address As String) As String

         Shared Function GetSMSType(ByVal PDUCode As String) As SMSBase.SMSType

TP-UD解码部分:

       TP-UD的解码的任务主要集中在Unicode的解码和7BitCode的解码。其中Unicode的解码很方便,只需要将两个字节的PDUCode通过Val函数转换成为数字,在通过ChrW函数即可得到。

       而7BitCode就显得比较难,下面以Test四个字符简单介绍其基本原理,具体的编码方式请参考相关资料。

              Byte1      11010100       0xD4

              Byte2      11110010       0xF2

              Byte3      10011100       0×9C

              Byte4      00001110       0×0E

              注:各字符二进制代码:

              T:1010100   e:1100101    s:1110011    t:1110100

       从这个例子可以看出一个Byte包含了一个字符的ASCII码的二进制部分及后续字符的二进制部分的低位。这样8个字符可以压缩成为7个Byte,SMS中140Byte的TP-UD长度就可以容纳160个英文字母。

通过观察可以看出,只要我们从后到前把所有的二进制代码拼接到一块,就能够方便的处理,上面例子通过拼接后得到:

00001110100111001111001011010100

我们可以直接通过从后往前的按7个一组的原则进行截取在处理就可以得到解码后的代码。为了编程的方便,我设计了一个简单易懂的解码过程,比起通过做乘除法来进行运算的简单,但最终效率不及它。但我想在普通场合应用也绰绰有余了。

1、  Decode7Bit得到一个PDU的TP-UD部分

2、  InvertHexString反转十六进制代码:例如123456=〉563412

3、  Binary字符串得到反转后的十六进制代码的二进制表示。注意这里依然使用字符串来表示二进制,为了便于“拼接”和“切割”

4、  根据charCount所提供的字符数(来自TP_UDL)按7个一组从字符串位往前截取,并用Chr函数转换成ASCII码。

以下是一些函数的声明部分,具体函数请参见Blog内的PDUDecoder

         ‘Deoce a unicode string

         Shared Function DecodeUnicode(ByVal strUnicode As String) As String

         ‘Decode 7bit to English

Shared Function InvertHexString(ByVal HexString As String) As String

Shared Function ByteToBinary(ByVal Dec As Byte) As String

Shared Function BinaryToInt(ByVal Binary As String) As Integer

Shared Function Decode7Bit(ByVal str7BitCode As String, ByVal charCount As Integer) As String

 

SMS_SUBMIT、SMS_RECEIVED、SMS_STATUS_REPORT

由于SMS_RECEIVED、SMS_STATUS_REPORT与SMS_SUBMIT比较相似,所以我重点讲讲SMS_SUBMIT。

当用SMSBase的GetSMSType确定一个PDUCode为SMS_SUBMIT时,就可以声明一个SMS_SUBMIT类的实例,通过传递此PDUCode作为构造函数的参数。构造函数立即调用GetOrignalData函数解码。

参考协议知道SMS_SUBMIT比SMSBase多出以下部分:

Public TP_MR As Byte

Public DesAddressLength As Byte

Public DesAddressType As Byte

Public DesAddressValue As String

Public TP_VP As Byte

参考协议我们可以很方便的得到GetOrignalData函数的实现:

    Public Overrides Sub GetOrignalData(ByVal PDUCode As String)

        SCAddressLength = GetByte(PDUCode)

        SCAddressType = GetByte(PDUCode)

        SCAddressValue = GetAddress((GetString(PDUCode, (SCAddressLength - 1) * 2)))

        FirstOctet = GetByte(PDUCode)

 

        TP_MR = GetByte(PDUCode)

 

        DesAddressLength = GetByte(PDUCode)

        DesAddressType = GetByte(PDUCode)

        DesAddressLength += DesAddressLength Mod 2

        DesAddressValue = GetAddress((GetString(PDUCode, DesAddressLength)))

 

        TP_PID = GetByte(PDUCode)

        TP_DCS = GetByte(PDUCode)

        TP_VP = GetByte(PDUCode)

        TP_UDL = GetByte(PDUCode)

        TP_UD = GetString(PDUCode, TP_UDL * 2)

End Sub

这就完成了整个解码过程,通过SMSBase的巧妙设计,此解码过程显得简单方便。

 

EMS_SUBMIT、EMS_RECEIVED

对于EMS(增强型短信),其基本结构和SMS类似,主要的区别就是Information Element(IE)。所以EMS_SUBMIT继承了SMS_SUBMIT,EMS_RECEIVED继承了SMS_RECEIVED

参考3GPP协议EMS部分我们可以做出以下的结构和定义

    Public Structure InfoElem       ‘See document “How to create EMS”

        Public Identifier As Byte

        Public Length As Byte

        Public Data As String

    End Structure

Public TP_UDHL As Byte

为了得到IE我写了一个函数:

    Shared Function GetIE(ByVal IECode As String) As InfoElem()

        Dim tmp As String = IECode, t As Integer = 0

        Dim result() As InfoElem

        Do Until IECode = “”

            ReDim Preserve result(t)

            With result(t)

                .Identifier = GetByte(IECode)

                .Length = GetByte(IECode)

                .Data = GetString(IECode, .Length * 2)

            End With

            t += 1

        Loop

        Return result

End Function

然后参考协议可以写出GetOrignalData函数。具体就不再赘述。

 

PDUDecoder

这个类的由一个结构,一个重要的解码函数,组成。

结构定义了需要取得的基本信息,可以视需要修改。我这里提供一个范例

Public Structure BaseInfo

Public SourceNumber As String

Public DestinationNumber As String

Public ReceivedDate As Date

Public Text As String

Public Type As SMS.Decoder.SMSBase.SMSType

Public EMSTotolPiece As Integer

Public EMSCurrentPiece As Integer

Public StatusFromReport As SMS_STATUS_REPORT.EnumStatus

Public DestinationReceivedDate

End Structure

解码函数的声明如下:

Public Shared Function Decode(ByVal PDUCode As String) As BaseInfo

内部主要处理步骤如下(源代码请参考PDUDecoder)

1.         根据SMSBase的GetSMSType函数得到短信类型SMSType

2.         根据SMSType生成对应的类的实例

3.         解码PDU,得到基本结构

4.         通过基本结构得到BaseInfo结构里面需要的数据

5.         通过decode7bit或者decodeUnicode函数得到TP_UD数据

 

到此为止,这就是整个PDU Decoder的详细介绍,具体使用可以参见Siemens Support Tool里面相关部分,在此不再赘述

PDU的编码器的工作原理是解码器的逆过程。根据需要编码器只需要编码发送的PDU代码,工作相对简单。本文讲解编码思路,具体代码请参考Blog中PDUEncoder部分

我把PDU的编码分为两部分,SMS和EMS。EMS部分我只提供了ConcatenatedShortMessage的编码器。这是超长短信的编码,用得最多。

SMS编码

编码一个SMS一般需要如下的信息:

       TP_Data_Coding_Scheme     TP_UD编码方式

              TP_Destination_Address       对方号码

              TP_Message_Reference        参考号码

              TP_Status_Report_Request   状态报告

              TP_User_Data                            用户信息

              TP_Validity_Priod                 有效期

              ServiceCenterNumber           短信中心号码

       所以在编码器中存在以上的属性,并在Set中加入了处理代码,将可读信息转换成对应的十六进制信息。

       特别注意的是TP_User_Data属性,它可以根据用户数据编码自动设置TP_UDL。对于纯英文编码,TP_UDL为所有的字符数;对于Unicode编码,由于一个字符由两个字节表示,TP_UDL为所有的字符数*2。注意检查TP_User_Data的长度,对于SMS来说编码后的TP_UD长度不能超过140字节。也就是说英文160个字符(140/7*8),中文70个字符。

对于TP_UD的编码在解码器中也有说明,在此不再赘述。

我还设计了几个枚举变量:

              ENUM_TP_DCS                         编码方式

              ENUM_TP_SRI                          状态报告

              ENUM_TP_VALID_PERIOD 有效期

              ENUM_TP_VPF                         有效期格式

       这些枚举变量可以简化输入,也利于日后扩充。

       当以上内容设置好以后,基本上一个短信的架子就出来了。此时调用GetSMSPDUCode进行组合,简单的把十六进制拼接起来就形成了一个完整的PDU代码。

EMS——ConcatenatedShortMessage部分

编码EMS较SMS复杂,但每条EMS的基础还是SMS,所以我直接继承了SMS类。区别主要是要处理好TP_UD和IE。对于ConcatenatedShortMessage,由于其IE和TP_UDHL占据了TP_UD的部分空间,所以每条短信英文只能容纳133字符,中文66字符。我们可以通过此信息得到短信条数。

如果TP_DCS为Unicode编码,则短信条目为:
TotalMessages = (TP_UD.Length / 4) \ 66 + ((TP_UD.Length / 4 Mod 66) = 0)+1

如果为7bit,则为:

TotalMessages = (tp_ud.Length \ 266) - ((tp_ud.Length Mod 266) = 0)+1

注意在程序中我为了简化以后的数组操作,就没有加一。

确定了短信条数以后通过一个循环就可以提取出每条短信的TP_UD。

Select Case tp_dcs

Case ENUM_TP_DCS.UCS2

tmpTP_UD = Mid(TP_UD, i * 66 * 4 + 1, 66 * 4)’When TP_UDL is odd, the max length of an Unicode string in PDU code is 66 Charactor.See [3GPP TS 23.040 V6.5.0 (2004-09] 9.2.3.24.1

Case ENUM_TP_DCS.DefaultAlphabet

tmpTP_UD = Mid(tp_ud, i * 133 * 2 + 1, 133 * 2)

End Select

此后还需要编码IE部分,关键代码是确定TP_UDL的值。对于TP_DCS为7bit来说确定此值显得比较复杂,弄不好容易出现多一个少一个的错误。

If tp_dcs = ENUM_TP_DCS.UCS2 Then

TP_UDL = tmpTP_UD.Length / 2 + 6 + 1 ‘6: length of IE

End If

If tp_dcs = ENUM_TP_DCS.DefaultAlphabet Then

TP_UDL = Fix((tmpTP_UD.Length + 7 * 2) * 4 / 7)   ‘6:length of IE

End If

然后根据3GPP里关于EMS的结构的说明就可以编写出EMS PDU的处理程序。详见原代码。

如果需要扩展EMS以适应更多种类的EMS,可以参考3GPP写出更为强大的编码程序。但最关键的还是需要处理好IE以及TP_UDL。

(十二)  短信部分——通过RS232发送和接收短信

通常,发送和接收短信的终端都是通过串行接口连接电脑,这类设备用得比较多的是GSM Modem和手机。这类设备通常都支持PDU模式,但仍有少数设备只支持Text模式。

设备硬件连接好以后可以通过发送AT指令测试设备是否连接正确能否正常通讯。在这里我使用Windows自带的“超级终端”工具进行通讯。此工具可以在程序——附件——通讯里面找到,如果没有请确认是否安装了此组件。或者在运行里面输入“hypertrm”也可以快速启动“超级终端”。

以下所有范例均以Siemens M55手机作为终端,有可能跟你设备返回的不同。具体参数清参阅设备相关AT指令集。本手机AT指令集在我主页上有下载。

准备工作:

1、  测试连接:“AT8 ”测试终端是否连接正确。成功后返回“OK”。

2、  设置回显:(此步骤为了测试方便)ATE18

3、  查阅及设置字符集:
AT+CSCS=?8
+CSCS: (”GSM”,”UCS2″)
说明该终端支持GSM与UCS2两种字符集。一般对于中文环境设置为UCS2
AT+CSCS=”UCS2″8

4、  取得短信中心号码:
AT+CSCA? 8
+CSCA: “+8613800280500″,145

5、  查询并设置SMS格式:
查询:AT+CMGF= 8
返回:+CMGF: (0)
0代表PDU模式。你的设备可能有其他的选项,请参考设备的AT指令集。
设置:AT+CMGF=08

6、  查询并设置短信储存位置:
查询:AT+CPMS=?8
返回:+CPMS: (”MT”,”SM”,”ME”),(”MT”,”SM”,”ME”),(”MT”,”SM”,”ME”)
       其中MT表示设备所有可用储存位置。SM代表SIM卡,ME代表机身。
一般设置为:AT+CPMS= “MT”,”MT”,”MT”8

查询短信:

1、  查询具有相同状态的所有短信
指令:AT+CMGL=n
其中n代表0-4的数字。
0——未读得短信。执行命令以后自行变为已读取。
1——已读短信。
2——草稿。
3——已发送短信。
4——全部
返回(例):
+CMGL: 76,3,,20

0891683108200805F011620D91683194041338F50000FF0530972D8603
76——序号

3——状态:发送

20——PDU串长度

2、  查询特定序号的短信
指令:AT+CMGR=n8
其中n代表序号
返回(例):
+CMGR: 3,,20
0891683108200805F011620D91683195041338F50000FF0530972D8603
3——状态:发送
20——PDU串长度

注意:PDU串长度表示PDU中除去短信中心部分剩下的代码的长度的1/2。例如上述PDU中PDU长度部分为11620D91683195041338F50000FF0530972D8603,40个字符,表示20个字节。

 

储存PDU
指令
AT+CMGW=[PDU长度] 8
>[PDU串]
例如
AT+CMGW=208

> 0891683108200805F011620D91683195041338F50000FF0530972D8603
注意,输入PDU后按“CTRL+Z”终止。程序中使用0×1A,0×1D作为终止。

返回:+CMGW: 85
85——序号

 

发送PDU

1、  发送输入的PDU串
指令:
AT+CMGS=[PDU长度] 8
>[PDU代码]

2、  发送指定序号的PDU串
指令:
AT+CMSS=[序号] 8

 

接收短信

接收刚收到的短信有两种方法:轮询终端;使用事件

轮询终端可以定期的使用AT+CMGL=0指令读取未读取得指令。方法简单,但许多时候都在做无用功,效率低下,一般不建议采用。下面主要讲解事件法:

指令:
       AT+CNMI=,,,,

参数:
mode

       0——缓存在终端

       1——直接发送到TE

mt

       0——接收到新的SMS不返回事件

       1——如果接收到的SMS存储在ME,则返回
+CMTI:,

       2——除了Class2 SMS,新的SMS直接发送到终端,返回:
+CMT:

       3——Class3 SMS使用mt=2的方法返回,其他类型的使用mt=1的方法返回。

bm

       0——小区广播不通知

       2——新的小区广播通知,返回
+CBM:

       3——Class3格式的小区广播通知,使用bm=2格式

ds

       0——状态报告不通知

       1——新的状态报告通知,返回:
+CDS:

       2——如果新的状态报告存储到ME,则返回:

              +CDSI:,

brf

       1——始终为1

例:

       一般我们使用AT+CNMI=1,1,0,2,1

当收到新的短信时终端返回:

       +CMTI:ME,5

新的状态报告:

       +CDSI:ME,6

程序可以通过判断返回值并使用AT+CMGR指令返回新到短信

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多