// Package wifinina implements TCP wireless communication over SPI with an // attached separate ESP32 SoC using the Arduino WiFiNINA protocol. // // In order to use this driver, the ESP32 must be flashed with specific // firmware from Arduino. For more information: // https://github.com/arduino/nina-fw // // 12/2022 sfeldma@gmail.com Heavily modified to use netdev interface package wifinina // import "tinygo.org/x/drivers/wifinina" import ( "encoding/binary" "encoding/hex" "fmt" "io" "machine" "math/bits" "net" "net/netip" "sync" "time" "tinygo.org/x/drivers" "tinygo.org/x/drivers/netdev" "tinygo.org/x/drivers/netlink" ) var _debug debug = debugBasic //var _debug debug = debugBasic | debugNetdev //var _debug debug = debugBasic | debugNetdev | debugCmd //var _debug debug = debugBasic | debugNetdev | debugCmd | debugDetail var ( driverName = "Tinygo ESP32 Wifi network device driver (WiFiNINA)" ) const ( maxNetworks = 10 statusNoShield connectionStatus = 255 statusIdle connectionStatus = 0 statusNoSSIDAvail connectionStatus = 1 statusScanCompleted connectionStatus = 2 statusConnected connectionStatus = 3 statusConnectFailed connectionStatus = 4 statusConnectionLost connectionStatus = 5 statusDisconnected connectionStatus = 6 statusAPListening connectionStatus = 7 statusAPConnected connectionStatus = 8 statusAPFailed connectionStatus = 9 encTypeTKIP encryptionType = 2 encTypeCCMP encryptionType = 4 encTypeWEP encryptionType = 5 encTypeNone encryptionType = 7 encTypeAuto encryptionType = 8 tcpStateClosed = 0 tcpStateListen = 1 tcpStateSynSent = 2 tcpStateSynRcvd = 3 tcpStateEstablished = 4 tcpStateFinWait1 = 5 tcpStateFinWait2 = 6 tcpStateCloseWait = 7 tcpStateClosing = 8 tcpStateLastACK = 9 tcpStateTimeWait = 10 flagCmd = 0 flagReply = 1 << 7 flagData = 0x40 cmdStart = 0xE0 cmdEnd = 0xEE cmdErr = 0xEF dummyData = 0xFF cmdSetNet = 0x10 cmdSetPassphrase = 0x11 cmdSetKey = 0x12 cmdSetIPConfig = 0x14 cmdSetDNSConfig = 0x15 cmdSetHostname = 0x16 cmdSetPowerMode = 0x17 cmdSetAPNet = 0x18 cmdSetAPPassphrase = 0x19 cmdSetDebug = 0x1A cmdGetTemperature = 0x1B cmdGetReasonCode = 0x1F cmdGetConnStatus = 0x20 cmdGetIPAddr = 0x21 cmdGetMACAddr = 0x22 cmdGetCurrSSID = 0x23 cmdGetCurrBSSID = 0x24 cmdGetCurrRSSI = 0x25 cmdGetCurrEncrType = 0x26 cmdScanNetworks = 0x27 cmdStartServerTCP = 0x28 cmdGetStateTCP = 0x29 cmdDataSentTCP = 0x2A cmdAvailDataTCP = 0x2B cmdGetDataTCP = 0x2C cmdStartClientTCP = 0x2D cmdStopClientTCP = 0x2E cmdGetClientStateTCP = 0x2F cmdDisconnect = 0x30 cmdGetIdxRSSI = 0x32 cmdGetIdxEncrType = 0x33 cmdReqHostByName = 0x34 cmdGetHostByName = 0x35 cmdStartScanNetworks = 0x36 cmdGetFwVersion = 0x37 cmdSendDataUDP = 0x39 cmdGetRemoteData = 0x3A cmdGetTime = 0x3B cmdGetIdxBSSID = 0x3C cmdGetIdxChannel = 0x3D cmdPing = 0x3E cmdGetSocket = 0x3F // All commands with DATA_FLAG 0x4x send a 16bit Len cmdSendDataTCP = 0x44 cmdGetDatabufTCP = 0x45 cmdInsertDataBuf = 0x46 // Regular format commands cmdSetPinMode = 0x50 cmdSetDigitalWrite = 0x51 cmdSetAnalogWrite = 0x52 errTimeoutChipReady hwerr = 0x01 errTimeoutChipSelect hwerr = 0x02 errCheckStartCmd hwerr = 0x03 errWaitRsp hwerr = 0x04 errUnexpectedLength hwerr = 0xE0 errNoParamsReturned hwerr = 0xE1 errIncorrectSentinel hwerr = 0xE2 errCmdErrorReceived hwerr = 0xEF errNotImplemented hwerr = 0xF0 errUnknownHost hwerr = 0xF1 errSocketAlreadySet hwerr = 0xF2 errConnectionTimeout hwerr = 0xF3 errNoData hwerr = 0xF4 errDataNotWritten hwerr = 0xF5 errCheckDataError hwerr = 0xF6 errBufferTooSmall hwerr = 0xF7 errNoSocketAvail hwerr = 0xFF noSocketAvail sock = 0xFF ) const ( protoModeTCP = iota protoModeUDP protoModeTLS protoModeMul ) type connectionStatus uint8 type encryptionType uint8 type sock uint8 type hwerr uint8 type Socket struct { protocol int clientConnected bool laddr netip.AddrPort // Set in Bind() raddr netip.AddrPort // Set in Connect() sock // Device socket, as returned from w.getSocket() } type Config struct { // SPI config Spi drivers.SPI Freq uint32 Sdo machine.Pin Sdi machine.Pin Sck machine.Pin // Device config Cs machine.Pin Ack machine.Pin Gpio0 machine.Pin Resetn machine.Pin // ResetIsHigh controls if the RESET signal to the processor should be // High or Low (the default). Set this to true for boards such as the // Arduino MKR 1010, where the reset signal needs to go high instead of // low. ResetIsHigh bool } type wifinina struct { cfg *Config notifyCb func(netlink.Event) mu sync.Mutex spi drivers.SPI cs machine.Pin ack machine.Pin gpio0 machine.Pin resetn machine.Pin buf [64]byte ssids [maxNetworks]string params *netlink.ConnectParams netConnected bool driverShown bool deviceShown bool spiSetup bool killWatchdog chan bool fault error sockets map[int]*Socket // keyed by sockfd } func New(cfg *Config) *wifinina { w := wifinina{ cfg: cfg, sockets: make(map[int]*Socket), killWatchdog: make(chan bool), cs: cfg.Cs, ack: cfg.Ack, gpio0: cfg.Gpio0, resetn: cfg.Resetn, } return &w } func (err hwerr) Error() string { return "[wifinina] error: 0x" + hex.EncodeToString([]byte{uint8(err)}) } func (w *wifinina) reason() string { reason := w.getReasonCode() switch reason { case 0: return "unknown failure" case 201: return "no AP found" case 202: return "auth failed" } return fmt.Sprintf("%d", reason) } func (w *wifinina) connectToAP() error { timeout := w.params.ConnectTimeout if timeout == 0 { timeout = netlink.DefaultConnectTimeout } if len(w.params.Ssid) == 0 { return netlink.ErrMissingSSID } if debugging(debugBasic) { fmt.Printf("Connecting to Wifi SSID '%s'...", w.params.Ssid) } start := time.Now() // Start the connection process w.setPassphrase(w.params.Ssid, w.params.Passphrase) // Check if we connected for { status := w.getConnectionStatus() switch status { case statusConnected: if debugging(debugBasic) { fmt.Printf("CONNECTED\r\n") } if w.notifyCb != nil { w.notifyCb(netlink.EventNetUp) } return nil case statusConnectFailed: if debugging(debugBasic) { fmt.Printf("FAILED (%s)\r\n", w.reason()) } return netlink.ErrConnectFailed } if time.Since(start) > timeout { break } time.Sleep(1 * time.Second) } if debugging(debugBasic) { fmt.Printf("FAILED (timed out)\r\n") } return netlink.ErrConnectTimeout } func (w *wifinina) startAP() error { timeout := w.params.ConnectTimeout if timeout == 0 { timeout = netlink.DefaultConnectTimeout } if len(w.params.Ssid) == 0 { return netlink.ErrMissingSSID } if debugging(debugBasic) { fmt.Printf("Starting Wifi AP as SSID '%s'...", w.params.Ssid) } start := time.Now() // Start the connection process switch { case w.params.Passphrase != "": w.setPassphraseForAP(w.params.Ssid, w.params.Passphrase) default: w.setNetworkForAP(w.params.Ssid) } // Check if we are listening for { status := w.getConnectionStatus() switch status { case statusAPListening: if debugging(debugBasic) { fmt.Printf("LISTENING\r\n") } if w.notifyCb != nil { w.notifyCb(netlink.EventNetUp) } return nil case statusAPFailed: if debugging(debugBasic) { fmt.Printf("FAILED (%s)\r\n", w.reason()) } return netlink.ErrConnectFailed } if time.Since(start) > timeout { break } time.Sleep(1 * time.Second) } if debugging(debugBasic) { fmt.Printf("FAILED (timed out)\r\n") } return netlink.ErrConnectTimeout } func (w *wifinina) netDisconnect() { w.disconnect() } func (w *wifinina) showDriver() { if w.driverShown { return } if debugging(debugBasic) { fmt.Printf("\r\n") fmt.Printf("%s\r\n\r\n", driverName) fmt.Printf("Driver version : %s\r\n", drivers.Version) } w.driverShown = true } func (w *wifinina) setupSPI() { if w.spiSetup { return } spi := machine.NINA_SPI spi.Configure(machine.SPIConfig{ Frequency: w.cfg.Freq, SDO: w.cfg.Sdo, SDI: w.cfg.Sdi, SCK: w.cfg.Sck, }) w.spi = spi w.spiSetup = true } func (w *wifinina) start() { pinUseDevice(w) w.cs.Configure(machine.PinConfig{Mode: machine.PinOutput}) w.ack.Configure(machine.PinConfig{Mode: machine.PinInput}) w.resetn.Configure(machine.PinConfig{Mode: machine.PinOutput}) w.gpio0.Configure(machine.PinConfig{Mode: machine.PinOutput}) w.gpio0.High() w.cs.High() w.resetn.Set(w.cfg.ResetIsHigh) time.Sleep(10 * time.Millisecond) w.resetn.Set(!w.cfg.ResetIsHigh) time.Sleep(750 * time.Millisecond) w.gpio0.Low() w.gpio0.Configure(machine.PinConfig{Mode: machine.PinInput}) } func (w *wifinina) stop() { w.resetn.Low() w.cs.Configure(machine.PinConfig{Mode: machine.PinInput}) } func (w *wifinina) showDevice() { if w.deviceShown { return } if debugging(debugBasic) { fmt.Printf("ESP32 firmware version : %s\r\n", w.getFwVersion()) mac := w.getMACAddr() fmt.Printf("MAC address : %s\r\n", mac.String()) fmt.Printf("\r\n") } w.deviceShown = true } func (w *wifinina) showIP() { if debugging(debugBasic) { ip, subnet, gateway := w.getIP() fmt.Printf("\r\n") fmt.Printf("DHCP-assigned IP : %s\r\n", ip) fmt.Printf("DHCP-assigned subnet : %s\r\n", subnet) fmt.Printf("DHCP-assigned gateway : %s\r\n", gateway) fmt.Printf("\r\n") } } func (w *wifinina) networkDown() bool { switch w.getConnectionStatus() { case statusConnected, statusAPListening, statusAPConnected: return false default: return true } } func (w *wifinina) watchdog() { ticker := time.NewTicker(w.params.WatchdogTimeout) for { select { case <-w.killWatchdog: return case <-ticker.C: w.mu.Lock() if w.fault != nil { if debugging(debugBasic) { fmt.Printf("Watchdog: FAULT: %s\r\n", w.fault) } w.netDisconnect() w.netConnect(true) w.fault = nil } else if w.networkDown() { if debugging(debugBasic) { fmt.Printf("Watchdog: Wifi NOT CONNECTED, trying again...\r\n") } if w.notifyCb != nil { w.notifyCb(netlink.EventNetDown) } w.netConnect(false) } w.mu.Unlock() } } } func (w *wifinina) netConnect(reset bool) error { if reset { w.start() } w.showDevice() retry: for i := 0; w.params.Retries == 0 || i < w.params.Retries; i++ { switch w.params.ConnectMode { case netlink.ConnectModeAP: if err := w.startAP(); err != nil { switch err { case netlink.ErrConnectTimeout, netlink.ErrConnectFailed: continue } return err } break retry default: if err := w.connectToAP(); err != nil { switch err { case netlink.ErrConnectTimeout, netlink.ErrConnectFailed: continue } return err } break retry } } if w.networkDown() { return netlink.ErrConnectFailed } w.showIP() return nil } func (w *wifinina) NetConnect(params *netlink.ConnectParams) error { w.mu.Lock() defer w.mu.Unlock() if w.netConnected { return netlink.ErrConnected } w.params = params w.showDriver() w.setupSPI() if err := w.netConnect(true); err != nil { return err } w.netConnected = true if w.params.WatchdogTimeout != 0 { go w.watchdog() } return nil } func (w *wifinina) NetDisconnect() { w.mu.Lock() defer w.mu.Unlock() if !w.netConnected { return } if w.params.WatchdogTimeout != 0 { w.killWatchdog <- true } w.netDisconnect() w.stop() w.netConnected = false if debugging(debugBasic) { fmt.Printf("\r\nDisconnected from Wifi SSID '%s'\r\n\r\n", w.params.Ssid) } if w.notifyCb != nil { w.notifyCb(netlink.EventNetDown) } } func (w *wifinina) NetNotify(cb func(netlink.Event)) { w.notifyCb = cb } func (w *wifinina) GetHostByName(name string) (netip.Addr, error) { if debugging(debugNetdev) { fmt.Printf("[GetHostByName] name: %s\r\n", name) } // If it's already in dotted-decimal notation, return a copy // per gethostbyname(3). if ip, err := netip.ParseAddr(name); err == nil { return ip, nil } w.mu.Lock() defer w.mu.Unlock() ip := w.getHostByName(name) if ip == "" { return netip.Addr{}, netdev.ErrHostUnknown } addr, ok := netip.AddrFromSlice([]byte(ip)) if !ok { return netip.Addr{}, netdev.ErrMalAddr } return addr, nil } func (w *wifinina) GetHardwareAddr() (net.HardwareAddr, error) { if debugging(debugNetdev) { fmt.Printf("[GetHardwareAddr]\r\n") } w.mu.Lock() defer w.mu.Unlock() return w.getMACAddr(), nil } func (w *wifinina) Addr() (netip.Addr, error) { if debugging(debugNetdev) { fmt.Printf("[GetIPAddr]\r\n") } w.mu.Lock() defer w.mu.Unlock() ip, _, _ := w.getIP() return ip, nil } // newSockfd returns the next available sockfd, or -1 if none available func (w *wifinina) newSockfd() int { if len(w.sockets) >= maxNetworks { return -1 } // Search for the next available sockfd starting at 0 for sockfd := 0; ; sockfd++ { if _, ok := w.sockets[sockfd]; !ok { return sockfd } } return -1 } // See man socket(2) for standard Berkely sockets for Socket, Bind, etc. // The driver strives to meet the function and semantics of socket(2). func (w *wifinina) Socket(domain int, stype int, protocol int) (int, error) { if debugging(debugNetdev) { fmt.Printf("[Socket] domain: %d, type: %d, protocol: %d\r\n", domain, stype, protocol) } switch domain { case netdev.AF_INET: default: return -1, netdev.ErrFamilyNotSupported } switch { case protocol == netdev.IPPROTO_TCP && stype == netdev.SOCK_STREAM: case protocol == netdev.IPPROTO_TLS && stype == netdev.SOCK_STREAM: case protocol == netdev.IPPROTO_UDP && stype == netdev.SOCK_DGRAM: default: return -1, netdev.ErrProtocolNotSupported } w.mu.Lock() defer w.mu.Unlock() sockfd := w.newSockfd() if sockfd == -1 { return -1, netdev.ErrNoMoreSockets } w.sockets[sockfd] = &Socket{ protocol: protocol, sock: noSocketAvail, } if debugging(debugNetdev) { fmt.Printf("[Socket] <-- sockfd %d\r\n", sockfd) } return sockfd, nil } func (w *wifinina) Bind(sockfd int, ip netip.AddrPort) error { if debugging(debugNetdev) { fmt.Printf("[Bind] sockfd: %d, addr: %s:%d\r\n", sockfd, ip.Addr(), ip.Port()) } w.mu.Lock() defer w.mu.Unlock() socket, ok := w.sockets[sockfd] if !ok { return netdev.ErrInvalidSocketFd } switch socket.protocol { case netdev.IPPROTO_TCP: case netdev.IPPROTO_TLS: case netdev.IPPROTO_UDP: socket.sock = w.getSocket() if socket.sock == noSocketAvail { return netdev.ErrNoMoreSockets } w.startServer(socket.sock, ip.Port(), protoModeUDP) } socket.laddr = ip return nil } func toUint32(ip [4]byte) uint32 { return uint32(ip[0])<<24 | uint32(ip[1])<<16 | uint32(ip[2])<<8 | uint32(ip[3]) } func (w *wifinina) Connect(sockfd int, host string, ip netip.AddrPort) error { if debugging(debugNetdev) { if host == "" { fmt.Printf("[Connect] sockfd: %d, addr: %s\r\n", sockfd, ip) } else { fmt.Printf("[Connect] sockfd: %d, host: %s:%d\r\n", sockfd, host, ip.Port()) } } w.mu.Lock() defer w.mu.Unlock() socket, ok := w.sockets[sockfd] if !ok { return netdev.ErrInvalidSocketFd } // Start the connection switch socket.protocol { case netdev.IPPROTO_TCP: socket.sock = w.getSocket() if socket.sock == noSocketAvail { return netdev.ErrNoMoreSockets } w.startClient(socket.sock, "", toUint32(ip.Addr().As4()), ip.Port(), protoModeTCP) case netdev.IPPROTO_TLS: socket.sock = w.getSocket() if socket.sock == noSocketAvail { return netdev.ErrNoMoreSockets } w.startClient(socket.sock, host, 0, ip.Port(), protoModeTLS) case netdev.IPPROTO_UDP: if socket.sock == noSocketAvail { return fmt.Errorf("Must Bind before Connecting") } // See start in sendUDP() socket.raddr = ip socket.clientConnected = true return nil } if w.getClientState(socket.sock) == tcpStateEstablished { socket.clientConnected = true return nil } if host == "" { return fmt.Errorf("Connect to %s failed", ip) } else { return fmt.Errorf("Connect to %s:%d failed", host, ip.Port()) } } func (w *wifinina) Listen(sockfd int, backlog int) error { if debugging(debugNetdev) { fmt.Printf("[Listen] sockfd: %d\r\n", sockfd) } w.mu.Lock() defer w.mu.Unlock() socket, ok := w.sockets[sockfd] if !ok { return netdev.ErrInvalidSocketFd } switch socket.protocol { case netdev.IPPROTO_TCP: socket.sock = w.getSocket() if socket.sock == noSocketAvail { return netdev.ErrNoMoreSockets } w.startServer(socket.sock, socket.laddr.Port(), protoModeTCP) case netdev.IPPROTO_UDP: default: return netdev.ErrProtocolNotSupported } return nil } func (w *wifinina) Accept(sockfd int) (int, netip.AddrPort, error) { if debugging(debugNetdev) { fmt.Printf("[Accept] sockfd: %d\r\n", sockfd) } w.mu.Lock() defer w.mu.Unlock() socket, ok := w.sockets[sockfd] if !ok { return -1, netip.AddrPort{}, netdev.ErrInvalidSocketFd } switch socket.protocol { case netdev.IPPROTO_TCP: default: return -1, netip.AddrPort{}, netdev.ErrProtocolNotSupported } skip: for { // Accept() will be sleeping most of the time, checking for // new clients every 1/10 sec. w.mu.Unlock() time.Sleep(100 * time.Millisecond) w.mu.Lock() // Check if we've faulted if w.fault != nil { return -1, netip.AddrPort{}, w.fault } // Check if a client has data var client sock = w.accept(socket.sock) if client == noSocketAvail { // None ready continue } // If we already have a socket for the client, skip for _, s := range w.sockets { if s.sock == client { continue skip } } // Otherwise, create a new socket clientfd := w.newSockfd() if clientfd == -1 { return -1, netip.AddrPort{}, netdev.ErrNoMoreSockets } w.sockets[clientfd] = &Socket{ protocol: netdev.IPPROTO_TCP, sock: client, clientConnected: true, } raddr := w.getRemoteData(client) return clientfd, raddr, nil } } func (w *wifinina) sockDown(socket *Socket) bool { if socket.protocol == netdev.IPPROTO_UDP { return false } return w.getClientState(socket.sock) != tcpStateEstablished } func (w *wifinina) sendTCP(sock sock, buf []byte, deadline time.Time) (int, error) { var timeoutDataSent = 25 // Send it n := int(w.sendData(sock, buf)) if n == 0 { return -1, io.EOF } // Check if data was sent for i := 0; i < timeoutDataSent; i++ { sent := w.checkDataSent(sock) if sent { return n, nil } // Check if we've timed out if !deadline.IsZero() { if time.Now().After(deadline) { return -1, netdev.ErrTimeout } } // Check if socket went down if w.getClientState(sock) != tcpStateEstablished { return -1, io.EOF } // Check if we've faulted if w.fault != nil { return -1, w.fault } // Unlock while we sleep, so others can make progress w.mu.Unlock() time.Sleep(100 * time.Millisecond) w.mu.Lock() } return -1, netdev.ErrTimeout } func (w *wifinina) sendUDP(sock sock, raddr netip.AddrPort, buf []byte, deadline time.Time) (int, error) { // Start a client for each send w.startClient(sock, "", toUint32(raddr.Addr().As4()), raddr.Port(), protoModeUDP) // Queue it ok := w.insertDataBuf(sock, buf) if !ok { return -1, fmt.Errorf("Insert UDP data failed, len(buf)=%d", len(buf)) } // Send it ok = w.sendUDPData(sock) if !ok { return -1, fmt.Errorf("Send UDP data failed, len(buf)=%d", len(buf)) } return len(buf), nil } func (w *wifinina) sendChunk(sockfd int, buf []byte, deadline time.Time) (int, error) { socket, ok := w.sockets[sockfd] if !ok { return -1, netdev.ErrInvalidSocketFd } // Check if we've timed out if !deadline.IsZero() { if time.Now().After(deadline) { return -1, netdev.ErrTimeout } } switch socket.protocol { case netdev.IPPROTO_TCP, netdev.IPPROTO_TLS: return w.sendTCP(socket.sock, buf, deadline) case netdev.IPPROTO_UDP: return w.sendUDP(socket.sock, socket.raddr, buf, deadline) } return -1, netdev.ErrProtocolNotSupported } func (w *wifinina) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) { if debugging(debugNetdev) { fmt.Printf("[Send] sockfd: %d, len(buf): %d, flags: %d\r\n", sockfd, len(buf), flags) } w.mu.Lock() defer w.mu.Unlock() // Break large bufs into chunks so we don't overrun the hw queue chunkSize := 1436 for i := 0; i < len(buf); i += chunkSize { end := i + chunkSize if end > len(buf) { end = len(buf) } _, err := w.sendChunk(sockfd, buf[i:end], deadline) if err != nil { return -1, err } } return len(buf), nil } func (w *wifinina) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) { if debugging(debugNetdev) { fmt.Printf("[Recv] sockfd: %d, len(buf): %d, flags: %d\r\n", sockfd, len(buf), flags) } w.mu.Lock() defer w.mu.Unlock() socket, ok := w.sockets[sockfd] if !ok { return -1, netdev.ErrInvalidSocketFd } // Limit max read size to chunk large read requests var max = len(buf) if max > 1436 { max = 1436 } for { // Check if we've timed out if !deadline.IsZero() { if time.Now().After(deadline) { return -1, netdev.ErrTimeout } } // Receive into buf, if any data available. It's ok if no data // is available, we'll just sleep a bit and recheck. Recv() // doesn't return unless there is data, even a single byte, or // on error such as timeout or EOF. n := int(w.getDataBuf(socket.sock, buf[:max])) if n > 0 { if debugging(debugNetdev) { fmt.Printf("[<--Recv] sockfd: %d, n: %d\r\n", sockfd, n) } return n, nil } // Check if socket went down if w.sockDown(socket) { // Get any last bytes n = int(w.getDataBuf(socket.sock, buf[:max])) if debugging(debugNetdev) { fmt.Printf("[<--Recv] sockfd: %d, n: %d, EOF\r\n", sockfd, n) } if n > 0 { return n, io.EOF } return -1, io.EOF } // Check if we've faulted if w.fault != nil { return -1, w.fault } // Unlock while we sleep, so others can make progress w.mu.Unlock() time.Sleep(100 * time.Millisecond) w.mu.Lock() } } func (w *wifinina) Close(sockfd int) error { if debugging(debugNetdev) { fmt.Printf("[Close] sockfd: %d\r\n", sockfd) } w.mu.Lock() defer w.mu.Unlock() socket, ok := w.sockets[sockfd] if !ok { return netdev.ErrInvalidSocketFd } if socket.clientConnected { w.stopClient(socket.sock) } delete(w.sockets, sockfd) return nil } func (w *wifinina) SetSockOpt(sockfd int, level int, opt int, value interface{}) error { if debugging(debugNetdev) { fmt.Printf("[SetSockOpt] sockfd: %d\r\n", sockfd) } return netdev.ErrNotSupported } func (w *wifinina) startClient(sock sock, hostname string, addr uint32, port uint16, mode uint8) { if debugging(debugCmd) { fmt.Printf(" [cmdStartClientTCP] sock: %d, hostname: \"%s\", addr: % 02X, port: %d, mode: %d\r\n", sock, hostname, addr, port, mode) } w.waitForChipReady() w.spiChipSelect() if len(hostname) > 0 { w.sendCmd(cmdStartClientTCP, 5) w.sendParamStr(hostname, false) } else { w.sendCmd(cmdStartClientTCP, 4) } w.sendParam32(addr, false) w.sendParam16(port, false) w.sendParam8(uint8(sock), false) w.sendParam8(mode, true) if len(hostname) > 0 { w.padTo4(17 + len(hostname)) } w.spiChipDeselect() w.waitRspCmd1(cmdStartClientTCP) } func (w *wifinina) getSocket() sock { if debugging(debugCmd) { fmt.Printf(" [cmdGetSocket]\r\n") } return sock(w.getUint8(w.req0(cmdGetSocket))) } func (w *wifinina) getClientState(sock sock) uint8 { if debugging(debugCmd) { fmt.Printf(" [cmdGetClientStateTCP] sock: %d\r\n", sock) } return w.getUint8(w.reqUint8(cmdGetClientStateTCP, uint8(sock))) } func (w *wifinina) sendData(sock sock, buf []byte) uint16 { if debugging(debugCmd) { fmt.Printf(" [cmdSendDataTCP] sock: %d, len(buf): %d\r\n", sock, len(buf)) } w.waitForChipReady() w.spiChipSelect() l := w.sendCmd(cmdSendDataTCP, 2) l += w.sendParamBuf([]byte{uint8(sock)}, false) l += w.sendParamBuf(buf, true) w.addPadding(l) w.spiChipDeselect() sent := w.getUint16(w.waitRspCmd1(cmdSendDataTCP)) return bits.RotateLeft16(sent, 8) } func (w *wifinina) checkDataSent(sock sock) bool { if debugging(debugCmd) { fmt.Printf(" [cmdDataSentTCP] sock: %d\r\n", sock) } sent := w.getUint8(w.reqUint8(cmdDataSentTCP, uint8(sock))) return sent > 0 } func (w *wifinina) getDataBuf(sock sock, buf []byte) uint16 { if debugging(debugCmd) { fmt.Printf(" [cmdGetDatabufTCP] sock: %d, len(buf): %d\r\n", sock, len(buf)) } w.waitForChipReady() w.spiChipSelect() p := uint16(len(buf)) l := w.sendCmd(cmdGetDatabufTCP, 2) l += w.sendParamBuf([]byte{uint8(sock)}, false) l += w.sendParamBuf([]byte{uint8(p & 0x00FF), uint8((p) >> 8)}, true) w.addPadding(l) w.spiChipDeselect() w.waitForChipReady() w.spiChipSelect() n := w.waitRspBuf16(cmdGetDatabufTCP, buf) w.spiChipDeselect() if n > 0 { if debugging(debugCmd) { fmt.Printf(" [<--cmdGetDatabufTCP] sock: %d, got n: %d\r\n", sock, n) } } return n } func (w *wifinina) stopClient(sock sock) { if debugging(debugCmd) { fmt.Printf(" [cmdStopClientTCP] sock: %d\r\n", sock) } w.getUint8(w.reqUint8(cmdStopClientTCP, uint8(sock))) } func (w *wifinina) startServer(sock sock, port uint16, mode uint8) { if debugging(debugCmd) { fmt.Printf(" [cmdStartServerTCP] sock: %d, port: %d, mode: %d\r\n", sock, port, mode) } w.waitForChipReady() w.spiChipSelect() l := w.sendCmd(cmdStartServerTCP, 3) l += w.sendParam16(port, false) l += w.sendParam8(uint8(sock), false) l += w.sendParam8(mode, true) w.addPadding(l) w.spiChipDeselect() w.waitRspCmd1(cmdStartServerTCP) } func (w *wifinina) accept(s sock) sock { if debugging(debugCmd) { fmt.Printf(" [cmdAvailDataTCP] sock: %d\r\n", s) } w.waitForChipReady() w.spiChipSelect() l := w.sendCmd(cmdAvailDataTCP, 1) l += w.sendParam8(uint8(s), true) w.addPadding(l) w.spiChipDeselect() newsock16 := w.getUint16(w.waitRspCmd1(cmdAvailDataTCP)) newsock := sock(uint8(bits.RotateLeft16(newsock16, 8))) if newsock != noSocketAvail { if debugging(debugCmd) { fmt.Printf(" [cmdAvailDataTCP-->] sock: %d, got sock: %d\r\n", s, newsock) } } return newsock } func (w *wifinina) getRemoteData(s sock) netip.AddrPort { if debugging(debugCmd) { fmt.Printf(" [cmdGetRemoteData] sock: %d\r\n", s) } sl := make([]string, 2) l := w.reqRspStr1(cmdGetRemoteData, uint8(s), sl) if l != 2 { w.faultf("getRemoteData wanted l=2, got l=%d", l) return netip.AddrPort{} } ip, _ := netip.AddrFromSlice([]byte(sl[0])[:4]) port := binary.BigEndian.Uint16([]byte(sl[1])) return netip.AddrPortFrom(ip, port) } // insertDataBuf adds data to the buffer used for sending UDP data func (w *wifinina) insertDataBuf(sock sock, buf []byte) bool { if debugging(debugCmd) { fmt.Printf(" [cmdInsertDataBuf] sock: %d, len(buf): %d\r\n", sock, len(buf)) } w.waitForChipReady() w.spiChipSelect() l := w.sendCmd(cmdInsertDataBuf, 2) l += w.sendParamBuf([]byte{uint8(sock)}, false) l += w.sendParamBuf(buf, true) w.addPadding(l) w.spiChipDeselect() n := w.getUint8(w.waitRspCmd1(cmdInsertDataBuf)) return n == 1 } // sendUDPData sends the data previously added to the UDP buffer func (w *wifinina) sendUDPData(sock sock) bool { if debugging(debugCmd) { fmt.Printf(" [cmdSendDataUDP] sock: %d\r\n", sock) } w.waitForChipReady() w.spiChipSelect() l := w.sendCmd(cmdSendDataUDP, 1) l += w.sendParam8(uint8(sock), true) w.addPadding(l) w.spiChipDeselect() n := w.getUint8(w.waitRspCmd1(cmdSendDataUDP)) return n == 1 } func (w *wifinina) disconnect() { if debugging(debugCmd) { fmt.Printf(" [cmdDisconnect]\r\n") } w.req1(cmdDisconnect) } func (w *wifinina) getFwVersion() string { if debugging(debugCmd) { fmt.Printf(" [cmdGetFwVersion]\r\n") } return w.getString(w.req0(cmdGetFwVersion)) } func (w *wifinina) getConnectionStatus() connectionStatus { if debugging(debugCmd) { fmt.Printf(" [cmdGetConnStatus]\r\n") } status := w.getUint8(w.req0(cmdGetConnStatus)) return connectionStatus(status) } func (w *wifinina) getCurrentencryptionType() encryptionType { enctype := w.getUint8(w.req1(cmdGetCurrEncrType)) return encryptionType(enctype) } func (w *wifinina) getCurrentBSSID() net.HardwareAddr { return w.getMACAddress(w.req1(cmdGetCurrBSSID)) } func (w *wifinina) getCurrentRSSI() int32 { return w.getInt32(w.req1(cmdGetCurrRSSI)) } func (w *wifinina) getCurrentSSID() string { return w.getString(w.req1(cmdGetCurrSSID)) } func (w *wifinina) getMACAddr() net.HardwareAddr { if debugging(debugCmd) { fmt.Printf(" [cmdGetMACAddr]\r\n") } return w.getMACAddress(w.req1(cmdGetMACAddr)) } func (w *wifinina) faultf(f string, args ...any) { // Only record the first fault if w.fault == nil { w.fault = fmt.Errorf(f, args...) } } func (w *wifinina) getIP() (ip, subnet, gateway netip.Addr) { if debugging(debugCmd) { fmt.Printf(" [cmdGetIPAddr]\r\n") } sl := make([]string, 3) l := w.reqRspStr1(cmdGetIPAddr, dummyData, sl) if l != 3 { w.faultf("getIP wanted l=3, got l=%d", l) return } ip, _ = netip.AddrFromSlice([]byte(sl[0])[:4]) subnet, _ = netip.AddrFromSlice([]byte(sl[1])[:4]) gateway, _ = netip.AddrFromSlice([]byte(sl[2])[:4]) return } func (w *wifinina) getHostByName(name string) string { if debugging(debugCmd) { fmt.Printf(" [cmdGetHostByName]\r\n") } ok := w.getUint8(w.reqStr(cmdReqHostByName, name)) if ok != 1 { return "" } return w.getString(w.req0(cmdGetHostByName)) } func (w *wifinina) getNetworkBSSID(idx int) net.HardwareAddr { if idx < 0 || idx >= maxNetworks { return net.HardwareAddr{} } return w.getMACAddress(w.reqUint8(cmdGetIdxBSSID, uint8(idx))) } func (w *wifinina) getNetworkChannel(idx int) uint8 { if idx < 0 || idx >= maxNetworks { return 0 } return w.getUint8(w.reqUint8(cmdGetIdxChannel, uint8(idx))) } func (w *wifinina) getNetworkEncrType(idx int) encryptionType { if idx < 0 || idx >= maxNetworks { return 0 } enctype := w.getUint8(w.reqUint8(cmdGetIdxEncrType, uint8(idx))) return encryptionType(enctype) } func (w *wifinina) getNetworkRSSI(idx int) int32 { if idx < 0 || idx >= maxNetworks { return 0 } return w.getInt32(w.reqUint8(cmdGetIdxRSSI, uint8(idx))) } func (w *wifinina) getNetworkSSID(idx int) string { if idx < 0 || idx >= maxNetworks { return "" } return w.ssids[idx] } func (w *wifinina) getReasonCode() uint8 { return w.getUint8(w.req0(cmdGetReasonCode)) } // getTime is the time as a Unix timestamp func (w *wifinina) getTime() uint32 { return w.getUint32(w.req0(cmdGetTime)) } func (w *wifinina) getTemperature() float32 { return w.getFloat32(w.req0(cmdGetTemperature)) } func (w *wifinina) setDebug(on bool) { var v uint8 if on { v = 1 } w.reqUint8(cmdSetDebug, v) } func (w *wifinina) setNetwork(ssid string) { w.reqStr(cmdSetNet, ssid) } func (w *wifinina) setPassphrase(ssid string, passphrase string) { if debugging(debugCmd) { fmt.Printf(" [cmdSetPassphrase] ssid: %s, passphrase: ******\r\n", ssid) } // Dont' show passphrase in debug output saveDebug := _debug _debug = _debug & ^debugDetail w.reqStr2(cmdSetPassphrase, ssid, passphrase) _debug = saveDebug } func (w *wifinina) setKey(ssid string, index uint8, key string) { w.waitForChipReady() w.spiChipSelect() w.sendCmd(cmdSetKey, 3) w.sendParamStr(ssid, false) w.sendParam8(index, false) w.sendParamStr(key, true) w.padTo4(8 + len(ssid) + len(key)) w.spiChipDeselect() w.waitRspCmd1(cmdSetKey) } func (w *wifinina) setNetworkForAP(ssid string) { w.reqStr(cmdSetAPNet, ssid) } func (w *wifinina) setPassphraseForAP(ssid string, passphrase string) { w.reqStr2(cmdSetAPPassphrase, ssid, passphrase) } func (w *wifinina) setDNS(which uint8, dns1 uint32, dns2 uint32) { w.waitForChipReady() w.spiChipSelect() w.sendCmd(cmdSetDNSConfig, 3) w.sendParam8(which, false) w.sendParam32(dns1, false) w.sendParam32(dns2, true) //pad?? w.spiChipDeselect() w.waitRspCmd1(cmdSetDNSConfig) } func (w *wifinina) setHostname(hostname string) { w.waitForChipReady() w.spiChipSelect() w.sendCmd(cmdSetHostname, 3) w.sendParamStr(hostname, true) w.padTo4(5 + len(hostname)) w.spiChipDeselect() w.waitRspCmd1(cmdSetHostname) } func (w *wifinina) setPowerMode(mode uint8) { w.reqUint8(cmdSetPowerMode, mode) } func (w *wifinina) scanNetworks() uint8 { return w.reqRspStr0(cmdScanNetworks, w.ssids[:]) } func (w *wifinina) startScanNetworks() uint8 { return w.getUint8(w.req0(cmdStartScanNetworks)) } func (w *wifinina) PinMode(pin uint8, mode uint8) { if debugging(debugCmd) { fmt.Printf(" [cmdSetPinMode] pin: %d, mode: %d\r\n", pin, mode) } w.req2Uint8(cmdSetPinMode, pin, mode) } func (w *wifinina) DigitalWrite(pin uint8, value uint8) { if debugging(debugCmd) { fmt.Printf(" [cmdSetDigitialWrite] pin: %d, value: %d\r\n", pin, value) } w.req2Uint8(cmdSetDigitalWrite, pin, value) } func (w *wifinina) AnalogWrite(pin uint8, value uint8) { w.req2Uint8(cmdSetAnalogWrite, pin, value) } func (w *wifinina) getString(l uint8) string { return string(w.buf[0:l]) } func (w *wifinina) getUint8(l uint8) uint8 { if l == 1 { return w.buf[0] } w.faultf("expected length 1, was actually %d", l) return 0 } func (w *wifinina) getUint16(l uint8) uint16 { if l == 2 { return binary.BigEndian.Uint16(w.buf[0:2]) } w.faultf("expected length 2, was actually %d", l) return 0 } func (w *wifinina) getUint32(l uint8) uint32 { if l == 4 { return binary.BigEndian.Uint32(w.buf[0:4]) } w.faultf("expected length 4, was actually %d", l) return 0 } func (w *wifinina) getInt32(l uint8) int32 { return int32(w.getUint32(l)) } func (w *wifinina) getFloat32(l uint8) float32 { return float32(w.getUint32(l)) } func (w *wifinina) getMACAddress(l uint8) net.HardwareAddr { if l == 6 { mac := w.buf[0:6] // Reverse the bytes for i, j := 0, len(mac)-1; i < j; i, j = i+1, j-1 { mac[i], mac[j] = mac[j], mac[i] } return mac } w.faultf("expected length 6, was actually %d", l) return net.HardwareAddr{} } func (w *wifinina) transfer(b byte) byte { v, err := w.spi.Transfer(b) if err != nil { w.faultf("SPI.Transfer") return 0 } return v } // Cmd Struct Message */ // ._______________________________________________________________________. // | START CMD | C/R | CMD | N.PARAM | PARAM LEN | PARAM | .. | END CMD | // |___________|______|______|_________|___________|________|____|_________| // | 8 bit | 1bit | 7bit | 8bit | 8bit | nbytes | .. | 8bit | // |___________|______|______|_________|___________|________|____|_________| // req0 sends a command to the device with no request parameters func (w *wifinina) req0(cmd uint8) uint8 { w.sendCmd0(cmd) return w.waitRspCmd1(cmd) } // req1 sends a command to the device with a single dummy parameters of 0xFF func (w *wifinina) req1(cmd uint8) uint8 { return w.reqUint8(cmd, dummyData) } // reqUint8 sends a command to the device with a single uint8 parameter func (w *wifinina) reqUint8(cmd uint8, data uint8) uint8 { w.sendCmdPadded1(cmd, data) return w.waitRspCmd1(cmd) } // req2Uint8 sends a command to the device with two uint8 parameters func (w *wifinina) req2Uint8(cmd, p1, p2 uint8) uint8 { w.sendCmdPadded2(cmd, p1, p2) return w.waitRspCmd1(cmd) } // reqStr sends a command to the device with a single string parameter func (w *wifinina) reqStr(cmd uint8, p1 string) uint8 { w.sendCmdStr(cmd, p1) return w.waitRspCmd1(cmd) } // reqStr2 sends a command to the device with 2 string parameters func (w *wifinina) reqStr2(cmd uint8, p1 string, p2 string) { w.sendCmdStr2(cmd, p1, p2) w.waitRspCmd1(cmd) } // reqStrRsp0 sends a command passing a string slice for the response func (w *wifinina) reqRspStr0(cmd uint8, sl []string) (l uint8) { w.sendCmd0(cmd) w.waitForChipReady() w.spiChipSelect() l = w.waitRspStr(cmd, sl) w.spiChipDeselect() return } // reqStrRsp1 sends a command with a uint8 param and a string slice for the response func (w *wifinina) reqRspStr1(cmd uint8, data uint8, sl []string) uint8 { w.sendCmdPadded1(cmd, data) w.waitForChipReady() w.spiChipSelect() l := w.waitRspStr(cmd, sl) w.spiChipDeselect() return l } func (w *wifinina) sendCmd0(cmd uint8) { w.waitForChipReady() w.spiChipSelect() w.sendCmd(cmd, 0) w.spiChipDeselect() } func (w *wifinina) sendCmdPadded1(cmd uint8, data uint8) { w.waitForChipReady() w.spiChipSelect() w.sendCmd(cmd, 1) w.sendParam8(data, true) w.transfer(dummyData) w.transfer(dummyData) w.spiChipDeselect() return } func (w *wifinina) sendCmdPadded2(cmd, data1, data2 uint8) { w.waitForChipReady() w.spiChipSelect() w.sendCmd(cmd, 1) w.sendParam8(data1, false) w.sendParam8(data2, true) w.transfer(dummyData) w.spiChipDeselect() } func (w *wifinina) sendCmdStr(cmd uint8, p1 string) { w.waitForChipReady() w.spiChipSelect() w.sendCmd(cmd, 1) w.sendParamStr(p1, true) w.padTo4(5 + len(p1)) w.spiChipDeselect() } func (w *wifinina) sendCmdStr2(cmd uint8, p1 string, p2 string) { w.waitForChipReady() w.spiChipSelect() w.sendCmd(cmd, 2) w.sendParamStr(p1, false) w.sendParamStr(p2, true) w.padTo4(6 + len(p1) + len(p2)) w.spiChipDeselect() } func (w *wifinina) waitRspCmd1(cmd uint8) uint8 { w.waitForChipReady() w.spiChipSelect() l := w.waitRspCmd(cmd, 1) w.spiChipDeselect() return l } func (w *wifinina) sendCmd(cmd uint8, numParam uint8) (l int) { if debugging(debugDetail) { fmt.Printf(" sendCmd: %02X %02X %02X", cmdStart, cmd & ^(uint8(flagReply)), numParam) } l = 3 w.transfer(cmdStart) w.transfer(cmd & ^(uint8(flagReply))) w.transfer(numParam) if numParam == 0 { w.transfer(cmdEnd) l += 1 if debugging(debugDetail) { fmt.Printf(" %02X", cmdEnd) } } if debugging(debugDetail) { fmt.Printf(" (%d)\r\n", l) } return } func (w *wifinina) sendParamLen16(p uint16) (l int) { w.transfer(uint8(p >> 8)) w.transfer(uint8(p & 0xFF)) if debugging(debugDetail) { fmt.Printf(" %02X %02X", uint8(p>>8), uint8(p&0xFF)) } return 2 } func (w *wifinina) sendParamBuf(p []byte, isLastParam bool) (l int) { if debugging(debugDetail) { fmt.Printf(" sendParamBuf:") } l += w.sendParamLen16(uint16(len(p))) for _, b := range p { if debugging(debugDetail) { fmt.Printf(" %02X", b) } w.transfer(b) l += 1 } if isLastParam { if debugging(debugDetail) { fmt.Printf(" %02X", cmdEnd) } w.transfer(cmdEnd) l += 1 } if debugging(debugDetail) { fmt.Printf(" (%d) \r\n", l) } return } func (w *wifinina) sendParamStr(p string, isLastParam bool) (l int) { if debugging(debugDetail) { fmt.Printf(" sendParamStr: p: %s, lastParam: %t\r\n", p, isLastParam) } l = len(p) w.transfer(uint8(l)) if l > 0 { w.spi.Tx([]byte(p), nil) } if isLastParam { w.transfer(cmdEnd) l += 1 } return } func (w *wifinina) sendParam8(p uint8, isLastParam bool) (l int) { if debugging(debugDetail) { fmt.Printf(" sendParam8: p: %d, lastParam: %t\r\n", p, isLastParam) } l = 2 w.transfer(1) w.transfer(p) if isLastParam { w.transfer(cmdEnd) l += 1 } return } func (w *wifinina) sendParam16(p uint16, isLastParam bool) (l int) { if debugging(debugDetail) { fmt.Printf(" sendParam16: p: %d, lastParam: %t\r\n", p, isLastParam) } l = 3 w.transfer(2) w.transfer(uint8(p >> 8)) w.transfer(uint8(p & 0xFF)) if isLastParam { w.transfer(cmdEnd) l += 1 } return } func (w *wifinina) sendParam32(p uint32, isLastParam bool) (l int) { if debugging(debugDetail) { fmt.Printf(" sendParam32: p: %d, lastParam: %t\r\n", p, isLastParam) } l = 5 w.transfer(4) w.transfer(uint8(p >> 24)) w.transfer(uint8(p >> 16)) w.transfer(uint8(p >> 8)) w.transfer(uint8(p & 0xFF)) if isLastParam { w.transfer(cmdEnd) l += 1 } return } func (w *wifinina) waitForChipReady() { if debugging(debugDetail) { fmt.Printf(" waitForChipReady\r\n") } for i := 0; w.ack.Get(); i++ { time.Sleep(1 * time.Millisecond) if i == 10000 { w.faultf("hung in waitForChipReady") return } } } func (w *wifinina) spiChipSelect() { if debugging(debugDetail) { fmt.Printf(" spiChipSelect\r\n") } w.cs.Low() start := time.Now() for time.Since(start) < 10*time.Millisecond { if w.ack.Get() { return } time.Sleep(100 * time.Microsecond) } w.faultf("hung in spiChipSelect") } func (w *wifinina) spiChipDeselect() { if debugging(debugDetail) { fmt.Printf(" spiChipDeselect\r\n") } w.cs.High() } func (w *wifinina) waitSpiChar(desired byte) { if debugging(debugDetail) { fmt.Printf(" waitSpiChar: desired: %02X\r\n", desired) } var read byte for i := 0; i < 10; i++ { w.readParam(&read) switch read { case cmdErr: w.faultf("cmdErr received, waiting for %d", desired) return case desired: return } time.Sleep(10 * time.Millisecond) } w.faultf("timeout waiting for SPI char %02X\r\n", desired) } func (w *wifinina) waitRspCmd(cmd uint8, np uint8) (l uint8) { if debugging(debugDetail) { fmt.Printf(" waitRspCmd: cmd: %02X, np: %d\r\n", cmd, np) } var data byte w.waitSpiChar(cmdStart) if !w.readAndCheckByte(cmd|flagReply, &data) { w.faultf("expected cmd %02X, read %02X", cmd, data) return } if w.readAndCheckByte(np, &data) { w.readParam(&l) for i := uint8(0); i < l; i++ { w.readParam(&w.buf[i]) } if !w.readAndCheckByte(cmdEnd, &data) { w.faultf("expected cmdEnd, read %02X", data) } } return } func (w *wifinina) waitRspBuf16(cmd uint8, buf []byte) (l uint16) { if debugging(debugDetail) { fmt.Printf(" waitRspBuf16: cmd: %02X, len(buf): %d\r\n", cmd, len(buf)) } var data byte w.waitSpiChar(cmdStart) if !w.readAndCheckByte(cmd|flagReply, &data) { w.faultf("expected cmd %02X, read %02X", cmd, data) return } if w.readAndCheckByte(1, &data) { l = w.readParamLen16() for i := uint16(0); i < l; i++ { w.readParam(&buf[i]) } if !w.readAndCheckByte(cmdEnd, &data) { w.faultf("expected cmdEnd, read %02X", data) } } return } func (w *wifinina) waitRspStr(cmd uint8, sl []string) (numRead uint8) { if debugging(debugDetail) { fmt.Printf(" waitRspStr: cmd: %02X, len(sl): %d\r\n", cmd, len(sl)) } var data byte w.waitSpiChar(cmdStart) if !w.readAndCheckByte(cmd|flagReply, &data) { w.faultf("expected cmd %02X, read %02X", cmd, data) return } numRead = w.transfer(dummyData) if numRead == 0 { w.faultf("waitRspStr numRead == 0") return } maxNumRead := uint8(len(sl)) for j, l := uint8(0), uint8(0); j < numRead; j++ { w.readParam(&l) for i := uint8(0); i < l; i++ { w.readParam(&w.buf[i]) } if j < maxNumRead { sl[j] = string(w.buf[0:l]) if debugging(debugDetail) { fmt.Printf(" str: %d (%d) - %08X\r\n", j, l, []byte(sl[j])) } } } for j := numRead; j < maxNumRead; j++ { if debugging(debugDetail) { fmt.Printf(" str: ", j, "\"\"\r") } sl[j] = "" } if !w.readAndCheckByte(cmdEnd, &data) { w.faultf("expected cmdEnd, read %02X", data) return } if numRead > maxNumRead { numRead = maxNumRead } return } func (w *wifinina) readAndCheckByte(check byte, read *byte) bool { w.readParam(read) return *read == check } // readParamLen16 reads 2 bytes from the SPI bus (MSB first), returning uint16 func (w *wifinina) readParamLen16() (v uint16) { b := w.transfer(0xFF) v = uint16(b) << 8 b = w.transfer(0xFF) v |= uint16(b) return } func (w *wifinina) readParam(b *byte) { *b = w.transfer(0xFF) } func (w *wifinina) addPadding(l int) { if debugging(debugDetail) { fmt.Printf(" addPadding: l: %d\r\n", l) } for i := (4 - (l % 4)) & 3; i > 0; i-- { if debugging(debugDetail) { fmt.Printf(" padding\r\n") } w.transfer(dummyData) } } func (w *wifinina) padTo4(l int) { if debugging(debugDetail) { fmt.Printf(" padTo4: l: %d\r\n", l) } for l%4 != 0 { if debugging(debugDetail) { fmt.Printf(" padding\r\n") } w.transfer(dummyData) l++ } }