golang实现AT命令的发送(Windows版)

问题的提出

由于golang的微线程(goroutines)的特征,实现同时交互进行的任务变得自然而容易。一直想实现一个能对手机进行自动发送AT的程序,而实现相应的自动化测试。现在用golang来偿试一下。

问题所依赖的库

使用golang来实现的对串口的操作的库从网上找到了一个,就不用重新发明轮子啦。https://github.com/huin/goserial
在Windows下使用的文件如下:
 

  serial.go

/*
Goserial is a simple go package to allow you to read and write from
the serial port as a stream of bytes.

It aims to have the same API on all platforms, including windows.  As
an added bonus, the windows package does not use cgo, so you can cross
compile for windows from another platform.  Unfortunately goinstall
does not currently let you cross compile so you will have to do it
manually:

GOOS=windows make clean install

Currently there is very little in the way of configurability.  You can
set the baud rate.  Then you can Read(), Write(), or Close() the
connection.  Read() will block until at least one byte is returned.
Write is the same.  There is currently no exposed way to set the
timeouts, though patches are welcome.

Currently ports are opened with 8 data bits, 1 stop bit, no parity, no hardware
flow control, and no software flow control by default.  This works fine for
many real devices and many faux serial devices including usb-to-serial
converters and bluetooth serial ports.

You may Read() and Write() simulantiously on the same connection (from
different goroutines).

Example usage:

  package main

  import (
        "github.com/tarm/goserial"
        "log"
  )

  func main() {
        c := &serial.Config{Name: "COM5", Baud: 115200}
        s, err := serial.OpenPort(c)
        if err != nil {
                log.Fatal(err)
        }

        n, err := s.Write([]byte("test"))
        if err != nil {
                log.Fatal(err)
        }

        buf := make([]byte, 128)
        n, err = s.Read(buf)
        if err != nil {
                log.Fatal(err)
        }
        log.Print("%q", buf[:n])
  }
*/

package goserial

import (
    "errors"
    "io"
)

var (
    ErrConfigStopBits = errors.New("goserial config: bad number of stop bits")
    ErrConfigByteSize = errors.New("goserial config: bad byte size")
    ErrConfigParity   = errors.New("goserial config: bad parity")
)

type ParityMode byte

const (
    ParityNone = ParityMode(iota)
    ParityEven
    ParityOdd
)

type ByteSize byte

const (
    Byte8 = ByteSize(iota)
    Byte5
    Byte6
    Byte7
)

type StopBits byte

const (
    StopBits1 = StopBits(iota)
    StopBits2
)

// Config contains the information needed to open a serial port.
//
// Currently few options are implemented, but more may be added in the
// future (patches welcome), so it is recommended that you create a
// new config addressing the fields by name rather than by order.
//
// For example:
//
//    c0 := &serial.Config{Name: "COM45", Baud: 115200}
// or
//    c1 := new(serial.Config)
//    c1.Name = "/dev/tty.usbserial"
//    c1.Baud = 115200
//
type Config struct {
    Name string
    Baud int

    Size     ByteSize
    Parity   ParityMode
    StopBits StopBits

    // RTSFlowControl bool
    // DTRFlowControl bool
    // XONFlowControl bool

    CRLFTranslate bool // Ignored on Windows.
    // TimeoutStuff int
}

func (c *Config) check() error {
    switch c.Size {
    case Byte5, Byte6, Byte7, Byte8:
    default:
        return ErrConfigByteSize
    }

    switch c.StopBits {
    case StopBits1, StopBits2:
    default:
        return ErrConfigParity
    }

    switch c.Parity {
    case ParityNone, ParityEven, ParityOdd:
    default:
        return ErrConfigParity
    }

    return nil
}

// OpenPort opens a serial port with the specified configuration
func OpenPort(c *Config) (io.ReadWriteCloser, error) {
    if err := c.check(); err != nil {
        return nil, err
    }

    return openPort(c.Name, c)
}

