Pages:  1 2 Ruby – Page 2 – lytsing's Blog

Category: Ruby

  • 使用Ruby在windows下通过串口自动测试AT

    原文http://deli.xmu.me/?p=7,放在Wayhome mm给我提供的空间,现在已经无法访问。

    手机芯片厂家常常会更新模组软件版本,在发给客户之前,一般先对AT进行一次测试。手机设计公司收到新的软件版本,烧入Flash, 也要对模组的AT进行一次测试,验证是否正常,比较与上个版本是否有差别。手工一个个验证,乏味,容易对工作失去兴趣。

    从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++编译器,这里以VC6.0为例。
    (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:
    开始 -> 程序 -> Ruby-186-27 -> RubyGems -> RubyGems Package Manager,输入:
    gem install ruby-serialport
    如果提示找不到mspdb60.dll, 请到http://www.dll-files.com 找,把它放到
    C:\Program Files\Microsoft Visual Studio\VC98\Bin
    接下来,就可以写一个测试脚本了。

    # filename: test.rb
    require 'rubygems'
    require 'serialport'
    
    # 0 is mapped to "COM1" on Windows, and 5 is COM6, 115200 is baud rate
    sp = SerialPort.new(5, 115200)
    sp.write "AT\r\n"
    sleep(0.2)
    puts sp.read   # hopefully "OK" ;-) 

    注意:如果没有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'
    
    class MSCOMM
      def initialize(port)
        @serial = WIN32OLE.new("MSCOMMLib.MSComm")
    
        @serial.CommPort = port
        @serial.Settings = "115200,N,8,1"
        @serial.InputLen = 0
        @serial.PortOpen = true
      end
      def write(str)
        @serial.Output = str
      end
      def read
        str = @serial.Input
        str
      end
      def close
        @serial.PortOpen = false
      end
      def serial
        @serial
      end
    end

    再写一个简单的测试用例test_comm.rb:

    #! /usr/bin/env ruby
    #
    # filename: test_comm.rb
    # @date 2009.5.16
    #
    
    require 'mscomm.rb
    
    comm  = MSCOMM.new(6)
    comm.write("AT\r\n")
    sleep(0.2)
    puts comm.read
    comm.close

    和上面的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?”)

    可是每添加一个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_cmd(comm, cmd, timeout = 0.2)

    sleep(timeout)

    end

    于是,就可以这样使用
    exec_cmd(comm, “AT+CDV=10000”, 3)
    exec_cmd(comm, “AT+CLCC?”)
    exec_cmd(comm, “AT+CHV”)
    exec_cmd(comm, “AT+CPOF”, 2)
    exec_cmd(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+CPIN?
    AT+CPINC?
    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+CMGS=13544049382,"Hello!"
    AT+CMGW=,13544049382,"Hi!"
    }
    
    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列,仅如此而已。

    脚本测试也不是万能的,AT本身具有随意性,写脚本意在减轻编码工作量,避免重复机械的劳动。

  • 用C++写ruby扩展

    为了测试CDMA短信的发送方便,于是想到编译成lib,提供ruby使用。

    SmsObject.h:
    void SendSms(const char* msg, const char* number, char* out);

    main.cpp:

    //
    // by deli 2009.6.11
    //
    #include <ruby.h>
    #include "SmsObject.h"
    
    static VALUE makepdu(VALUE self, VALUE arg1, VALUE arg2) {
        VALUE s;
        char pdu[512] = {0};
        char* msg = RSTRING(arg1)->ptr;
        char* number = RSTRING(arg2)->ptr;
    
        SendSms(msg, number, pdu);
        s = rb_str_new2(pdu);
    
        return s;
    }
    
    extern "C"
    void __declspec(dllexport) Init_sms() {
        VALUE myModule = rb_define_module("CDMA");
    
        VALUE myClass = 
            rb_define_class_under(myModule, "SMS", rb_cObject);
    
        int arg_count = 2;
        rb_define_method(myClass, "makepdu", RUBY_METHOD_FUNC(makepdu), arg_count);
    }

    注意: C++要加上RUBY_METHOD_FUNC, C不用加。不然就会出现类似的错误:
    error C2664: ‘rb_define_method’ : cannot convert parameter 3 from
    ‘unsigned long (unsigned long,unsigned long,unsigned long)’ to ‘unsigned long (_
    _cdecl *)(…)’
    None of the functions with this name in scope match the target type

    test.rb:

    #!/usr/bin/ruby 
    
    require 'sms'
    
    include CDMA
    
    obj = SMS.new
    pdu = obj.makepdu('什么都可以想,什么都可以不想,便觉得是个自由的人', '15338896034')
    
    puts pdu

    D:\Ruby\ruby-serial\sms>ruby test.rb
    0000021002040702c54ce225a8d008420003200000013220c27602724487ea9f7a772b079ff86276
    02724487ea9f7a772a706b079ff8627dfc4e4afcbb317a71540f53a98bb42275d00501a70801c00d
    0101

    大概就是这样。