用过西门子手机的都知道西门子手机在人性化设计方面做得比较好,但是让笔者失望的是电脑端上使用的官方软件SDS,其缺点是操作不方便,速度比较慢,感觉人性化设计不到位。后来官方推出了用于65系列的MobilePhoneManager,界面很好看,但安装后大于120MB的容量及其较慢的速度又让人大跌眼镜。
GhostMobile(简称GM)是我用过的国产非官方软件里面比较好的一款,但经常出现传输中一直等待的情况。估计作者并没有超时设计。另外GM文件传输速度很慢,短信管理不方便,后来也由于作者使用了新的手机,也就放弃了GM。后来又找到一款SiemensMobileControl,简称SiMoCo,是国外的非官方软件。800多kb的身躯及其速度、功能方面都超过了官方及GM,令人刮目相看,一度成为我最喜欢的软件。但用后发现一些问题,软件过于专业,选项太多,对中文的支持不好。
所以最终的目的就是做一款能够实现文件传输、短信、便签、任务、重要记事管理的软件。
(三)准备工作
2004的暑假我已经做了一部分,实现了基本的文件传输和短信功能,当时取名叫作M55FileTransferTool。后来在东北手机网上公开了,有一些GM无法连接的手机我的软件都可以连接,所以到现在为止有些网友还在使用我的这个软件。但由于知识不够,做得不是很理想,经常出现问题。
短信部分是官方网站下载的ATC_Command_Set_For_L55_Platform,详细地讲述了55平台上的AT指令集。其实SMS部分的AT指令各大手机厂商都是通用的,已基本上属于同一的指令集了。但是发现文件传输是OBEX却不是那么简单。
这条线索给我了极大的鼓舞,因为后来,顺藤摸瓜找到了红外线传输协议,意外地发现了IrOBEX的描述协议竟然和监视到的HEX代码的结构一样。随后经过仔细的研究发现就是OBEX协议,此协议可以作为上层协议用在红外线协议、蓝牙协议等。此过程大约经历了2个多月。其实现在看来这个问题简单了,手机的工厂模式的串口监视里面就会显示当前使用的协议。当数据传输开始时,会自动从GIPSY变为OBEX。但那个时候哪知道呢?
跨越了OBEX协议的障碍以后我写了一个OBEX-Multithread类,写得很垃圾,把十六进制转换成字符串,然后再转回来在发送。中间使用了string作字符串操作,速度很慢,测试以后只能勉强超过GM的传输速度。
后来借着SerialMonitor监视GM读取手机通讯薄的原理,发现通讯薄是在\telecom\pb目录里面,但是这个目录在手机里面是隐藏的,无法直接访问。由于原来写得OBEX库很糟糕,只能对应文件传输,对于这个特殊文件夹里面的文件都无法操作。修改了之后效果不好,遂放弃了OBEX-Multithread。
由于学习的原因,中途也只得停下来准备期末考试和六级。中途无聊的时候研究IrMC里面的vCard、vNote、vCalc格式,基本弄懂了如何同步通讯薄、便签、日历。2005年1月14日,放假回家了就正式开始动工,把所有的东西都重新写,对我来说,这是一个巨大的挑战。
(四)AT指令简介
AT指令在当代手机通讯中起着重要的作用,能够通过AT指令控制手机的许多行为,包括拨叫号码、按键控制、传真、GPRS等。西门子M55手机为我提供了很多的AT指令,网络上关于AT指令的资料也很多,我这里提取一些比较重要的做个简单解释。其他的手机也基本上通用,更详细的资料请查阅手机生产商的资料。
欲使用AT命令,可以安装微软的超级终端程序,选择好端口连接速度以后就可以正常使用了。
AT指令用法
注:并不是所有的AT指令都支持1和2。
常用基本AT指令
以上这些指令都用于与手机连接的时候初始化用。取得手机IMEI及IMSI可以给使程序支持更多的手机连接并且保持数据独立。
短信部分
以上命令是短消息部分最经常使用的命令。具体条目及使用方法会在后面讲解。
(五)OBEX介绍
一、什么是OBEX,它有什么用途?
OBEX全称为ObjectExchange,中文对象交换,所以称之为对象交换协议。它在此软件当中有着核心地位,文件传输和IrMC同步都会使用到它。
OBEX协议构建在IrDA架构的上层.
OBEX协议定义了一种柔性的概念——objects。也即是对象。这些对象可以包括文件,诊断信息,电子商务卡片,银行的存款等等。Objects在这里没有高级的技术含义,而是视你的应用而定。
OBEX能够具有以下几个特点:
1、友好的应用——可实现快速开发。
2、紧缩——可用在资源有限的小型设备上。
3、跨平台
4、柔性的数据支持。
5、方便的作为其他Internet传输协议的上层协议。
6、可扩展性——提供了对未来需求的扩充支持而不影响以存在的实现。例如可扩展安全,数据压缩等。
7、可测试可调试。
更为具体的关于OBEX的介绍请查阅IrOBEX协议。
二、OBEX对象模型
关于Headers
对象模型回答了对象是如何在OBEX协议描述的。这个模型必须包括被传输的对象和对对象的描述。为了做到这点,OBEX定义了Headers的概念。
Headers的构成
Headers简单的由和组成,简称为和。
HI由一个字节组成,指出了Header包含的内容以及它的格式。HV包含了一个或者多个字节,其结构由HI所决定。
所有的Header都是可选的,取决于设备的类型和事务的种类。你可以使用所有的Header,或者一些,或者没有。ID可以使Header可解析以及与传输顺序无关,也可以使不支持的Header被忽略掉。
HI又可以分为两部分,高2位和低6位。高2位确定了HI的编码方式(见表二),低6位确定了HI的意义(见表三)。两个表都是我从IrOBEX中的表摘抄并部分翻译过来的。
表二
HI的第8和第7位
意义
Byte块,2个字节的无符号整数前缀。
1Byte容量。
11(0xC0)
4Byte容量,以高位先传输为原则。
表三
HI
Header名称
描述
0xC0
Count
连接中用于指名对象的数量。
Name
对象的名字。一般为文件名。
Type
对象的类型。例如text,html,binary,manufacturespecific
0xC4
Time
Description
对对象的文本描述
Target
操作的目的服务名
HTTP
一个HTTP1.x头
Body
对象的一部分
Endofbody
对象的最后一部分
Who
OBEXApplication标识,用于表明是否是同一个应用。
0xCB
ConnectionID
用于OBEX多路连接的标识
App.Parameters
扩展的应用层请求和回复信息
Auth.Challenge
Authenticationdigest-challenge
Auth.Response
Authenticationdigest-response
ObjectClass
对象的OBEX对象类
Reserved
保留
Userdefined
用户自定义的。
关于常用Header的更详尽的解释,更详尽信息请参考IrOBEX
2、LengthLength描述了对象的大小,由4个字节组成。如果Length事先知道,这个Header应该被用到。这样可以让接受者迅速的知道需要分配多少空间,可使处理更为迅速。但这也不是必须的,有些情况下长度无法确认,但设备可以通过End-BodyHeader知道什么时候结束。
3、Time
BodyHeader由HI、一个2Byte长度的描述和整个的对象本身。End-of-Body组成和Body组成一样,但标识了这是对象的最后一部分。如果对象本来就很小,就直接使用End-of-Body。
三、请求(Request)和回应(Response)
OBEX使用Request和Response作为最基本的操作。请求的每个Request必然有一个Response,否则可认为Request失败。
Request由一个或多个的Packet(包)组成,每个包的结构如下表
Request数据包结构
Byte0
Byte1,2
Byte3ton
操作码(opcode)
PacketLength(包长度)
Headers或请求信息
由于每个Request可能有多个Packet,opcode的最高位称为Finalbit。如果被设置为1,那么说明这是Request的最后一个Packet。例如:当用PUT操作发送一个大文件时,会有几个Packet作为一个Request。那么只有最后一个Packet的FinalBit设置为1。
Response也由一个或多个Packet组成,每个包的结构如下表
Response数据包结构
ResponseCode(返回值)
ResponseLength(回应长度)
ResponseData回应的数据
同样的ResponseCode的最高位也叫做FinalBit。ResponseData可能包含对象和Header,或者其它信息。
下表列出了了常见的opcode和responseCode,更详尽的请参考IrOBEX1.2文档。
opcode
Opcode(w/highbitset)
定义
Connect
连接
Disconnect
断开连接
Put
发送一个对象
Get
取得一个对象
保留的
SetPath
设置路径
0xFF*
Abort
取消当前的操作
作为扩展保留
Userdefinable
用户自定义的
*总是设置FinalBit
ResponseCode
Continue(继续)
OK,Success
BadRequest(服务端不明白Request)
Fobidden(禁止——服务器明白Request,但拒绝)
NotFound(未找到)
四、说明。
1、Connect(连接)
此操作初始化会话然后设置参数。其Request格式为
Byte3
Byte4
Byte5,6
Byte7ton
包长度
OBEX版本
标志
最大OBEX包长度
可选Header
注:OBEX版本现在为1.0。
Response格式为:
对于更多关于Connect的说明请参考IrOBEX
2、Disconnect(断开当前会话)
此操作断开OBEX会话。例如断开当前FolderListingService,然后使用Connect连接到IrMCSyncService实现同步通讯薄等功能。
Disconnect格式为:
Bytes1,2
Bytes3ton
成功的断开会返回0Xa0,拒绝会返回0xD3
3、Put操作
Put操作从客户端发送一个对象到服务端。一般至少含有Name和Length两个Header。对于文件而言有可能还有Date/TimeHeader。
Put操作的格式如下:
PacketLength包长度
一组Header
回应格式如下:
关于Put操作的更详细的用法将在文件传输部分作解释。
4、Get操作
Get操作从服务端返回一个对象。
Get操作的格式如下:
关于Get操作更详细的用法将在文件传输部分作解释。
5、Abort操作
Abort操作中断一个多包操作(例如发送一个大文件)。Abort操作可以包含描述中断原因的DescriptionHeader。
Abort操作的格式如下:
0xFF
Abort对应的应该是一个成功的操作(0xA0),指明这个操作已被接收并且服务端已经重新与客户端同步。如果返回的另外的值,客户端应该Disconnect。
6、SetPath
SetPath操作用于切换对方的路径。通常使用一个NameHeader用于指定对方路径名称。如果为空,则返回默认目录,通常为根目录
SetPath操作格式如下:
(六)OBEX应用——文件传输部分
在手机数据传输方面基本OBEX应用分为
l文件传输
lIrMC同步
文件传输又可以细分为以下基本操作
l初始化连接
l断开连接
l设置路径
l取得目录信息
l创建目录
l上传下载文件
l删除文件或空目录
在笔者的软件当中设计了OBEX这个类,里面包含了以上所有的基本操作。另外针对M55的服务端的特殊性又设计了更名、取得磁盘空间信息、移动、拷贝文件的功能。具体请参考源代码。
下面具体讲述各个操作的细节。
初始化连接包括了使手机进入OBEX状态再到发送Connect指令的一系列过程。具体流程参考下图。
其中AT^SQWE=0和AT^SQWE=3是西门子特有的隐藏的AT指令,甚至在官方的AT指令集里面都没有提到。其作用是初始化手机到OBEX模式。
发送Connect指令收到手机回复以后确定MaxPacketLength等参数。最后连接到Folder-ListingService进行文件操作。如果需要IrMC同步的话,在Connect后直接连接到IrMCSyncService即可,手机立刻进入同步模式,所有的应用程序退出。
在笔者的程序中当中首先使用AT指令确定手机当前的工作,如果超时,尝试发送+++并等待1秒钟以便手机从不正常的OBEX状态中退出。然后在此发送AT,成功后即进行文件操作,否则引发一个错误。
程序中使用SetPath操作设置路径。需要注意的是,可以设计两种风格的过程:一种使用绝对路径,另一种使用相对路径。
对于手机而言,经笔者实践证明,使用绝对路径要比使用相对路径方便,而且更准确,但效率上稍逊,特别是在多层目录的情况下。由于手机没有能够返回当前路径的方法,所以相对路径总难以控制,只有通过程序控制,极容易出现错误。所以建议使用绝对路径。
如果使用绝对路径,那么路径名将呈现\path1\path2这种形式。首先回到根目录,然后一级一级的到达目的。在笔者的OBEX类当中,可以看到BacktoRoot这个过程。其作用就是把当前程序路径切换到根目录以免引起混乱。
要取得一个目录的文件信息可以用发送Get指令。该Get指令需要一个TypeHeader,其值为x-obex/folder-listing
当SetPath的Flags的Bit1设为1并且无法找到目录时,服务端会创建这个目录并进入。
请注意,再次使用SetPath并将Flags的Bit0设为1返回上层目录,否则容易引起混乱。
使用Put和Get命令可以实现上传和下载文件。注意所有的文件操作都在SetPath所指定的目录下进行。
使用Put命令实现上传时,至少需要提供NameHeader,BodyHeader,一般还要提供LengthHeader,DateTimeHeader。大文件传输需要正确处理BodyHeader,不能超出了服务端最大所能接收的Packet的大小,否则会出现错误。
需要注意的是如果当前文件存在,那么Put命令不会覆盖已有的文件而是追加,导致错误。传输文件之前需要首先删除同名文件。
使用Get命令实现下载时,只需要提供NameHeader即可。具体例子参考上一节。
使用Put命令,其NameHeader指定为文件名或目录名并将BodyHeader设置为空即可。
非空目录无法删除,会返回错误。
(七)IrMC简介
要实现通讯薄、日历、便签的同步,需要用到IrDA协议里面的IrMC部分。
下面主要讲述我在开发当中所用到的部分以及实现方法。更为详细的资料请参考IrMC协议。
Phonebook
在手机软件桌面端通讯薄的管理是整个软件的必备功能之一,利用其信息可以方便的和Outlook等软件实现同步,实现更强大的功能。
通讯薄的管理分为读取、删除、增添、修改。通过这几个功能的组合可以实现更为强大的同步功能。下面分条概述。
l读取
1.读取指定LUID的Entry使用OBEX的GET命令取得\telecom\pb\luid\xxxx.vcf,其中xxxx代表了LUID号码。得到的依然是一个vCard文件,只不过只包含特定LUID号码的vCard信息。可以简单的通过Outlook查看vCard所包含的信息。
l删除欲删除一个Entry
2.得到ChangeCount
4.使用OBEX的PUT命令,传输一个xxxx.vcf(xxxx指LUID)空文件到\telecom\pb\luid覆盖即可。
l添加欲添加一个文件:
1.连接到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、vNote、vCalender的.Net简单编码解码器请参阅SIEMENSSUPPORTTOOL源代码中的IrMC部分。
vCardObject(vCard对象)
vCardProperty(vCard属性)
vCard是一个或多个Property的集合。一个Property是唯一命名的值。一系列的Property可以在vCard中成为一组。
vCardProperty的格式如下:
注:
1、PropertyName及PropertyParameters不区分大小写。
2、PropertyParameters可选,可以为零个或多个,与ProperyName以分号相隔,与PropertyValue以冒号相隔。
3、vCard可以分多行呈现。由于在这个软件里面应用得不多,所以笔者也没有钻研具体实现方法。可以参考vCardSpecification。
例如TEL;HOME;+86111222333其PropertyName为TEL,PropertyParameters为HOME,PropertyValue为+86111222333。
Encoding(编码)
vCard默认的编码方式是7-Bit。默认编码方式可以使用ENCODING属性参数(Propertyparameter)改变。其值为可以为BASE64;QUOTED-PRINTABLE;8BIT。这个参数可以用在任何的Property里。
例如:
X-ESI-CATEGORIES;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE:=E5=AE=B6=E4=BA=BA
例如ENCODING=QUOTED-PRINTABLE:Home=E5=AE=B6People=E4=BA=BA
CharacterSet(字符集)
默认的字符集是ASCII。可以通过CHARSET参数改变默认的字符集。其参数可取的值为所有IANA(InternetAssignedNumbersAuthority)注册的字符集。这个参数可以用于任何Property,但某些Property并不起作用。
vCard例子:
BEGIN:VCARD
VERSION:2.1
X-IRMC-LUID:1017646
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
BDAY:1985-04-23
END:VCARD
(九)短信部分——PDU简介及其格式
PDU是大多数手机短信通讯的核心,仅有少数手机只支持Text模式(例如笔者的MOTOC330)。PDU模式比起Text模式可以提供能为强大的功能,但其编码较Text模式困难。无论哪种模式,我们都可以通过AT指令控制终端实现短信的发送、接收、删除等管理。下面主要介绍PDU的构成及编码解码。
PDU的构成
下面举一个发送和接收的例子。
1、手机发送的一个PDU串:
0891683108200805F011190D91683188902848F40008FF108FD9662F4E0067616D4B8BD577ED4FE
对比3GPP协议得到:(二进制代码从左到右依次为高位->低位)
短信中心地址字段
08地址长度:8个字节,包括其后的91
FirstOctet字段
11包含TP-MTI(2bit),TP-RD(1bit),TP-VPF(2bit),TP-RP(1bit),TP-UDHI(1bit),TP-SRR(1bit)
二进制表示形式:00010001
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:0TP-User-Data-Header-Indicator(用户数据头标示)Bit6:0指示这是一个SMS消息,没有用户数据头。EMS消息需要设置。
TP-RP:0TP-Reply-Path(回复路径)Bit7:0指示没有设置回复路径。
消息参考值TP-MR
19TP-Message-Reference
对方号码字段
0D91683188902848F4
其结构与短信中心号码字段部分类似,不再赘述。
协议标识TP-PID
00TP-Protocol-Identifier(上层协议指示),一般设置为00,表示普通GSM,点对点编码方法TP-DCS
08TP-Data-Coding-Scheme(数据编码设置),指示TP-UD的编码方式。08代表Unicode方式。00为7Bit编码
有效期TP-VP
FFTP-Validity-Period(有效期)。FF表示最大。
用户数据长度TP-UDL
10TP-User-Data-Length(用户数据长度)
用户数据TP-UD
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
协议标识00TP-DCS点对点
编码方式
08TP-DCSUnicode编码
用户数据长度
10TP-DHL
用户数据
8FD9662F4E0067616D4B8BD577ED4FE1TP-UD
(十)短信部分——VB.NET解码PDU
解码器的构成
NameSpaceSMS
Decoder
MustInheritClassSMSBase
ClassEMS_RECEIVED
ClassEMS_SUBMIT
ClassSMS_RECEIVED
ClassSMS_STATUS_REPORT
ClassSMS_SUBMIT
ClassPDUDecoder
SMSBase部分
SMSBase包含了所有短信类型所共有的基本信息部分以及一个指示短信类型的枚举SMSType,继承的类扩展其特有的基本信息部分。
PublicTP_PIDAsByte
PublicTP_DCSAsByte
PublicTP_UDLAsByte
PublicTP_UDAsString
PublicTextAsString
PublicTypeAsSMSType
PublicUserDataAsString
PublicEnumSMSType
SMS_RECEIVED=0
SMS_STATUS_REPORT=2
SMS_SUBMIT=1
EMS_SUBMIT=65
EndEnum
SMSBase中定义了一个必须重写的过程GetOrignalData,其参数为PDUCode,目的是为了得到PDU的基本信息。不同的短信类型具有不同的解码过程,所以作为一个必须重写的函数。
PublicMustOverrideSubGetOrignalData(ByValPDUCodeAsString)
SMSBase中还有一系列的辅助函数,具体实现方法见源代码:
处理PDU代码的:
SharedFunctionGetByte(ByRefPDUCodeAsString)AsByte
SharedFunctionGetString(ByRefPDUCodeAsString,ByValLengthAsInteger)AsString
SharedFunctionGetDate(ByRefSCTSAsString)AsDate
SharedFunctionSwap(ByRefTwoBitStrAsString)AsString
SharedFunctionGetAddress(ByRefAddressAsString)AsString
SharedFunctionGetSMSType(ByValPDUCodeAsString)AsSMSBase.SMSType
TP-UD解码部分:
TP-UD的解码的任务主要集中在Unicode的解码和7BitCode的解码。其中Unicode的解码很方便,只需要将两个字节的PDUCode通过Val函数转换成为数字,在通过ChrW函数即可得到。
Byte1110101000xD4
Byte2111100100xF2
注:各字符二进制代码:
T:1010100e:1100101s:1110011t:1110100
从这个例子可以看出一个Byte包含了一个字符的ASCII码的二进制部分及后续字符的二进制部分的低位。这样8个字符可以压缩成为7个Byte,SMS中140Byte的TP-UD长度就可以容纳160个英文字母。
通过观察可以看出,只要我们从后到前把所有的二进制代码拼接到一块,就能够方便的处理,上面例子通过拼接后得到:
00001110100111001111001011010100
我们可以直接通过从后往前的按7个一组的原则进行截取在处理就可以得到解码后的代码。为了编程的方便,我设计了一个简单易懂的解码过程,比起通过做乘除法来进行运算的简单,但最终效率不及它。但我想在普通场合应用也绰绰有余了。
1、Decode7Bit得到一个PDU的TP-UD部分
2、InvertHexString反转十六进制代码:例如123456=〉563412
4、根据charCount所提供的字符数(来自TP_UDL)按7个一组从字符串位往前截取,并用Chr函数转换成ASCII码。
SharedFunctionDecodeUnicode(ByValstrUnicodeAsString)AsString
SharedFunctionInvertHexString(ByValHexStringAsString)AsString
SharedFunctionByteToBinary(ByValDecAsByte)AsString
SharedFunctionBinaryToInt(ByValBinaryAsString)AsInteger
SharedFunctionDecode7Bit(ByValstr7BitCodeAsString,ByValcharCountAsInteger)AsString
SMS_SUBMIT、SMS_RECEIVED、SMS_STATUS_REPORT
由于SMS_RECEIVED、SMS_STATUS_REPORT与SMS_SUBMIT比较相似,所以我重点讲讲SMS_SUBMIT。
参考协议知道SMS_SUBMIT比SMSBase多出以下部分:
PublicTP_MRAsByte
PublicDesAddressLengthAsByte
PublicDesAddressTypeAsByte
PublicDesAddressValueAsString
PublicTP_VPAsByte
参考协议我们可以很方便的得到GetOrignalData函数的实现:
PublicOverridesSubGetOrignalData(ByValPDUCodeAsString)
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+=DesAddressLengthMod2
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)
EndSub
这就完成了整个解码过程,通过SMSBase的巧妙设计,此解码过程显得简单方便。
EMS_SUBMIT、EMS_RECEIVED
对于EMS(增强型短信),其基本结构和SMS类似,主要的区别就是InformationElement(IE)。所以EMS_SUBMIT继承了SMS_SUBMIT,EMS_RECEIVED继承了SMS_RECEIVED
参考3GPP协议EMS部分我们可以做出以下的结构和定义
PublicIdentifierAsByte
PublicLengthAsByte
PublicDataAsString
EndStructure
PublicTP_UDHLAsByte
为了得到IE我写了一个函数:
SharedFunctionGetIE(ByValIECodeAsString)AsInfoElem()
DimtmpAsString=IECode,tAsInteger=0
Dimresult()AsInfoElem
ReDimPreserveresult(t)
Withresult(t)
.Identifier=GetByte(IECode)
.Length=GetByte(IECode)
.Data=GetString(IECode,.Length*2)
EndWith
t+=1
Loop
Returnresult
EndFunction
然后参考协议可以写出GetOrignalData函数。具体就不再赘述。
PDUDecoder
这个类的由一个结构,一个重要的解码函数,组成。
结构定义了需要取得的基本信息,可以视需要修改。我这里提供一个范例
PublicStructureBaseInfo
PublicSourceNumberAsString
PublicDestinationNumberAsString
PublicReceivedDateAsDate
PublicTypeAsSMS.Decoder.SMSBase.SMSType
PublicEMSTotolPieceAsInteger
PublicEMSCurrentPieceAsInteger
PublicStatusFromReportAsSMS_STATUS_REPORT.EnumStatus
PublicDestinationReceivedDate
PublicSharedFunctionDecode(ByValPDUCodeAsString)AsBaseInfo
内部主要处理步骤如下(源代码请参考PDUDecoder)
1.根据SMSBase的GetSMSType函数得到短信类型SMSType
2.根据SMSType生成对应的类的实例
3.解码PDU,得到基本结构
4.通过基本结构得到BaseInfo结构里面需要的数据
5.通过decode7bit或者decodeUnicode函数得到TP_UD数据
PDU的编码器的工作原理是解码器的逆过程。根据需要编码器只需要编码发送的PDU代码,工作相对简单。本文讲解编码思路,具体代码请参考Blog中PDUEncoder部分
我把PDU的编码分为两部分,SMS和EMS。EMS部分我只提供了ConcatenatedShortMessage的编码器。这是超长短信的编码,用得最多。
SMS编码
编码一个SMS一般需要如下的信息:
TP_Data_Coding_SchemeTP_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有效期格式
这些枚举变量可以简化输入,也利于日后扩充。
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/4Mod66)=0)+1
如果为7bit,则为:
TotalMessages=(tp_ud.Length\266)-((tp_ud.LengthMod266)=0)+1
注意在程序中我为了简化以后的数组操作,就没有加一。
确定了短信条数以后通过一个循环就可以提取出每条短信的TP_UD。
SelectCasetp_dcs
CaseENUM_TP_DCS.UCS2
CaseENUM_TP_DCS.DefaultAlphabet
tmpTP_UD=Mid(tp_ud,i*133*2+1,133*2)
EndSelect
此后还需要编码IE部分,关键代码是确定TP_UDL的值。对于TP_DCS为7bit来说确定此值显得比较复杂,弄不好容易出现多一个少一个的错误。
Iftp_dcs=ENUM_TP_DCS.UCS2Then
EndIf
Iftp_dcs=ENUM_TP_DCS.DefaultAlphabetThen
然后根据3GPP里关于EMS的结构的说明就可以编写出EMSPDU的处理程序。详见原代码。
如果需要扩展EMS以适应更多种类的EMS,可以参考3GPP写出更为强大的编码程序。但最关键的还是需要处理好IE以及TP_UDL。
(十二)短信部分——通过RS232发送和接收短信
通常,发送和接收短信的终端都是通过串行接口连接电脑,这类设备用得比较多的是GSMModem和手机。这类设备通常都支持PDU模式,但仍有少数设备只支持Text模式。
准备工作:
2、设置回显:(此步骤为了测试方便)ATE18
5、查询并设置SMS格式:查询:AT+CMGF=8返回:+CMGF:(0)0代表PDU模式。你的设备可能有其他的选项,请参考设备的AT指令集。设置:AT+CMGF=08
查询短信:
1、查询具有相同状态的所有短信指令:AT+CMGL=n其中n代表0-4的数字。0——未读得短信。执行命令以后自行变为已读取。1——已读短信。2——草稿。3——已发送短信。4——全部返回(例):+CMGL:76,3,,20
0891683108200805F011620D91683194041338F50000FF0530972D860376——序号
3——状态:发送
20——PDU串长度
2、查询特定序号的短信指令:AT+CMGR=n8其中n代表序号返回(例):+CMGR:3,,200891683108200805F011620D91683195041338F50000FF0530972D86033——状态:发送20——PDU串长度
注意:PDU串长度表示PDU中除去短信中心部分剩下的代码的长度的1/2。例如上述PDU中PDU长度部分为11620D91683195041338F50000FF0530972D8603,40个字符,表示20个字节。
储存PDU指令:AT+CMGW=[PDU长度]8>[PDU串]例如:AT+CMGW=208
返回:+CMGW:8585——序号
发送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——除了Class2SMS,新的SMS直接发送到终端,返回:+CMT:
3——Class3SMS使用mt=2的方法返回,其他类型的使用mt=1的方法返回。