// func Flush()

// func SendBreak()

// func RegisterBreakHandler(func())
 

serial_windows.go

// +build windows

package goserial

import (
    "fmt"
    "io"
    "os"
    "sync"
    "syscall"
    "unsafe"
)

type serialPort struct {
    f  *os.File
    fd syscall.Handle
    rl sync.Mutex
    wl sync.Mutex
    ro *syscall.Overlapped
    wo *syscall.Overlapped
}

type structDCB struct {
    DCBlength, BaudRate                            uint32
    flags                                          [4]byte
    wReserved, XonLim, XoffLim                     uint16
    ByteSize, Parity, StopBits                     byte
    XonChar, XoffChar, ErrorChar, EofChar, EvtChar byte
    wReserved1                                     uint16
}

type structTimeouts struct {
    ReadIntervalTimeout         uint32
    ReadTotalTimeoutMultiplier  uint32
    ReadTotalTimeoutConstant    uint32
    WriteTotalTimeoutMultiplier uint32
    WriteTotalTimeoutConstant   uint32
}

func openPort(name string, c *Config) (rwc io.ReadWriteCloser, err error) {
    if len(name) > 0 && name[0]!= '\\' {
        name = "\\\\.\\"+ name
    }

    h, err := syscall.CreateFile(syscall.StringToUTF16Ptr(name),
        syscall.GENERIC_READ|syscall.GENERIC_WRITE,
        0,
        nil,
        syscall.OPEN_EXISTING,
        syscall.FILE_ATTRIBUTE_NORMAL|syscall.FILE_FLAG_OVERLAPPED,
        0)
    if err != nil {
        return nil, err
    }
    f := os.NewFile(uintptr(h), name)
    defer func() {
        if err != nil {
            f.Close()
        }
    }()

    if err = setCommState(h, c); err != nil {
        return
    }
    if err = setupComm(h, 64, 64); err != nil {
        return
    }
    if err = setCommTimeouts(h); err != nil {
        return
    }
    if err = setCommMask(h); err != nil {
        return
    }

    ro, err := newOverlapped()
    if err != nil {
        return
    }
    wo, err := newOverlapped()
    if err != nil {
        return
    }
    port :=new(serialPort)
    port.f = f
    port.fd = h
    port.ro = ro
    port.wo = wo

    return port, nil
}

func (p *serialPort) Close() error {
    return p.f.Close()
}

func (p *serialPort) Write(buf []byte) (int, error) {
    p.wl.Lock()
    defer p.wl.Unlock()

    if err := resetEvent(p.wo.HEvent); err != nil {
        return 0, err
    }
    var n uint32
    err := syscall.WriteFile(p.fd, buf,&n, p.wo)
    if err != nil && err != syscall.ERROR_IO_PENDING {
        return int(n), err
    }
    return getOverlappedResult(p.fd, p.wo)
}

func (p *serialPort) Read(buf []byte) (int, error) {
    if p == nil || p.f == nil {
        return 0, fmt.Errorf("Invalid port on read %v %v", p, p.f)
    }

    p.rl.Lock()
    defer p.rl.Unlock()

    if err := resetEvent(p.ro.HEvent); err != nil {
        return 0, err
    }
    var done uint32
    err := syscall.ReadFile(p.fd, buf,&done, p.ro)
    if err != nil && err != syscall.ERROR_IO_PENDING {
        return int(done), err
    }
    return getOverlappedResult(p.fd, p.ro)
}

var (
    nSetCommState,
    nSetCommTimeouts,
    nSetCommMask,
    nSetupComm,
    nGetOverlappedResult,
    nCreateEvent,
    nResetEvent uintptr
)

