tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/espat/espat.go (about)

     1  // Package espat implements TCP/UDP wireless communication over serial
     2  // with a separate ESP8266 or ESP32 board using the Espressif AT command set
     3  // across a UART interface.
     4  //
     5  // In order to use this driver, the ESP8266/ESP32 must be flashed with firmware
     6  // supporting the AT command set. Many ESP8266/ESP32 chips already have this firmware
     7  // installed by default. You will need to install this firmware if you have an
     8  // ESP8266 that has been flashed with NodeMCU (Lua) or Arduino firmware.
     9  //
    10  // AT Command Core repository:
    11  // https://github.com/espressif/esp32-at
    12  //
    13  // Datasheet:
    14  // https://www.espressif.com/sites/default/files/documentation/0a-esp8266ex_datasheet_en.pdf
    15  //
    16  // AT command set:
    17  // https://www.espressif.com/sites/default/files/documentation/4a-esp8266_at_instruction_set_en.pdf
    18  //
    19  // 02/2023    sfeldma@gmail.com    Heavily modified to use netdev interface
    20  
    21  package espat // import "tinygo.org/x/drivers/espat"
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"machine"
    27  	"net"
    28  	"net/netip"
    29  	"strconv"
    30  	"strings"
    31  	"sync"
    32  	"time"
    33  
    34  	"tinygo.org/x/drivers/netdev"
    35  	"tinygo.org/x/drivers/netlink"
    36  )
    37  
    38  type Config struct {
    39  	// UART config
    40  	Uart *machine.UART
    41  	Tx   machine.Pin
    42  	Rx   machine.Pin
    43  }
    44  
    45  type socket struct {
    46  	inUse    bool
    47  	protocol int
    48  	laddr    netip.AddrPort
    49  }
    50  
    51  type Device struct {
    52  	cfg  *Config
    53  	uart *machine.UART
    54  	// command responses that come back from the ESP8266/ESP32
    55  	response []byte
    56  	// data received from a TCP/UDP connection forwarded by the ESP8266/ESP32
    57  	data   []byte
    58  	socket socket
    59  	mu     sync.Mutex
    60  }
    61  
    62  func NewDevice(cfg *Config) *Device {
    63  	return &Device{
    64  		cfg:      cfg,
    65  		response: make([]byte, 1500),
    66  		data:     make([]byte, 0, 1500),
    67  	}
    68  }
    69  
    70  func (d *Device) NetConnect(params *netlink.ConnectParams) error {
    71  
    72  	if len(params.Ssid) == 0 {
    73  		return netlink.ErrMissingSSID
    74  	}
    75  
    76  	d.uart = d.cfg.Uart
    77  	d.uart.Configure(machine.UARTConfig{TX: d.cfg.Tx, RX: d.cfg.Rx})
    78  
    79  	// Connect to ESP8266/ESP32
    80  	fmt.Printf("Connecting to device...")
    81  
    82  	for i := 0; i < 5; i++ {
    83  		if d.Connected() {
    84  			break
    85  		}
    86  		time.Sleep(1 * time.Second)
    87  	}
    88  
    89  	if !d.Connected() {
    90  		fmt.Printf("FAILED\r\n")
    91  		return netlink.ErrConnectFailed
    92  	}
    93  
    94  	fmt.Printf("CONNECTED\r\n")
    95  
    96  	// Connect to Wifi AP
    97  	fmt.Printf("Connecting to Wifi SSID '%s'...", params.Ssid)
    98  
    99  	d.SetWifiMode(WifiModeClient)
   100  
   101  	err := d.ConnectToAP(params.Ssid, params.Passphrase, 10 /* secs */)
   102  	if err != nil {
   103  		fmt.Printf("FAILED\r\n")
   104  		return err
   105  	}
   106  
   107  	fmt.Printf("CONNECTED\r\n")
   108  
   109  	ip, err := d.Addr()
   110  	if err != nil {
   111  		return err
   112  	}
   113  	fmt.Printf("DHCP-assigned IP: %s\r\n", ip)
   114  	fmt.Printf("\r\n")
   115  
   116  	return nil
   117  }
   118  
   119  func (d *Device) NetDisconnect() {
   120  	d.DisconnectFromAP()
   121  	fmt.Printf("\r\nDisconnected from Wifi\r\n\r\n")
   122  }
   123  
   124  func (d *Device) NetNotify(cb func(netlink.Event)) {
   125  	// Not supported
   126  }
   127  
   128  func (d *Device) GetHostByName(name string) (netip.Addr, error) {
   129  	ip, err := d.GetDNS(name)
   130  	if err != nil {
   131  		return netip.Addr{}, err
   132  	}
   133  	return netip.ParseAddr(ip)
   134  }
   135  
   136  func (d *Device) GetHardwareAddr() (net.HardwareAddr, error) {
   137  	return net.HardwareAddr{}, netlink.ErrNotSupported
   138  }
   139  
   140  func (d *Device) Addr() (netip.Addr, error) {
   141  	resp, err := d.GetClientIP()
   142  	if err != nil {
   143  		return netip.Addr{}, err
   144  	}
   145  	prefix := "+CIPSTA:ip:"
   146  	for _, line := range strings.Split(resp, "\n") {
   147  		if ok := strings.HasPrefix(line, prefix); ok {
   148  			ip := line[len(prefix)+1 : len(line)-2]
   149  			return netip.ParseAddr(ip)
   150  		}
   151  	}
   152  	return netip.Addr{}, fmt.Errorf("Error getting IP address")
   153  }
   154  
   155  func (d *Device) Socket(domain int, stype int, protocol int) (int, error) {
   156  
   157  	switch domain {
   158  	case netdev.AF_INET:
   159  	default:
   160  		return -1, netdev.ErrFamilyNotSupported
   161  	}
   162  
   163  	switch {
   164  	case protocol == netdev.IPPROTO_TCP && stype == netdev.SOCK_STREAM:
   165  	case protocol == netdev.IPPROTO_TLS && stype == netdev.SOCK_STREAM:
   166  	case protocol == netdev.IPPROTO_UDP && stype == netdev.SOCK_DGRAM:
   167  	default:
   168  		return -1, netdev.ErrProtocolNotSupported
   169  	}
   170  
   171  	// Only supporting single connection mode, so only one socket at a time
   172  	if d.socket.inUse {
   173  		return -1, netdev.ErrNoMoreSockets
   174  	}
   175  	d.socket.inUse = true
   176  	d.socket.protocol = protocol
   177  
   178  	return 0, nil
   179  }
   180  
   181  func (d *Device) Bind(sockfd int, ip netip.AddrPort) error {
   182  	d.socket.laddr = ip
   183  	return nil
   184  }
   185  
   186  func (d *Device) Connect(sockfd int, host string, ip netip.AddrPort) error {
   187  	var err error
   188  	var addr = ip.Addr().String()
   189  	var rport = strconv.Itoa(int(ip.Port()))
   190  	var lport = strconv.Itoa(int(d.socket.laddr.Port()))
   191  
   192  	switch d.socket.protocol {
   193  	case netdev.IPPROTO_TCP:
   194  		err = d.ConnectTCPSocket(addr, rport)
   195  	case netdev.IPPROTO_UDP:
   196  		err = d.ConnectUDPSocket(addr, rport, lport)
   197  	case netdev.IPPROTO_TLS:
   198  		err = d.ConnectSSLSocket(host, rport)
   199  	}
   200  
   201  	if err != nil {
   202  		if host == "" {
   203  			return fmt.Errorf("Connect to %s timed out", ip)
   204  		} else {
   205  			return fmt.Errorf("Connect to %s:%d timed out", host, ip.Port())
   206  		}
   207  	}
   208  
   209  	return nil
   210  }
   211  
   212  func (d *Device) Listen(sockfd int, backlog int) error {
   213  	switch d.socket.protocol {
   214  	case netdev.IPPROTO_UDP:
   215  	default:
   216  		return netdev.ErrProtocolNotSupported
   217  	}
   218  	return nil
   219  }
   220  
   221  func (d *Device) Accept(sockfd int) (int, netip.AddrPort, error) {
   222  	return -1, netip.AddrPort{}, netdev.ErrNotSupported
   223  }
   224  
   225  func (d *Device) sendChunk(sockfd int, buf []byte, deadline time.Time) (int, error) {
   226  	// Check if we've timed out
   227  	if !deadline.IsZero() {
   228  		if time.Now().After(deadline) {
   229  			return -1, netdev.ErrTimeout
   230  		}
   231  	}
   232  	err := d.StartSocketSend(len(buf))
   233  	if err != nil {
   234  		return -1, err
   235  	}
   236  	n, err := d.Write(buf)
   237  	if err != nil {
   238  		return -1, err
   239  	}
   240  	_, err = d.Response(1000)
   241  	if err != nil {
   242  		return -1, err
   243  	}
   244  	return n, err
   245  }
   246  
   247  func (d *Device) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
   248  
   249  	d.mu.Lock()
   250  	defer d.mu.Unlock()
   251  
   252  	// Break large bufs into chunks so we don't overrun the hw queue
   253  
   254  	chunkSize := 1436
   255  	for i := 0; i < len(buf); i += chunkSize {
   256  		end := i + chunkSize
   257  		if end > len(buf) {
   258  			end = len(buf)
   259  		}
   260  		_, err := d.sendChunk(sockfd, buf[i:end], deadline)
   261  		if err != nil {
   262  			return -1, err
   263  		}
   264  	}
   265  
   266  	return len(buf), nil
   267  }
   268  
   269  func (d *Device) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
   270  
   271  	d.mu.Lock()
   272  	defer d.mu.Unlock()
   273  
   274  	var length = len(buf)
   275  
   276  	// Limit length read size to chunk large read requests
   277  	if length > 1436 {
   278  		length = 1436
   279  	}
   280  
   281  	for {
   282  		// Check if we've timed out
   283  		if !deadline.IsZero() {
   284  			if time.Now().After(deadline) {
   285  				return -1, netdev.ErrTimeout
   286  			}
   287  		}
   288  
   289  		n, err := d.ReadSocket(buf[:length])
   290  		if err != nil {
   291  			return -1, err
   292  		}
   293  		if n == 0 {
   294  			d.mu.Unlock()
   295  			time.Sleep(100 * time.Millisecond)
   296  			d.mu.Lock()
   297  			continue
   298  		}
   299  
   300  		return n, nil
   301  	}
   302  }
   303  
   304  func (d *Device) Close(sockfd int) error {
   305  	d.mu.Lock()
   306  	defer d.mu.Unlock()
   307  
   308  	d.socket.inUse = false
   309  	return d.DisconnectSocket()
   310  }
   311  
   312  func (d *Device) SetSockOpt(sockfd int, level int, opt int, value interface{}) error {
   313  	return netdev.ErrNotSupported
   314  }
   315  
   316  // Connected checks if there is communication with the ESP8266/ESP32.
   317  func (d *Device) Connected() bool {
   318  	d.Execute(Test)
   319  
   320  	// handle response here, should include "OK"
   321  	_, err := d.Response(1000)
   322  	if err != nil {
   323  		return false
   324  	}
   325  	return true
   326  }
   327  
   328  // Write raw bytes to the UART.
   329  func (d *Device) Write(b []byte) (n int, err error) {
   330  	return d.uart.Write(b)
   331  }
   332  
   333  // Read raw bytes from the UART.
   334  func (d *Device) Read(b []byte) (n int, err error) {
   335  	return d.uart.Read(b)
   336  }
   337  
   338  // how long in milliseconds to pause after sending AT commands
   339  const pause = 300
   340  
   341  // Execute sends an AT command to the ESP8266/ESP32.
   342  func (d Device) Execute(cmd string) error {
   343  	_, err := d.Write([]byte("AT" + cmd + "\r\n"))
   344  	return err
   345  }
   346  
   347  // Query sends an AT command to the ESP8266/ESP32 that returns the
   348  // current value for some configuration parameter.
   349  func (d Device) Query(cmd string) (string, error) {
   350  	_, err := d.Write([]byte("AT" + cmd + "?\r\n"))
   351  	return "", err
   352  }
   353  
   354  // Set sends an AT command with params to the ESP8266/ESP32 for a
   355  // configuration value to be set.
   356  func (d Device) Set(cmd, params string) error {
   357  	_, err := d.Write([]byte("AT" + cmd + "=" + params + "\r\n"))
   358  	return err
   359  }
   360  
   361  // Version returns the ESP8266/ESP32 firmware version info.
   362  func (d Device) Version() []byte {
   363  	d.Execute(Version)
   364  	r, err := d.Response(2000)
   365  	if err != nil {
   366  		//return []byte("unknown")
   367  		return []byte(err.Error())
   368  	}
   369  	return r
   370  }
   371  
   372  // Echo sets the ESP8266/ESP32 echo setting.
   373  func (d Device) Echo(set bool) {
   374  	if set {
   375  		d.Execute(EchoConfigOn)
   376  	} else {
   377  		d.Execute(EchoConfigOff)
   378  	}
   379  	// TODO: check for success
   380  	d.Response(100)
   381  }
   382  
   383  // Reset restarts the ESP8266/ESP32 firmware. Due to how the baud rate changes,
   384  // this messes up communication with the ESP8266/ESP32 module. So make sure you know
   385  // what you are doing when you call this.
   386  func (d Device) Reset() {
   387  	d.Execute(Restart)
   388  	d.Response(100)
   389  }
   390  
   391  // ReadSocket returns the data that has already been read in from the responses.
   392  func (d *Device) ReadSocket(b []byte) (n int, err error) {
   393  	// make sure no data in buffer
   394  	d.Response(300)
   395  
   396  	count := len(b)
   397  	if len(b) >= len(d.data) {
   398  		// copy it all, then clear socket data
   399  		count = len(d.data)
   400  		copy(b, d.data[:count])
   401  		d.data = d.data[:0]
   402  	} else {
   403  		// copy all we can, then keep the remaining socket data around
   404  		copy(b, d.data[:count])
   405  		copy(d.data, d.data[count:])
   406  		d.data = d.data[:len(d.data)-count]
   407  	}
   408  
   409  	return count, nil
   410  }
   411  
   412  // Response gets the next response bytes from the ESP8266/ESP32.
   413  // The call will retry for up to timeout milliseconds before returning nothing.
   414  func (d *Device) Response(timeout int) ([]byte, error) {
   415  	// read data
   416  	var size int
   417  	var start, end int
   418  	pause := 100 // pause to wait for 100 ms
   419  	retries := timeout / pause
   420  
   421  	for {
   422  		size = d.uart.Buffered()
   423  
   424  		if size > 0 {
   425  			end += size
   426  			d.uart.Read(d.response[start:end])
   427  
   428  			// if "+IPD" then read socket data
   429  			if strings.Contains(string(d.response[:end]), "+IPD") {
   430  				// handle socket data
   431  				return nil, d.parseIPD(end)
   432  			}
   433  
   434  			// if "OK" then the command worked
   435  			if strings.Contains(string(d.response[:end]), "OK") {
   436  				return d.response[start:end], nil
   437  			}
   438  
   439  			// if "Error" then the command failed
   440  			if strings.Contains(string(d.response[:end]), "ERROR") {
   441  				return d.response[start:end], errors.New("response error:" + string(d.response[start:end]))
   442  			}
   443  
   444  			// if anything else, then keep reading data in?
   445  			start = end
   446  		}
   447  
   448  		// wait longer?
   449  		retries--
   450  		if retries == 0 {
   451  			return nil, errors.New("response timeout error:" + string(d.response[start:end]))
   452  		}
   453  
   454  		time.Sleep(time.Duration(pause) * time.Millisecond)
   455  	}
   456  }
   457  
   458  func (d *Device) parseIPD(end int) error {
   459  	// find the "+IPD," to get length
   460  	s := strings.Index(string(d.response[:end]), "+IPD,")
   461  
   462  	// find the ":"
   463  	e := strings.Index(string(d.response[:end]), ":")
   464  
   465  	// find the data length
   466  	val := string(d.response[s+5 : e])
   467  
   468  	// TODO: verify count
   469  	v, err := strconv.Atoi(val)
   470  	if err != nil {
   471  		// not expected data here. what to do?
   472  		return err
   473  	}
   474  
   475  	// load up the socket data
   476  	//d.data = append(d.data, d.response[e+1:end]...)
   477  	d.data = append(d.data, d.response[e+1:e+1+v]...)
   478  	return nil
   479  }
   480  
   481  // IsSocketDataAvailable returns of there is socket data available
   482  func (d *Device) IsSocketDataAvailable() bool {
   483  	return len(d.data) > 0 || d.uart.Buffered() > 0
   484  }