在我的文章《初识iPhone基带通讯》中简单的介绍了如何利用iPhone基带进行通讯,接着我又写了《利用iPhone基带读写SIM卡联系人》,介绍了SIM卡数据读写的一些基带功能。下面我将进行关于iPhone基带的最后一篇介绍:《利用iPhone基带发送短信息》。阅读本文前,请先阅读一下之前的那两篇关于基带的介绍。

AT+CMGF

进行短信发送前,我们要设置短信发送的模式。短信发送模式分两种:文本模式和PDU(Protocol Data Unit)模式。文本模式中短信字符串可以直接放到AT指令中,但它不可以发中文等非ASCII字符。PDU模式是移动设备的基本短信模式,它同时可以支持中文的发送。

AT+CMGF命令用来设置短信的发送模式:

1
result = sendATCommand(baseband, @"AT+CMGF=0\r");

可以通过AT+CMGF=0指令设置发送模式为PDU模式,而AT+CMGF=1则为文本模式。本例将主要以PDU模式进行介绍,PDU模式也更加复杂一些,但它能支持中文。

AT+CMGS

AT+CMGS指令用于发送短信,文本模式下发送流程比较简单,比如向“10010”
发送“1”。

1
2
3
AT+CMGS="10010"
> 1 <Ctrl+Z>
OK

而PDU模式就比较复杂了,具体流程如下:

1
2
3
AT+CMGS=[length]
> [PDU] <Ctrl+Z>
OK

其中length是PDU除消息中心设置部分的长度,PDU就是PDU串内容,它是一个十六进制的字符串。

PDU(Protocol Data Unit)

PDU是对短消息中心、发送目的手机号码以及短信内容等进行的一些特殊编码,它是十六进制的字符串,具体格式为:

1
[短信息中心地址长度][短信息中心号码类型][短信息中心号码][文件头字节][信息类型][发送目的手机号码长度][发送目的手机号码类型][发送目的手机号码][协议标识][数据编码方案][有效期][用户数据长度][用户数据]

可以看出这个结构是蛮复杂的,一共由13项数据组成,如果是超过70个字符的长短信还需要更多字段。

手机号码的编码

从上面的编码格式可以看出,无论是短消息中心,还是发送目的手机号码,他们的编码都为:长度+类型+号码。长度就是手机号的实际长度,类型可以为918191表示带国际区号号码,比如前面+86的,81表示号码不带国际区号。由于PDU编码中规定十六进制的数据字符都是一对一对的,单数的号码位数末尾将补一个字符“F”,如10010只有5位数字,将表示为6位的10010F。同时手机号码中奇数和偶数的位数还要翻转。我写的翻转方法如下(NSString+Catagory):

1
2
3
4
5
6
7
8
9
10
11
12
13
- (NSString*)hexSwipString{
unichar *oldBuf = malloc([self length]*sizeof(unichar));
unichar *newBuf = malloc([self length]*sizeof(unichar));
[self getCharacters:oldBuf range:NSMakeRange(0, [self length])];
for (int i = 0; i < [self length]; i+=2) {
newBuf[i] = oldBuf[i+1];
newBuf[i+1] = oldBuf[i];
}
NSString *result = [NSString stringWithCharacters:newBuf length:[self length]];
free(oldBuf);
free(newBuf);
return result;
}

那么10010的最终编码为:810110F

整体编码

发送短信时,可以将短消息中心长度设置为00,那么就可以不用填写短消息中心了,它会自动采用手机默认设置,数据编码方案字段用08,即“UCS2”编码方式,为了可以支持中文。过期时间一般为“AA”,表示过期时间为一天,其实这个感觉好像没效果。其他一些字段可以直接采用默认的即可。整体编码函数如下。

1
2
3
4
5
6
7
8
9
10
11
12
NSString *PDUEncodeSendingSMS(NSString *phone, NSString *text){
NSMutableString *string = [NSMutableString stringWithString:@"001100"];
[string appendFormat:@"%02X", (int)[phone length]];
if ([phone length]%2 != 0) {
phone = [phone stringByAppendingString:@"F"];
}
[string appendFormat:@"81%@",[phone hexSwipString]];
[string appendString:@"0008AA"];//数据编码方案08,过期AA
NSString *ucs2Text = [text ucs2EncodingString];
[string appendFormat:@"%02x%@", (int)[ucs2Text length]/2, ucs2Text];
return [NSString stringWithString:string];
}

短信发送过程

上面介绍了短信发送相关的一些编码问题,下面来说下短信发送流程和相关代码。

短信AT指令发送流程函数:

1
2
3
4
5
6
7
8
9
10
11
BOOL sendSMSWithPDUMode(NSFileHandle *baseband, NSString *phone, NSString *text){
NSString *pduString = PDUEncodeSendingSMS(phone, text);
NSString *result = sendATCommand(baseband, [NSString stringWithFormat:@"AT+CMGS=%d\r", (int)[pduString length]/2-1]);
result = sendATCommand(baseband, [NSString stringWithFormat:@"%@\x1A", pduString]);
if ([result hasSuffix:@"OK\r\n"]) {
return YES;
}
else{
return NO;
}
}

发送短信前,需要进行一些必要的设置

1
2
3
NSString *result = sendATCommand(baseband, @"AT+CSCS=\"UCS2\"\r");
result = sendATCommand(baseband, @"ATE0\r");
result = sendATCommand(baseband, @"AT+CMGF=0\r");

OK,我们就可以向10010发送一个“测试”了。

1
sendSMSWithPDUMode(baseband, @"10010", @"测试");

试试把10010换成你自己的号码,看看能否收到自己发的短信。

源码地址:https://gist.github.com/shenqiliang/9415355