func init() {
    k32, err := syscall.LoadLibrary("kernel32.dll")
    if err != nil {
        panic("LoadLibrary " + err.Error())
    }
    defer syscall.FreeLibrary(k32)

    nSetCommState = getProcAddr(k32, "SetCommState")
    nSetCommTimeouts = getProcAddr(k32, "SetCommTimeouts")
    nSetCommMask = getProcAddr(k32, "SetCommMask")
    nSetupComm = getProcAddr(k32, "SetupComm")
    nGetOverlappedResult = getProcAddr(k32, "GetOverlappedResult")
    nCreateEvent = getProcAddr(k32, "CreateEventW")
    nResetEvent = getProcAddr(k32, "ResetEvent")
}

func getProcAddr(lib syscall.Handle, name string) uintptr {
    addr, err := syscall.GetProcAddress(lib, name)
    if err != nil {
        panic(name + " " + err.Error())
    }
    return addr
}

func setCommState(h syscall.Handle, c *Config) error {
    var params structDCB
    params.DCBlength = uint32(unsafe.Sizeof(params))

    params.flags[0] = 0x01  // fBinary
    params.flags[0] |= 0x10 // Assert DSR

    params.BaudRate = uint32(c.Baud)

    // Select byte size.
    switch c.Size {
    case Byte5:
        params.ByteSize = 5
    case Byte6:
        params.ByteSize = 6
    case Byte7:
        params.ByteSize = 7
    case Byte8:
        params.ByteSize = 8
    default:
        panic(c.Size)
    }

    // Select parity mode.
    switch c.Parity {
    case ParityNone:
        params.Parity = 0
    case ParityEven:
        params.Parity = 2
    case ParityOdd:
        params.Parity = 1
    default:
        panic(c.Parity)
    }

    // Selet stop bits.
    switch c.StopBits {
    case StopBits1:
        params.StopBits = 0
    case StopBits2:
        params.StopBits = 2
    default:
        panic(c.StopBits)
    }

    r, _, err := syscall.Syscall(nSetCommState,2, uintptr(h), uintptr(unsafe.Pointer(&params)),0)
    if r == 0 {
        return err
    }
    return nil
}

func setCommTimeouts(h syscall.Handle) error {
    var timeouts structTimeouts
    const MAXDWORD = 1<<32- 1
    timeouts.ReadIntervalTimeout = MAXDWORD
    timeouts.ReadTotalTimeoutMultiplier = MAXDWORD
    timeouts.ReadTotalTimeoutConstant = MAXDWORD- 1

    /* From http://msdn.microsoft.com/en-us/library/aa363190(v=VS.85).aspx

         For blocking I/O see below:

         Remarks:

         If an application sets ReadIntervalTimeout and
         ReadTotalTimeoutMultiplier to MAXDWORD and sets
         ReadTotalTimeoutConstant to a value greater than zero and
         less than MAXDWORD, one of the following occurs when the
         ReadFile function is called:

         If there are any bytes in the input buffer, ReadFile returns
               immediately with the bytes in the buffer.

         If there are no bytes in the input buffer, ReadFile waits
                   until a byte arrives and then returns immediately.

         If no bytes arrive within the time specified by
               ReadTotalTimeoutConstant, ReadFile times out.
    */


    r, _, err := syscall.Syscall(nSetCommTimeouts,2, uintptr(h), uintptr(unsafe.Pointer(&timeouts)),0)
    if r == 0 {
        return err
    }
    return nil
}

func setupComm(h syscall.Handle, in, out int) error {
    r, _, err := syscall.Syscall(nSetupComm,3, uintptr(h), uintptr(in), uintptr(out))
    if r == 0 {
        return err
    }
    return nil
}

func setCommMask(h syscall.Handle) error {
    const EV_RXCHAR = 0x0001
    r, _, err := syscall.Syscall(nSetCommMask,2, uintptr(h), EV_RXCHAR, 0)
    if r == 0 {
        return err
    }
    return nil
}

