《初识iPhone基带通讯》文章中,我们初步了解到了如何在iPhone中进行基带通讯,和基本的通讯方式(GSM网络AT指令),并介绍了一些比较简单AT指令。本文将进一步介绍如何利用iPhone基带读写手机SIM卡联系人。本文简要介绍一下SIM卡的一些常识,AT指令中中文字符的相关处理,并介绍如果读写SIM卡中的联系人数据。

SIM卡存储

SIM卡虽小,但每个SIM卡都有一定的存储空间。SIM也有容量,有32K,64K,甚至128K的都有。容量小的SIM卡一般可以存储200条联系人。SIM不仅可以存储联系人,也可以存储若干条短信,这取决于SIM卡的容量,这些容量信息在一些老式手机中可以看到。然而如今进入智能手机时代,SIM卡的容量已经显得太小了,比如在iPhone中,我们已经没法将联系人存储到SIM卡。iPhone OS 2.0开始,系统设置里加入一个将SIM卡的联系人全部导入到iPhone的功能。你可以在“设置” > “邮件、通讯录、日历”页面看到“导入SIM卡通讯录”。除此似乎再没有其他方式访问SIM卡数据。

联系人数据

SIM卡联系人数据结构很简单,只包括名字和电话号码这两个最基本字段。

AT指令通讯设置

为了方便进行我们的SIM存储,我们需要进行一些配置。包括存储位置的设定,中文的支持,回显的设定。下面一一介绍一下。

存储位置设定

首先我们要设定我们进行联系人操作存储单元为SIM卡:

1
NSString *result = sendATCommand(baseband, @"AT+CPBS=\"SM\"\r");

选择联系人存储单元的AT指令为:AT+CPBS,它的值可以为“ME”(手机)或“SM”(SIM卡)。旧式手机一般可以选择联系人保存位置是手机还是SIM卡,就是通过此命令进行设置。这里我们设置为“SM”(SIM卡)。

AT指令中文处理

由于AT指令中只能用ASCII码,中文的话是不支持的,这就需要我们对中文进行一些特殊的编码。目前大多数AT指令都支持UCS2编码,即Unicode两字节编码。AT指令中要把一个Unicode转换为一个4个字节的16进制表示来进行传输。比如“简体中文”要转换为“7B804F534E2D6587”。为了实现UCS2编码传输。我们需要对AT终端进行编码设置,即执行AT+CSCS="UCS2"指令:

1
result = sendATCommand(baseband, @"AT+CSCS=\"UCS2\"\r");

同时我也写了一个NSString的Catagory,来支持UCS2编码的转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@implementation NSString(UCS2Encoding)
- (NSString*)ucs2EncodingString{
NSMutableString *result = [NSMutableString string];
for (int i = 0; i < [self length]; i++) {
unichar unic = [self characterAtIndex:i];
[result appendFormat:@"%04hX",unic];
}
return [NSString stringWithString:result];
}
- (NSString*)ucs2DecodingString{
NSUInteger length = [self length]/4;
unichar *buf = malloc(sizeof(unichar)*length);
const char *scanString = [self UTF8String];
for (int i = 0; i < length; i++) {
sscanf(scanString+i*4, "%04hX", buf+i);
}
return [[NSString alloc] initWithCharacters:buf length:length];
}
@end

关闭回显

每条AT指令执行的结果默认都会返回一遍所执行的AT指令,这个是AT指令终端的回显功能,为了减少不必要的处理,我们就把它关闭,执行ATE0

1
result = sendATCommand(baseband, @"ATE0\r");

增加联系人

增加联系人需要使用AT+CPBW指令,此命令格式为

1
AT+CPBW=序号,号码,类型,名字

序号是存储位置,不能超过手机存储容量。如果为空,则会在最低存储位存储。号码就是手机号码,类型一般设置为空即可。名字为联系人的姓名。如果只有序号,其他都是空,那么将删除序号下的联系人,如AT+CPBW=5将删除存储序号为5的联系人。

我写了一个addNewSIMContact函数来插入一个联系人,相应的代码如下:

1
2
3
4
5
6
7
8
9
BOOL addNewSIMContact(NSFileHandle *baseband, NSString *name, NSString *phone){
NSString *result = sendATCommand(baseband, [NSString stringWithFormat:@"AT+CPBW=,\"%@\",,\"%@\"\r", phone, [name ucs2EncodingString]]);
if ([result hasSuffix:@"OK\r\n"]) {
return YES;
}
else{
return NO;
}
}

读取联系人

读取联系人需要使用AT+CPBR指令。首先我们要查看系统能够存储多少个联系人,使用AT+CPBR=?可以返回存储容量信息。返回结果为:

1
2
+CPBR:(起始序号-最大序号), 电话长度, 名字长度
OK

要读取联系人需要用下面指令

1
AT+CPBR=起始序号,终止序号

这样就会读取从起始序号到终止序号的所有联系人,返回的格式为:

1
2
3
+CPBR: 序号1,电话,类型,名字
+CPBR: 序号2,电话,类型,名字
OK

因此,先用AT+CPBR=?获取容量,然后用AT+CPBR=1,最大容量就能返回所有的联系人记录了。然后在进行一些必要的解析即可。相应的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
NSArray *readAllSIMContacts(NSFileHandle *baseband){
NSString *result = sendATCommand(baseband, @"AT+CPBR=?\r");
if (![result hasSuffix:@"OK\r\n"]) {
return nil;
}
int max = 0;
sscanf([result UTF8String], "%*[^+]+CPBR: (%*d-%d)", &max);
result = sendATCommand(baseband, [NSString stringWithFormat:@"AT+CPBR=1,%d\r",max]);
NSMutableArray *records = [NSMutableArray array];
NSScanner *scanner = [NSScanner scannerWithString:result];
[scanner scanUpToString:@"+CPBR:" intoString:NULL];
while ([scanner scanString:@"+CPBR:" intoString:NULL]) {
NSString *phone = nil;
NSString *name = nil;
[scanner scanInt:NULL];
[scanner scanString:@",\"" intoString:NULL];
[scanner scanUpToString:@"\"" intoString:&phone];
[scanner scanString:@"\"," intoString:NULL];
[scanner scanInt:NULL];
[scanner scanString:@",\"" intoString:NULL];
[scanner scanUpToString:@"\"" intoString:&name];
[scanner scanUpToString:@"+CPBR:" intoString:NULL];
if ([phone length] > 0 && [name length] > 0) {
[records addObject:@{@"name":[name ucs2DecodingString], @"phone":phone}];
}
}
return [NSArray arrayWithArray:records];
}

总结和其他

  • SIM卡有一定的容量存储联系人和短信。
  • 读写SIM卡联系人可以通过与基带通讯的AT指令进行。
  • AT指令中中文处理可以用UCS2编码方式来进行处理。
  • 对SIM卡读写前,务必要把操作的存储位置设置为SIM卡。
  • 读取联系人时可以先获取最大容量,然后根据最大容量列出所有联系人。
  • 如果需要一个完善的系统,可能还需要对联系人信息的长度进行判断和处理。可以用AT+CPBR=?获取这些信息,本文就不再详细介绍了。
  • 我已经将以上全部代码上传的gist,大家可以试试:https://gist.github.com/shenqiliang/9303513