用Ruby在windows下通过串口自动测试AT
原文: http://deli.xmu.me/?p=7
手机芯片厂家常常会更新软件版本,在发给客户之前,一般先对AT进行一次测试。手机设计公司收到新的软件版本,烧入Flash, 同样也要对模组的AT进行一次测试,验证是否正常,比较与上个版本是否有差别。手工一个个验证,乏味,容易对工作失去兴趣。要是能自动测试,那该多好啊。
用C写? 烦烦烦!perl? 不熟悉? python? 呃…. 这时候,ruby向我们走来了。从http://rubyinstaller.rubyforge.org 可以下载 Ruby One-Click,现在最新版本安装软件为ruby186-27_rc2.exe。安装过程中,把RubyGems也勾上。在windows上用ruby访问串口,可以用win32ole模块,也可以用ruby-serialport,后者是跨平台的。安装ruby-serialport前要做一些准备工作,确认有装Microsoft's Visual C++ 或Borland's C++编译器,
(1)设置环境变量:
PATH=C:\Program Files\Microsoft Visual Studio\VC98\Bin
INCLUDE=C:\Program Files\Microsoft Visual Studio\VC98\Include
LIB=C:\Program Files\Microsoft Visual Studio\VC98\Lib
(2)安装ruby-serialport:
开始>运行>CMD 输入:
gem install ruby-serialport
接下来,就可以写一个测试脚本了。
#! /usr/bin/env ruby
#
require 'rubygems'
require 'serialport'
# 0 is mapped to "COM1" on Windows, and 5 is COM6, 115200 is baud rate
port = 1
baud = 4800
sp = SerialPort.new(port-1,baud,data_bits=8,stop_bits=1,parity=SerialPort::EVEN)
p "com #{port} , #{sp.get_modem_params()} "
sp.write "AT\r\n"
sleep(0.2)
puts sp.read
注意:如果没有sleep,可能会收不到"OK"响应。
除了 ruby-serialport , 还有 win32ole, 美其名曰:Windows Automation。win32ole是Masaki Suketa编写的Ruby扩展,是标准ruby发行版本的一部分。
现在写一个mscomm.rb文件
#! /usr/bin/env ruby
#
# filename: mscomm.rb
# @date 2009.5.16
#
require 'win32ole'
def initialize(port,settings="4800,N,8,1")
@serial = WIN32OLE.new("MSCOMMLib.MSComm")
@serial.CommPort = port
@serial.Settings = settings
@serial.InputLen = 8
@serial.PortOpen = true
p "com #{port} , #{settings} "
end
def write(str)
@serial.Output = str
nil
end
def read
str = @serial.Input
str
end
def close
@serial.PortOpen = false
end
def serial
@serial
end
end
class String #为String类添加2个方法,可以直接调用: "\x01\x00".to_hex
def to_hex # "\x01\x00" to "01 00"
self.split('').map{|x| x.ord < 16 ? '0' + x.ord.to_s(16) : x.ord.to_s(16) }.join(' ')
end
def jhex # "01 00" to "\x01\x00"
self.split(' ').map{|x| x.hex.chr}.join
end
end
再写一个简单的测试用例test_comm.rb:
#! /usr/bin/env ruby
#
# filename: test_comm.rb
# @date 2009.5.16
#
require 'mscomm.rb'
sp = MSCOMM.new(1)
$last = Time.now
str=''
while true do
sleep 0.005
s= sp.read
if Time.now - $last > 0.2 or str.size > 22
if str.size > 2
p str
log str
end
str = '' #超时才清空
end
if s.size > 0
$last = Time.now
str << s
p str.to_hex
end
case str
when /\x01\x01(.....)/
#do some thing.
end
和上面的test.rb用法一样,没什么神秘感,是不是很简单呢
再加个查询信号的AT,在test_comm.rb倒数第二行加上
comm.write("AT+CSQ?\r\n")
sleep(0.2)
puts comm.read
要是很多很多… 唉,又好烦。想想,还是写个函数吧.
def exec_cmd(comm, cmd)
comm.write("#{cmd}\r\n")
sleep(0.2)
begin
result = comm.read
result = "Command not support\n" if result.include?("ERROR\n")
rescue
result = "Writing serial port error\n"
end
puts result
end
于是,test_comm.rb中间部分的可以可以写成:
exec_cmd(comm, "AT")
exec_cmd(comm, "AT+CSQ?")
哇,真不容易啊,情况好多了。原来函数也可以这样定义,这就是ruby 的duck typing编程风格。可是每添加一个AT… 还是得写一串的exec_cmd(comm… 。Dave Thomas大师说过:"Don't Repeat Yourself!" ,这是ruby的设计理念。瞧瞧我们还能做点什么呢?假设所有的AT都放在另外一个文件呢?我们也可以一个个读取出来。不过现在考虑的是暂时放在同个文件,那就定义一个字符串数组:
atcmd = {
'AT',
'AT+CSQ?',
'AT+CREG?'
}
也可以这样写:
Atcmd = %w{
AT
AT+CSQ?
AT+CREG?
}
第二种对于添加AT比较方便,但缺点是AT不能出现空格。那么test_comm.rb 现在又可以写为:
atcmd.each {|at| exec_cmd(comm, at) }
哇噻,好简单哦。
可是,有的AT需要花一些时间才有响应。之前都默认是0.2秒,好吧,重新定义:
def exec_cmd2(comm, cmd, timeout = 0.2)
…
sleep(timeout)
…
end
于是,就可以这样使用
exec_cmd2(comm, "AT+CDV=10000", 3)
exec_cmd2(comm, "AT+CLCC?")
exec_cmd2(comm, "AT+CHV")
exec_cmd2(comm, "AT+CPOF", 2)
exec_cmd2(comm, "AT+CPON", 5)
像这样的一组AT,具有依赖顺序而每个AT的响应时间又不一样,我们只能根据不同的情况对AT做分类,写不同的测试脚本。
再回头看看 test_commm.rb,如果我们想把输入与输出都放在同一个Excel表格,那该如何写呢?原理一样,依旧用win32ole:
#! /usr/bin/env ruby
#
# filename: excel.rb
# @date 2009.5.16
#
require 'win32ole'
class Excel
def initialize(filename = nil)
@excel = WIN32OLE.new("excel.Application") # create Excel object
@excel.Visible = TRUE
if (filename == nil)
@workbook = @excel.Workbooks.Add() # create new file
else
@workbook = @excel.Workbooks.Open(filename) # open exist file
end
end
def setvalue(pos, data)
@excel.Range(pos).Value = data
end
def save
@excel.Save()
end
def close
@excel.Quit()
end
def excelobj
@excel
end
end # end of class
我们重新写个test_cdma_at.rb
#! /usr/bin/env ruby
#
# test_cdma_at.rb
# @date 2009.5.16
#
require 'mscomm.rb'
require 'excel.rb'
def exec_cmd(comm, cmd)
comm.write("#{cmd}\r\n")
sleep(0.2)
begin
result = comm.read
result = "Command not support\n" if result.include?("ERROR\n")
rescue
result = "Writing serial port error\n"
end
return result
end
atcmd = %w{
AT
AT+PSS?
AT+CPIN?
AT+CPINC?
AT+ARSI=1
AT+GMI
AT+GESN?
AT+CIMI?
AT+CSQ?
AT+CREG=2
AT+CREG?
AT+VMCC?;+VMNC?
AT+CPBS=?
AT+CPBS?
AT+CPBS="ME"
AT+CPBS?
AT+CPBW=3,13544049382,"violet",0
AT+CPBR=3
AT+CPBw=3
AT+CPMS?
AT+CPMS=?
AT+CMGF=?
AT+CMGF?
AT+CMGF=1
AT+CMGS=13544049382,"Hello!"
AT+CMGR=3
AT+CMGD=3
AT+CMGW=,13544049382,"Hi!"
AT+CMGD=4
AT+CMGW=4,13544049382,"Hi!"
AT+CMGR=4
AT+CNUM?
AT+ISF?
AT+VSPST?
AT+CLIP=1
AT+SPEAKER=1
AT+VGT=?
AT+VGT?
AT+VGT=6
AT+VGR=?
AT+VGR?
AT+VGR=6
AT+CLCC?
AT+CPOF
AT+CPON
}
comm = MSCOMM.new(6)
excel = Excel.new("d:\\Book1.xls")
i = 1
atcmd.each {|at|
excel.setvalue("a#{i}", at)
excel.excelobj.Range("b#{i}").Value = exec_cmd(comm, at)
i += 1
}
comm.close
excel.save
excel.close
代码不难理解,就是把AT输入放在Excel表格某行的A列,响应写入某行的B列,仅如此而已。
脚本也不是万能的,单凭一个脚本就能安枕无忧,一劳永逸,这只是说梦话罢了。写脚本意在减轻编码工作量,避免重复机械的劳动。
参考:
1. 刘绪宏 使用ruby语言实现自动测试与数据采集
2. 《Programming Ruby》 第二版