func resetEvent(h syscall.Handle) error {
    r, _, err := syscall.Syscall(nResetEvent,1, uintptr(h), 0,0)
    if r == 0 {
        return err
    }
    return nil
}

func newOverlapped() (*syscall.Overlapped, error) {
    var overlapped syscall.Overlapped
    r, _, err := syscall.Syscall6(nCreateEvent,4, 0, 1, 0, 0,0, 0)
    if r == 0 {
        return nil, err
    }
    overlapped.HEvent = syscall.Handle(r)
    return &overlapped, nil
}

func getOverlappedResult(h syscall.Handle, overlapped *syscall.Overlapped) (int, error) {
    var n int
    r, _, err := syscall.Syscall6(nGetOverlappedResult,4,
        uintptr(h),
        uintptr(unsafe.Pointer(overlapped)),
        uintptr(unsafe.Pointer(&n)), 1, 0, 0)
    if r == 0 {
        return n, err
    }

    return n, nil
}
 

问题的解

为了能在脚本中使用,决定使之能对任意串口,执行单条AT命令。

atsender.go

package main

import (
        "./goserial"
        "log"
        "io"
        "fmt"
        "os"
        "strconv"
        "strings"
)

func main() {
        comname :="COM51"
        atcommand :="at\r\n"
        baud :=115200

        if len(os.Args) > 1 {
            comname = os.Args[1]
        }
        if len(os.Args) > 2 {
            atcommand = checkAtformat(os.Args[2])
            baud = 115200
        }
        if len(os.Args) > 3 {
            baud,_ = strconv.Atoi(os.Args[3])
        }
        fmt.Printf("Com: %s Baud: %d AT: %s ",comname,baud ,atcommand)
        s,_ := openCom(comname ,baud)//"COM51",115200
        _, err := s.Write([]byte(atcommand))
        if err != nil {
                log.Fatal(err)
        }
        receiveCom(s)
        s.Close()
}

func openCom(name string,baud int)(io.ReadWriteCloser, error){
        c :=&goserial.Config{Name: name, Baud: baud}
        s, err := goserial.OpenPort(c)
        if err != nil {
                log.Fatal(err)
        }
        return s,nil
}

func checkAtformat(in string)(string){
    if strings.Contains(in,"\r\n") {
        return in
    }else {
        return in + "\r\n"
    }
    return ""
}

func receiveCom(s io.ReadWriteCloser){
    buf := make([]byte,128)
    for{
           n, err := s.Read(buf)
           if err != nil {
               log.Fatal(err)
           }
        strResult := string(buf[:n])
           fmt.Println(strResult)
        if strings.Contains(strResult,"OK\r\n"){
            break
        }
    }
}
 

辅助工具

由于AT命令在执行时,有时会用时较长,须要有一个等待命令,也用golang实现一下吧。总之是练习而已。
 

sleep.go

 
// sleep.go
package main

import (
    "fmt"
    "os"
    "strconv"
    "time"
)

func main() {
    if len(os.Args) > 1 {
        count, _ := strconv.Atoi(os.Args[1])
//        t0 := time.Now()
        go promtString("*",count)
        time.Sleep(time.Duration(count) * time.Second)
//        t1 := time.Now()
//        fmt.Printf("\nThe call took %v to run.\n", t1.Sub(t0))
        fmt.Println()
    } else {
        fmt.Println("Sleep count Seconds.\nUsage: sleep [count]")
    }
}

func promtString(str string, count int) {
    for i := 0; i< count; i++ {
        fmt.Print(str)
        time.Sleep(time.Second)
    }
}
 

应用脚本

有了这两个命令行程序,就可以编写自动化脚本啦。
 
echo off
for %%i in (1,2,3,4)do call :Call_Case

:Call_Case
atsender COM51 atd10086;
sleep 10
atsender COM51 at+chup
sleep 5
goto :eof

本文来自:CSDN博客

感谢作者:huangliujing

查看原文:golang实现AT命令的发送(Windows版)

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。