package at import ( "fmt" "log" "strings" "time" "go.bug.st/serial" ) // Some constants const ( ReadTimeout = time.Second InputBufSize = 128 ) // Command types type CmdType byte // Command types base on request/answer semantic: const ( CmdTest CmdType = iota // AT\r\n // OK CmdCheck // AT+\r\n // OK CmdGet // AT+\r\n // +: CmdQuestion // AT+?\r\n // +: ) type atPort struct { baudrate int portName string port serial.Port inputBuf []byte } type Port interface { Connect() error Disconnect() error Request(cmdType CmdType, cmd string) (string, error) GetName() string IsConnected() bool GetSerialPort() serial.Port // For extra need } func New(portName string, baudrate int) Port { return &atPort{ portName: portName, baudrate: baudrate, inputBuf: make([]byte, InputBufSize), } } func (p *atPort) Connect() error { log.Println("Connecting to", p.portName, "...") s, err := serial.Open(p.portName, &serial.Mode{BaudRate: p.baudrate}) if err != nil { return fmt.Errorf("open port: %w", err) } // s.Close() There is no open f // s.Open() p.port = s p.port.SetReadTimeout(ReadTimeout) p.port.ResetInputBuffer() p.port.ResetOutputBuffer() return nil } func (p *atPort) Disconnect() error { defer func() { p.port = nil }() if err := p.port.Close(); err != nil { return fmt.Errorf("close port: %w", err) } return nil } // Low level write/read function func (p *atPort) makeReq(msg string) (string, error) { // Write p.port.ResetInputBuffer() log.Println("Write...") // DEBUG if written, err := p.port.Write([]byte(msg)); err != nil { return "", fmt.Errorf("serial port write: %w", err) } else { log.Println("Written:", written) // DEBUG } // Read log.Println("Read...") // DEBUG readLen, err := p.port.Read(p.inputBuf) if err != nil { return "", fmt.Errorf("port read: %w", err) } log.Println("Read: ", readLen, string(p.inputBuf[:readLen])) // DEBUG return string(p.inputBuf[:readLen]), nil } func (p *atPort) Request(cmdType CmdType, cmd string) (string, error) { msg := "AT" // Make command // By default it just will be AT check cmd switch cmdType { case CmdGet, CmdCheck: msg += cmd case CmdQuestion: msg += cmd + "?" } msg += "\r\n" rawResp, err := p.makeReq(msg) log.Println("Got") if err != nil { return "", fmt.Errorf("make at request: %w", err) } if len(rawResp) == 0 { return "", fmt.Errorf("read nothing") } resp := strings.Split(rawResp, "\n") switch cmdType { case CmdTest, CmdCheck: // Check and test cmds do not suppose anything but OK if len(resp[1]) >= 2 && resp[1][:2] == "OK" { return "", nil } return "", fmt.Errorf("connection lost") case CmdGet, CmdQuestion: checkL := len(cmd) + 1 if len(resp[1]) >= checkL && resp[1][:checkL] != "+"+cmd { return "", fmt.Errorf("connetion lost") } return strings.Split(resp[1], ":")[1], nil } return "", fmt.Errorf("undefined command type") } func (p *atPort) GetName() string { return p.portName } func (p *atPort) IsConnected() bool { return p.port != nil } func (p *atPort) GetSerialPort() serial.Port { return p.port }