From e80a22d0ba544124133da1595c659e1b4b78ac01 Mon Sep 17 00:00:00 2001 From: BCG Date: Sun, 22 Dec 2019 04:25:10 -0500 Subject: [PATCH] Client driver for WiFiNINA firmware (#98) * wifinina: implementation of WiFiNINA driver, including: - TCP client example is working - reading sockets and mqtt working - switched over to common net package also used by espat package - smoke tests and updated README for wifinina --- Makefile | 4 + README.md | 1 + examples/wifinina/mqttclient/main.go | 149 ++++ examples/wifinina/mqttsub/main.go | 162 ++++ examples/wifinina/tcpclient/main.go | 143 ++++ examples/wifinina/webclient/main.go | 153 ++++ wifinina/protocol/readme.md | 3 + wifinina/tcp.go | 213 ++++++ wifinina/timer.go | 28 + wifinina/wifinina.go | 1032 ++++++++++++++++++++++++++ 10 files changed, 1888 insertions(+) create mode 100644 examples/wifinina/mqttclient/main.go create mode 100644 examples/wifinina/mqttsub/main.go create mode 100644 examples/wifinina/tcpclient/main.go create mode 100644 examples/wifinina/webclient/main.go create mode 100644 wifinina/protocol/readme.md create mode 100644 wifinina/tcp.go create mode 100644 wifinina/timer.go create mode 100644 wifinina/wifinina.go diff --git a/Makefile b/Makefile index 1393aaf..2b0e0e0 100644 --- a/Makefile +++ b/Makefile @@ -85,6 +85,10 @@ smoke-test: @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13x/main.go @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/tcpclient/main.go + @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/webclient/main.go + @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/ws2812/main.go @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bme280/main.go diff --git a/README.md b/README.md index fa1479d..dfb239d 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ The following 34 devices are supported. | [DS1307 real time clock](https://datasheets.maximintegrated.com/en/ds/DS1307.pdf) | I2C | | [DS3231 real time clock](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf) | I2C | | ["Easystepper" stepper motor controller](https://en.wikipedia.org/wiki/Stepper_motor) | GPIO | +| [ESP32 as WiFi Coprocessor with Arduino nina-fw](https://github.com/arduino/nina-fw) | SPI | | [ESP8266/ESP32 AT Command set for WiFi/TCP/UDP](https://github.com/espressif/esp32-at) | UART | | [GPS module](https://www.u-blox.com/en/product/neo-6-series) | I2C/UART | | [HUB75 RGB led matrix](https://cdn-learn.adafruit.com/downloads/pdf/32x16-32x32-rgb-led-matrix.pdf) | SPI | diff --git a/examples/wifinina/mqttclient/main.go b/examples/wifinina/mqttclient/main.go new file mode 100644 index 0000000..f99c605 --- /dev/null +++ b/examples/wifinina/mqttclient/main.go @@ -0,0 +1,149 @@ +// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1. +// It creates an MQTT connection that publishes a message every second +// to an MQTT broker. +// +// In other words: +// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> Internet <--> MQTT broker. +// +// You must install the Paho MQTT package to build this program: +// +// go get -u github.com/eclipse/paho.mqtt.golang +// +package main + +import ( + "fmt" + "machine" + "math/rand" + "time" + + "tinygo.org/x/drivers/net/mqtt" + "tinygo.org/x/drivers/wifinina" +) + +// access point info +const ssid = "" +const pass = "" + +// IP address of the MQTT broker to use. Replace with your own info. +const server = "tcp://test.mosquitto.org:1883" + +//const server = "ssl://test.mosquitto.org:8883" + +// these are the default pins for the Arduino Nano33 IoT. +// change these to connect to a different UART or pins for the ESP8266/ESP32 +var ( + + // these are the default pins for the Arduino Nano33 IoT. + uart = machine.UART2 + tx = machine.NINA_TX + rx = machine.NINA_RX + spi = machine.NINA_SPI + + // this is the ESP chip that has the WIFININA firmware flashed on it + adaptor = &wifinina.Device{ + SPI: spi, + CS: machine.NINA_CS, + ACK: machine.NINA_ACK, + GPIO0: machine.NINA_GPIO0, + RESET: machine.NINA_RESETN, + } + + console = machine.UART0 + topic = "tinygo" +) + +func main() { + time.Sleep(3000 * time.Millisecond) + + uart.Configure(machine.UARTConfig{TX: tx, RX: rx}) + rand.Seed(time.Now().UnixNano()) + + // Configure SPI for 8Mhz, Mode 0, MSB First + spi.Configure(machine.SPIConfig{ + Frequency: 8 * 1e6, + MOSI: machine.NINA_MOSI, + MISO: machine.NINA_MISO, + SCK: machine.NINA_SCK, + }) + + // Init esp8266/esp32 + adaptor.Configure() + + connectToAP() + + opts := mqtt.NewClientOptions() + opts.AddBroker(server).SetClientID("tinygo-client-" + randomString(10)) + + println("Connectng to MQTT...") + cl := mqtt.NewClient(opts) + if token := cl.Connect(); token.Wait() && token.Error() != nil { + failMessage(token.Error().Error()) + } + + for i := 0; ; i++ { + println("Publishing MQTT message...") + data := []byte(fmt.Sprintf(`{"e":[{"n":"hello %d","v":101}]}`, i)) + token := cl.Publish(topic, 0, false, data) + token.Wait() + if err := token.Error(); err != nil { + switch t := err.(type) { + case wifinina.Error: + println(t.Error(), "attempting to reconnect") + if token := cl.Connect(); token.Wait() && token.Error() != nil { + failMessage(token.Error().Error()) + } + default: + println(err.Error()) + } + } + time.Sleep(1 * time.Millisecond) + } + + // Right now this code is never reached. Need a way to trigger it... + println("Disconnecting MQTT...") + cl.Disconnect(100) + + println("Done.") +} + +// connect to access point +func connectToAP() { + time.Sleep(2 * time.Second) + println("Connecting to " + ssid) + adaptor.SetPassphrase(ssid, pass) + for st, _ := adaptor.GetConnectionStatus(); st != wifinina.StatusConnected; { + println("Connection status: " + st.String()) + time.Sleep(1 * time.Second) + st, _ = adaptor.GetConnectionStatus() + } + println("Connected.") + time.Sleep(2 * time.Second) + ip, _, _, err := adaptor.GetIP() + for ; err != nil; ip, _, _, err = adaptor.GetIP() { + println(err.Error()) + time.Sleep(1 * time.Second) + } + println(ip.String()) +} + +// Returns an int >= min, < max +func randomInt(min, max int) int { + return min + rand.Intn(max-min) +} + +// Generate a random string of A-Z chars with len = l +func randomString(len int) string { + bytes := make([]byte, len) + for i := 0; i < len; i++ { + bytes[i] = byte(randomInt(65, 90)) + } + return string(bytes) +} + +func failMessage(msg string) { + for { + println(msg) + time.Sleep(1 * time.Second) + } +} diff --git a/examples/wifinina/mqttsub/main.go b/examples/wifinina/mqttsub/main.go new file mode 100644 index 0000000..fc988e0 --- /dev/null +++ b/examples/wifinina/mqttsub/main.go @@ -0,0 +1,162 @@ +// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1. +// It creates an MQTT connection that publishes a message every second +// to an MQTT broker. +// +// In other words: +// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> Internet <--> MQTT broker. +// +// You must also install the Paho MQTT package to build this program: +// +// go get -u github.com/eclipse/paho.mqtt.golang +// +package main + +import ( + "fmt" + "machine" + "math/rand" + "time" + + "tinygo.org/x/drivers/net/mqtt" + "tinygo.org/x/drivers/wifinina" +) + +// access point info +const ssid = "" +const pass = "" + +// IP address of the MQTT broker to use. Replace with your own info. +const server = "tcp://test.mosquitto.org:1883" + +//const server = "ssl://test.mosquitto.org:8883" + +// change these to connect to a different UART or pins for the ESP8266/ESP32 +var ( + + // these are the default pins for the Arduino Nano33 IoT. + uart = machine.UART2 + tx = machine.NINA_TX + rx = machine.NINA_RX + spi = machine.NINA_SPI + + // this is the ESP chip that has the WIFININA firmware flashed on it + adaptor = &wifinina.Device{ + SPI: spi, + CS: machine.NINA_CS, + ACK: machine.NINA_ACK, + GPIO0: machine.NINA_GPIO0, + RESET: machine.NINA_RESETN, + } + + console = machine.UART0 + + cl mqtt.Client + topicTx = "tinygo/tx" + topicRx = "tinygo/rx" +) + +func subHandler(client mqtt.Client, msg mqtt.Message) { + fmt.Printf("[%s] ", msg.Topic()) + fmt.Printf("%s\r\n", msg.Payload()) +} + +func main() { + time.Sleep(3000 * time.Millisecond) + + uart.Configure(machine.UARTConfig{TX: tx, RX: rx}) + rand.Seed(time.Now().UnixNano()) + + // Configure SPI for 8Mhz, Mode 0, MSB First + spi.Configure(machine.SPIConfig{ + Frequency: 8 * 1e6, + MOSI: machine.NINA_MOSI, + MISO: machine.NINA_MISO, + SCK: machine.NINA_SCK, + }) + + // Init esp8266/esp32 + adaptor.Configure() + + connectToAP() + + opts := mqtt.NewClientOptions() + opts.AddBroker(server).SetClientID("tinygo-client-" + randomString(10)) + + println("Connecting to MQTT broker at", server) + cl = mqtt.NewClient(opts) + if token := cl.Connect(); token.Wait() && token.Error() != nil { + failMessage(token.Error().Error()) + } + + // subscribe + token := cl.Subscribe(topicRx, 0, subHandler) + token.Wait() + if token.Error() != nil { + failMessage(token.Error().Error()) + } + + go publishing() + + select {} + + // Right now this code is never reached. Need a way to trigger it... + println("Disconnecting MQTT...") + cl.Disconnect(100) + + println("Done.") +} + +func publishing() { + for i := 0; ; i++ { + println("Publishing MQTT message...") + data := []byte(fmt.Sprintf(`{"e":[{"n":"hello %d","v":101}]}`, i)) + token := cl.Publish(topicRx, 0, false, data) + token.Wait() + if token.Error() != nil { + println(token.Error().Error()) + } + + time.Sleep(100 * time.Millisecond) + } +} + +// connect to access point +func connectToAP() { + time.Sleep(2 * time.Second) + println("Connecting to " + ssid) + adaptor.SetPassphrase(ssid, pass) + for st, _ := adaptor.GetConnectionStatus(); st != wifinina.StatusConnected; { + println("Connection status: " + st.String()) + time.Sleep(1 * time.Second) + st, _ = adaptor.GetConnectionStatus() + } + println("Connected.") + time.Sleep(2 * time.Second) + ip, _, _, err := adaptor.GetIP() + for ; err != nil; ip, _, _, err = adaptor.GetIP() { + println(err.Error()) + time.Sleep(1 * time.Second) + } + println(ip.String()) +} + +// Returns an int >= min, < max +func randomInt(min, max int) int { + return min + rand.Intn(max-min) +} + +// Generate a random string of A-Z chars with len = l +func randomString(len int) string { + bytes := make([]byte, len) + for i := 0; i < len; i++ { + bytes[i] = byte(randomInt(65, 90)) + } + return string(bytes) +} + +func failMessage(msg string) { + for { + println(msg) + time.Sleep(1 * time.Second) + } +} diff --git a/examples/wifinina/tcpclient/main.go b/examples/wifinina/tcpclient/main.go new file mode 100644 index 0000000..82db1b4 --- /dev/null +++ b/examples/wifinina/tcpclient/main.go @@ -0,0 +1,143 @@ +// This example opens a TCP connection using a device with WiFiNINA firmware +// and sends some data, for the purpose of testing speed and connectivity. +// +// You can open a server to accept connections from this program using: +// +// nc -w 5 -lk 8080 +// +package main + +import ( + "bytes" + "fmt" + "machine" + "time" + + "tinygo.org/x/drivers/net" + "tinygo.org/x/drivers/wifinina" +) + +// access point info +const ssid = "" +const pass = "" + +// IP address of the server aka "hub". Replace with your own info. +const serverIP = "" + +// these are the default pins for the Arduino Nano33 IoT. +// change these to connect to a different UART or pins for the ESP8266/ESP32 +var ( + + // these are the default pins for the Arduino Nano33 IoT. + uart = machine.UART2 + tx = machine.NINA_TX + rx = machine.NINA_RX + spi = machine.NINA_SPI + + // this is the ESP chip that has the WIFININA firmware flashed on it + adaptor = &wifinina.Device{ + SPI: spi, + CS: machine.NINA_CS, + ACK: machine.NINA_ACK, + GPIO0: machine.NINA_GPIO0, + RESET: machine.NINA_RESETN, + } + + console = machine.UART0 +) + +var buf = &bytes.Buffer{} + +func main() { + + uart.Configure(machine.UARTConfig{TX: tx, RX: rx}) + + // Configure SPI for 8Mhz, Mode 0, MSB First + spi.Configure(machine.SPIConfig{ + Frequency: 8 * 1e6, + MOSI: machine.NINA_MOSI, + MISO: machine.NINA_MISO, + SCK: machine.NINA_SCK, + }) + + adaptor.Configure() + + connectToAP() + + for { + sendBatch() + time.Sleep(500 * time.Millisecond) + } + println("Done.") +} + +func sendBatch() { + + // make TCP connection + ip := net.ParseIP(serverIP) + raddr := &net.TCPAddr{IP: ip, Port: 8080} + laddr := &net.TCPAddr{Port: 8080} + + message("---------------\r\nDialing TCP connection") + conn, err := net.DialTCP("tcp", laddr, raddr) + for ; err != nil; conn, err = net.DialTCP("tcp", laddr, raddr) { + message(err.Error()) + time.Sleep(5 * time.Second) + } + + n := 0 + w := 0 + start := time.Now() + + // send data + message("Sending data") + + for i := 0; i < 1000; i++ { + buf.Reset() + fmt.Fprint(buf, + "\r---------------------------- i == ", i, " ----------------------------"+ + "\r---------------------------- i == ", i, " ----------------------------") + if w, err = conn.Write(buf.Bytes()); err != nil { + println("error:", err.Error(), "\r") + continue + } + n += w + } + + buf.Reset() + ms := time.Now().Sub(start).Milliseconds() + fmt.Fprint(buf, "\nWrote ", n, " bytes in ", ms, " ms\r\n") + message(buf.String()) + + if _, err := conn.Write(buf.Bytes()); err != nil { + println("error:", err.Error(), "\r") + } + + // Right now this code is never reached. Need a way to trigger it... + println("Disconnecting TCP...") + conn.Close() +} + +// connect to access point +func connectToAP() { + time.Sleep(2 * time.Second) + message("Connecting to " + ssid) + adaptor.SetPassphrase(ssid, pass) + for st, _ := adaptor.GetConnectionStatus(); st != wifinina.StatusConnected; { + message("Connection status: " + st.String()) + time.Sleep(1 * time.Second) + st, _ = adaptor.GetConnectionStatus() + } + message("Connected.") + time.Sleep(2 * time.Second) + ip, _, _, err := adaptor.GetIP() + for ; err != nil; ip, _, _, err = adaptor.GetIP() { + message(err.Error()) + time.Sleep(1 * time.Second) + } + message(ip.String()) +} + +func message(msg string) { + println(msg, "\r") +} diff --git a/examples/wifinina/webclient/main.go b/examples/wifinina/webclient/main.go new file mode 100644 index 0000000..051d687 --- /dev/null +++ b/examples/wifinina/webclient/main.go @@ -0,0 +1,153 @@ +// This example opens a TCP connection using a device with WiFiNINA firmware +// and sends a HTTP request to retrieve a webpage, based on the following +// Arduino example: +// +// https://github.com/arduino-libraries/WiFiNINA/blob/master/examples/WiFiWebClientRepeating/ +// +package main + +import ( + "fmt" + "machine" + "time" + + "tinygo.org/x/drivers/net" + "tinygo.org/x/drivers/wifinina" +) + +// access point info +const ssid = "" +const pass = "" + +// IP address of the server aka "hub". Replace with your own info. +const server = "tinygo.org" + +// these are the default pins for the Arduino Nano33 IoT. +// change these to connect to a different UART or pins for the ESP8266/ESP32 +var ( + + // these are the default pins for the Arduino Nano33 IoT. + uart = machine.UART2 + tx = machine.NINA_TX + rx = machine.NINA_RX + spi = machine.NINA_SPI + + // this is the ESP chip that has the WIFININA firmware flashed on it + adaptor = &wifinina.Device{ + SPI: spi, + CS: machine.NINA_CS, + ACK: machine.NINA_ACK, + GPIO0: machine.NINA_GPIO0, + RESET: machine.NINA_RESETN, + } + + console = machine.UART0 +) + +var buf [256]byte + +var lastRequestTime time.Time +var conn net.Conn + +func main() { + + uart.Configure(machine.UARTConfig{TX: tx, RX: rx}) + + // Configure SPI for 8Mhz, Mode 0, MSB First + spi.Configure(machine.SPIConfig{ + Frequency: 8 * 1e6, + MOSI: machine.NINA_MOSI, + MISO: machine.NINA_MISO, + SCK: machine.NINA_SCK, + }) + + adaptor.Configure() + + connectToAP() + + for { + loop() + } + println("Done.") +} + +func loop() { + if conn != nil { + for n, err := conn.Read(buf[:]); n > 0; n, err = conn.Read(buf[:]) { + if err != nil { + println("Read error: " + err.Error()) + } else { + print(string(buf[0:n])) + } + } + } + if time.Now().Sub(lastRequestTime).Milliseconds() >= 10000 { + makeHTTPRequest() + } +} + +func makeHTTPRequest() { + + var err error + if conn != nil { + conn.Close() + } + + // make TCP connection + ip := net.ParseIP(server) + raddr := &net.TCPAddr{IP: ip, Port: 80} + laddr := &net.TCPAddr{Port: 8080} + + message("\r\n---------------\r\nDialing TCP connection") + conn, err = net.DialTCP("tcp", laddr, raddr) + for ; err != nil; conn, err = net.DialTCP("tcp", laddr, raddr) { + message("connection failed: " + err.Error()) + time.Sleep(5 * time.Second) + } + println("Connected!\r") + + print("Sending HTTP request...") + fmt.Fprintln(conn, "GET / HTTP/1.1") + fmt.Fprintln(conn, "Host:", server) + fmt.Fprintln(conn, "User-Agent: TinyGo/0.10.0") + fmt.Fprintln(conn, "Connection: close") + fmt.Fprintln(conn) + println("Sent!\r\n\r") + + lastRequestTime = time.Now() +} + +func readLine(conn *net.TCPSerialConn) string { + println("Attempting to read...\r") + b := buf[:] + for expiry := time.Now().Unix() + 10; time.Now().Unix() > expiry; { + if n, err := conn.Read(b); n > 0 && err == nil { + return string(b[0:n]) + } + } + return "" +} + +// connect to access point +func connectToAP() { + time.Sleep(2 * time.Second) + message("Connecting to " + ssid) + adaptor.SetPassphrase(ssid, pass) + for st, _ := adaptor.GetConnectionStatus(); st != wifinina.StatusConnected; { + message("Connection status: " + st.String()) + time.Sleep(1 * time.Second) + st, _ = adaptor.GetConnectionStatus() + } + message("Connected.") + time.Sleep(2 * time.Second) + ip, _, _, err := adaptor.GetIP() + for ; err != nil; ip, _, _, err = adaptor.GetIP() { + message(err.Error()) + time.Sleep(1 * time.Second) + } + message(ip.String()) +} + +func message(msg string) { + println(msg, "\r") +} diff --git a/wifinina/protocol/readme.md b/wifinina/protocol/readme.md new file mode 100644 index 0000000..0f2a5e3 --- /dev/null +++ b/wifinina/protocol/readme.md @@ -0,0 +1,3 @@ +WiFiNINA protocol +================= + diff --git a/wifinina/tcp.go b/wifinina/tcp.go new file mode 100644 index 0000000..9a7d32e --- /dev/null +++ b/wifinina/tcp.go @@ -0,0 +1,213 @@ +package wifinina + +import ( + "fmt" + "strconv" + "time" + + "tinygo.org/x/drivers/net" +) + +const ( + ReadBufferSize = 128 +) + +func (d *Device) NewDriver() net.DeviceDriver { + return &Driver{dev: d, sock: NoSocketAvail} +} + +type Driver struct { + dev *Device + sock uint8 + readBuf readBuffer +} + +type readBuffer struct { + data [ReadBufferSize]byte + head int + size int +} + +func (drv *Driver) GetDNS(domain string) (string, error) { + ipAddr, err := drv.dev.GetHostByName(domain) + return ipAddr.String(), err +} + +func (drv *Driver) ConnectTCPSocket(addr, portStr string) error { + return drv.connectSocket(addr, portStr, ProtoModeTCP) +} + +func (drv *Driver) ConnectSSLSocket(addr, portStr string) error { + return drv.connectSocket(addr, portStr, ProtoModeTLS) +} + +func (drv *Driver) connectSocket(addr, portStr string, mode uint8) error { + + // convert port to uint16 + p64, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return fmt.Errorf("could not convert port to uint16: %s", err.Error()) + } + port := uint16(p64) + + // look up the hostname if necessary; if an IP address was specified, the + // same will be returned. Otherwise, an IPv4 for the hostname is returned. + ipAddr, err := drv.dev.GetHostByName(addr) + if err != nil { + return err + } + ip := ipAddr.AsUint32() + + // check to see if socket is already set; if so, stop it + if drv.sock != NoSocketAvail { + if err := drv.stop(); err != nil { + return err + } + } + + // get a socket from the device + if drv.sock, err = drv.dev.GetSocket(); err != nil { + return err + } + + // attempt to start the client + if err := drv.dev.StartClient(ip, port, drv.sock, mode); err != nil { + return err + } + + // FIXME: this 4 second timeout is simply mimicking the Arduino driver + for t := newTimer(4 * time.Second); !t.Expired(); { + connected, err := drv.IsConnected() + if err != nil { + return err + } + if connected { + return nil + } + wait(1 * time.Millisecond) + } + + return ErrConnectionTimeout +} + +func (drv *Driver) ConnectUDPSocket(addr, sport, lport string) error { + return ErrNotImplemented +} + +func (drv *Driver) DisconnectSocket() error { + return drv.stop() +} + +func (drv *Driver) StartSocketSend(size int) error { + // not needed for WiFiNINA??? + return nil +} + +func (drv *Driver) Response(timeout int) ([]byte, error) { + return nil, nil +} + +func (drv *Driver) Write(b []byte) (n int, err error) { + if drv.sock == NoSocketAvail { + return 0, ErrNoSocketAvail + } + if len(b) == 0 { + return 0, ErrNoData + } + written, err := drv.dev.SendData(b, drv.sock) + if err != nil { + return 0, err + } + if written == 0 { + return 0, ErrDataNotWritten + } + if sent, _ := drv.dev.CheckDataSent(drv.sock); !sent { + return 0, ErrCheckDataError + } + return len(b), nil +} + +func (drv *Driver) ReadSocket(b []byte) (n int, err error) { + avail, err := drv.available() + if err != nil { + println("ReadSocket error: " + err.Error()) + return 0, err + } + if avail == 0 { + return 0, nil + } + length := len(b) + if avail < length { + length = avail + } + copy(b, drv.readBuf.data[drv.readBuf.head:drv.readBuf.head+length]) + drv.readBuf.head += length + drv.readBuf.size -= length + return length, nil +} + +// IsSocketDataAvailable returns of there is socket data available +func (drv *Driver) IsSocketDataAvailable() bool { + n, err := drv.available() + return err == nil && n > 0 +} + +func (drv *Driver) available() (int, error) { + if drv.readBuf.size == 0 { + n, err := drv.dev.GetDataBuf(drv.sock, drv.readBuf.data[:]) + if n > 0 { + drv.readBuf.head = 0 + drv.readBuf.size = n + } + if err != nil { + return int(n), err + } + } + return drv.readBuf.size, nil +} + +func (drv *Driver) IsConnected() (bool, error) { + if drv.sock == NoSocketAvail { + return false, nil + } + s, err := drv.status() + if err != nil { + return false, err + } + isConnected := !(s == TCPStateListen || s == TCPStateClosed || + s == TCPStateFinWait1 || s == TCPStateFinWait2 || s == TCPStateTimeWait || + s == TCPStateSynSent || s == TCPStateSynRcvd || s == TCPStateCloseWait) + // TODO: investigate if the below is necessary (as per Arduino driver) + //if !isConnected { + // //close socket buffer? + // WiFiSocketBuffer.close(_sock); + // _sock = 255; + //} + return isConnected, nil +} + +func (drv *Driver) status() (uint8, error) { + if drv.sock == NoSocketAvail { + return TCPStateClosed, nil + } + return drv.dev.GetClientState(drv.sock) +} + +func (drv *Driver) stop() error { + if drv.sock == NoSocketAvail { + return nil + } + drv.dev.StopClient(drv.sock) + for t := newTimer(5 * time.Second); !t.Expired(); { + st, _ := drv.status() + if st == TCPStateClosed { + break + } + // FIXME: without the time.Sleep below this blocks until TCPStateClosed, + // however with it got goroutine stack overflows; not sure if this is still + // an issue so should investigate further + //time.Sleep(1 * time.Millisecond) + } + drv.sock = NoSocketAvail + return nil +} diff --git a/wifinina/timer.go b/wifinina/timer.go new file mode 100644 index 0000000..ca58e82 --- /dev/null +++ b/wifinina/timer.go @@ -0,0 +1,28 @@ +package wifinina + +import "time" + +func wait(duration time.Duration) { + newTimer(duration).WaitUntilExpired() +} + +type timer struct { + start int64 + interval int64 +} + +func newTimer(interval time.Duration) timer { + return timer{ + start: time.Now().UnixNano(), + interval: int64(interval), + } +} + +func (t timer) Expired() bool { + return time.Now().UnixNano() > (t.start + t.interval) +} + +func (t timer) WaitUntilExpired() { + for !t.Expired() { + } +} diff --git a/wifinina/wifinina.go b/wifinina/wifinina.go new file mode 100644 index 0000000..69f9558 --- /dev/null +++ b/wifinina/wifinina.go @@ -0,0 +1,1032 @@ +package wifinina + +import ( + "encoding/binary" + "fmt" + "time" + + "machine" + + "tinygo.org/x/drivers/net" +) + +const _debug = false + +const ( + MaxSockets = 4 + MaxNetworks = 10 + MaxAttempts = 10 + + MaxLengthSSID = 32 + MaxLengthWPAKey = 63 + MaxLengthWEPKey = 13 + + LengthMacAddress = 6 + LengthIPV4 = 4 + + WlFailure = -1 + WlSuccess = 1 + + StatusNoShield ConnectionStatus = 255 + StatusIdle ConnectionStatus = 0 + StatusNoSSIDAvail ConnectionStatus = 1 + StatusScanCompleted ConnectionStatus = 2 + StatusConnected ConnectionStatus = 3 + StatusConnectFailed ConnectionStatus = 4 + StatusConnectionLost ConnectionStatus = 5 + StatusDisconnected ConnectionStatus = 6 + + 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 + /* + // Default state value for Wifi state field + #define NA_STATE -1 + */ + + FlagCmd = 0 + FlagReply = 1 << 7 + FlagData = 0x40 + + NinaCmdPos = 1 + NinaParamLenPos = 2 + + 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 + // TEST_CMD = 0x13 + + 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 + // GET_IDX_SSID_CMD = 0x31, + // GET_TEST_CMD = 0x38 + + // All command with DATA_FLAG 0x40 send a 16bit Len + CmdSendDataTCP = 0x44 + CmdGetDatabufTCP = 0x45 + CmdInsertDataBuf = 0x46 + + // regular format commands + CmdSetPinMode = 0x50 + CmdSetDigitalWrite = 0x51 + CmdSetAnalogWrite = 0x52 + + ErrTimeoutSlaveReady Error = 0x01 + ErrTimeoutSlaveSelect Error = 0x02 + ErrCheckStartCmd Error = 0x03 + ErrWaitRsp Error = 0x04 + ErrUnexpectedLength Error = 0xE0 + ErrNoParamsReturned Error = 0xE1 + ErrIncorrectSentinel Error = 0xE2 + ErrCmdErrorReceived Error = 0xEF + ErrNotImplemented Error = 0xF0 + ErrUnknownHost Error = 0xF1 + ErrSocketAlreadySet Error = 0xF2 + ErrConnectionTimeout Error = 0xF3 + ErrNoData Error = 0xF4 + ErrDataNotWritten Error = 0xF5 + ErrCheckDataError Error = 0xF6 + ErrBufferTooSmall Error = 0xF7 + ErrNoSocketAvail Error = 0xFF + + NoSocketAvail uint8 = 0xFF +) + +const ( + ProtoModeTCP = iota + ProtoModeUDP + ProtoModeTLS + ProtoModeMul +) + +type ConnectionStatus uint8 + +func (c ConnectionStatus) String() string { + switch c { + case StatusIdle: + return "Idle" + case StatusNoSSIDAvail: + return "No SSID Available" + case StatusScanCompleted: + return "Scan Completed" + case StatusConnected: + return "Connected" + case StatusConnectFailed: + return "Connect Failed" + case StatusConnectionLost: + return "Connection Lost" + case StatusDisconnected: + return "Disconnected" + case StatusNoShield: + return "No Shield" + default: + return "Unknown" + } +} + +type EncryptionType uint8 + +func (e EncryptionType) String() string { + switch e { + case EncTypeTKIP: + return "TKIP" + case EncTypeCCMP: + return "WPA2" + case EncTypeWEP: + return "WEP" + case EncTypeNone: + return "None" + case EncTypeAuto: + return "Auto" + default: + return "Unknown" + } +} + +type IPAddress string // TODO: does WiFiNINA support ipv6??? + +func (addr IPAddress) String() string { + if len(addr) < 4 { + return "" + } + return fmt.Sprintf("%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]) +} + +func ParseIPv4(s string) (IPAddress, error) { + var v0, v1, v2, v3 uint8 + if _, err := fmt.Sscanf(s, "%d.%d.%d.%d", &v0, &v1, &v2, &v3); err != nil { + return "", err + } + return IPAddress([]byte{v0, v1, v2, v3}), nil +} + +func (addr IPAddress) AsUint32() uint32 { + if len(addr) < 4 { + return 0 + } + b := []byte(string(addr)) + return binary.BigEndian.Uint32(b[0:4]) +} + +type MACAddress uint64 + +func (addr MACAddress) String() string { + return fmt.Sprintf("%016X", uint64(addr)) +} + +type Error uint8 + +func (err Error) Error() string { + return fmt.Sprintf("wifinina error: 0x%02X", uint8(err)) +} + +// Cmd Struct Message */ +// ._______________________________________________________________________. +// | START CMD | C/R | CMD | N.PARAM | PARAM LEN | PARAM | .. | END CMD | +// |___________|______|______|_________|___________|________|____|_________| +// | 8 bit | 1bit | 7bit | 8bit | 8bit | nbytes | .. | 8bit | +// |___________|______|______|_________|___________|________|____|_________| +// +type command struct { + cmd uint8 + reply bool + params []int + paramData []byte +} + +type Device struct { + SPI machine.SPI + CS machine.Pin + ACK machine.Pin + GPIO0 machine.Pin + RESET machine.Pin + + buf [64]byte + ssids [10]string +} + +func (d *Device) Configure() { + + net.UseDriver(d.NewDriver()) + + d.CS.Configure(machine.PinConfig{machine.PinOutput}) + d.ACK.Configure(machine.PinConfig{machine.PinInput}) + d.RESET.Configure(machine.PinConfig{machine.PinOutput}) + d.GPIO0.Configure(machine.PinConfig{machine.PinOutput}) + + d.GPIO0.High() + d.CS.High() + d.RESET.Low() + time.Sleep(1 * time.Millisecond) + d.RESET.High() + time.Sleep(1 * time.Millisecond) + + d.GPIO0.Low() + d.GPIO0.Configure(machine.PinConfig{machine.PinInput}) + +} + +// ----------- client methods (should this be a separate struct?) ------------ + +func (d *Device) StartClient(addr uint32, port uint16, sock uint8, mode uint8) error { + if _debug { + println("[StartClient] called StartClient()\r") + fmt.Printf("[StartClient] addr: % 02X, port: %d, sock: %d\r\n", addr, port, sock) + } + if err := d.waitForSlaveSelect(); err != nil { + d.spiSlaveDeselect() + return err + } + l := d.sendCmd(CmdStartClientTCP, 4) + l += d.sendParam32(addr, false) + l += d.sendParam16(port, false) + l += d.sendParam8(sock, false) + l += d.sendParam8(mode, true) + d.addPadding(l) + d.spiSlaveDeselect() + _, err := d.waitRspCmd1(CmdStartClientTCP) + return err +} + +func (d *Device) GetSocket() (uint8, error) { + return d.getUint8(d.req0(CmdGetSocket)) +} + +func (d *Device) GetClientState(sock uint8) (uint8, error) { + return d.getUint8(d.reqUint8(CmdGetClientStateTCP, sock)) +} + +func (d *Device) SendData(buf []byte, sock uint8) (uint16, error) { + if err := d.waitForSlaveSelect(); err != nil { + d.spiSlaveDeselect() + return 0, err + } + l := d.sendCmd(CmdSendDataTCP, 2) + l += d.sendParamBuf([]byte{sock}, false) + l += d.sendParamBuf(buf, true) + d.addPadding(l) + d.spiSlaveDeselect() + return d.getUint16(d.waitRspCmd1(CmdSendDataTCP)) +} + +func (d *Device) CheckDataSent(sock uint8) (bool, error) { + var lastErr error + for timeout := 0; timeout < 10; timeout++ { + sent, err := d.getUint8(d.reqUint8(CmdDataSentTCP, sock)) + if err != nil { + lastErr = err + } + if sent > 0 { + return true, nil + } + wait(100 * time.Microsecond) + } + return false, lastErr +} + +func (d *Device) GetDataBuf(sock uint8, buf []byte) (int, error) { + if err := d.waitForSlaveSelect(); err != nil { + d.spiSlaveDeselect() + return 0, err + } + p := uint16(len(buf)) + l := d.sendCmd(CmdGetDatabufTCP, 2) + l += d.sendParamBuf([]byte{sock}, false) + l += d.sendParamBuf([]byte{uint8(p & 0x00FF), uint8((p) >> 8)}, true) + d.addPadding(l) + d.spiSlaveDeselect() + if err := d.waitForSlaveSelect(); err != nil { + d.spiSlaveDeselect() + return 0, err + } + n, err := d.waitRspBuf16(CmdGetDatabufTCP, buf) + d.spiSlaveDeselect() + return int(n), err +} + +func (d *Device) StopClient(sock uint8) error { + if _debug { + println("[StopClient] called StopClient()\r") + } + _, err := d.getUint8(d.reqUint8(CmdStopClientTCP, sock)) + return err +} + +// ---------- /client methods (should this be a separate struct?) ------------ + +/* + static bool startServer(uint16_t port, uint8_t sock); + static uint8_t getServerState(uint8_t sock); + static bool getData(uint8_t connId, uint8_t *data, bool peek, bool* connClose); + static int getDataBuf(uint8_t connId, uint8_t *buf, uint16_t bufSize); + static bool sendData(uint8_t sock, const uint8_t *data, uint16_t len); + static bool sendDataUdp(uint8_t sock, const char* host, uint16_t port, const uint8_t *data, uint16_t len); + static uint16_t availData(uint8_t connId); + + + static bool ping(const char *host); + static void reset(); + + static void getRemoteIpAddress(IPAddress& ip); + static uint16_t getRemotePort(); +*/ + +func (d *Device) Disconnect() error { + _, err := d.req1(CmdDisconnect) + return err +} + +func (d *Device) GetFwVersion() (string, error) { + return d.getString(d.req0(CmdGetFwVersion)) +} + +func (d *Device) GetConnectionStatus() (ConnectionStatus, error) { + status, err := d.getUint8(d.req0(CmdGetConnStatus)) + return ConnectionStatus(status), err +} + +func (d *Device) GetCurrentEncryptionType() (EncryptionType, error) { + enctype, err := d.getUint8(d.req1(CmdGetCurrEncrType)) + return EncryptionType(enctype), err +} + +func (d *Device) GetCurrentBSSID() (MACAddress, error) { + return d.getMACAddress(d.req1(CmdGetCurrBSSID)) +} + +func (d *Device) GetCurrentRSSI() (int32, error) { + return d.getInt32(d.req1(CmdGetCurrBSSID)) +} + +func (d *Device) GetCurrentSSID() (string, error) { + return d.getString(d.req1(CmdGetCurrSSID)) +} + +func (d *Device) GetMACAddress() (MACAddress, error) { + return d.getMACAddress(d.req1(CmdGetMACAddr)) +} + +func (d *Device) GetIP() (ip, subnet, gateway IPAddress, err error) { + sl := make([]string, 3) + if l, err := d.reqRspStr1(CmdGetIPAddr, dummyData, sl); err != nil { + return "", "", "", err + } else if l != 3 { + return "", "", "", ErrUnexpectedLength + } + return IPAddress(sl[0]), IPAddress(sl[1]), IPAddress(sl[2]), err +} + +func (d *Device) GetHostByName(hostname string) (IPAddress, error) { + ok, err := d.getUint8(d.reqStr(CmdReqHostByName, hostname)) + if err != nil { + return "", err + } + if ok != 1 { + return "", ErrUnknownHost + } + ip, err := d.getString(d.req0(CmdGetHostByName)) + return IPAddress(ip), err +} + +func (d *Device) GetNetworkBSSID(idx int) (MACAddress, error) { + if idx < 0 || idx >= MaxNetworks { + return 0, nil + } + return d.getMACAddress(d.reqUint8(CmdGetIdxBSSID, uint8(idx))) +} + +func (d *Device) GetNetworkChannel(idx int) (uint8, error) { + if idx < 0 || idx >= MaxNetworks { + return 0, nil + } + return d.getUint8(d.reqUint8(CmdGetIdxChannel, uint8(idx))) +} + +func (d *Device) GetNetworkEncrType(idx int) (EncryptionType, error) { + if idx < 0 || idx >= MaxNetworks { + return 0, nil + } + enctype, err := d.getUint8(d.reqUint8(CmdGetIdxEncrType, uint8(idx))) + return EncryptionType(enctype), err +} + +func (d *Device) GetNetworkRSSI(idx int) (int32, error) { + if idx < 0 || idx >= MaxNetworks { + return 0, nil + } + return d.getInt32(d.reqUint8(CmdGetIdxRSSI, uint8(idx))) +} + +func (d *Device) GetNetworkSSID(idx int) string { + if idx < 0 || idx >= MaxNetworks { + return "" + } + return d.ssids[idx] +} + +func (d *Device) GetReasonCode() (uint8, error) { + return d.getUint8(d.req0(CmdGetReasonCode)) +} + +func (d *Device) GetTime() (string, error) { + return d.getString(d.req0(CmdGetTime)) +} + +func (d *Device) GetTemperature() (float32, error) { + return d.getFloat32(d.req0(CmdGetTemperature)) +} + +func (d *Device) Ping(ip IPAddress, ttl uint8) int16 { + return 0 +} + +func (d *Device) SetDebug(on bool) error { + var v uint8 + if on { + v = 1 + } + _, err := d.reqUint8(CmdSetDebug, v) + return err +} + +func (d *Device) SetNetwork(ssid string) error { + _, err := d.reqStr(CmdSetNet, ssid) + return err +} + +func (d *Device) SetPassphrase(ssid string, passphrase string) error { + _, err := d.reqStr2(CmdSetPassphrase, ssid, passphrase) + return err +} + +func (d *Device) SetKey(ssid string, index uint8, key string) error { + return ErrNotImplemented +} + +func (d *Device) SetNetworkForAP(ssid string) error { + _, err := d.reqStr(CmdSetAPNet, ssid) + return err +} + +func (d *Device) SetPassphraseForAP(ssid string, passphrase string) error { + _, err := d.reqStr2(CmdSetAPPassphrase, ssid, passphrase) + return err +} + +func (d *Device) SetIP(which uint8, ip uint32, gw uint32, subnet uint32) error { + return ErrNotImplemented +} + +func (d *Device) SetDNS(which uint8, dns1 uint32, dns2 uint32) error { + return ErrNotImplemented +} + +func (d *Device) SetHostname(hostname string) error { + return ErrNotImplemented +} + +func (d *Device) SetPowerMode(mode uint8) error { + _, err := d.reqUint8(CmdSetPowerMode, mode) + return err +} + +func (d *Device) ScanNetworks() (uint8, error) { + return d.reqRspStr0(CmdScanNetworks, d.ssids[:]) +} + +func (d *Device) StartScanNetworks() (uint8, error) { + return d.getUint8(d.req0(CmdStartServerTCP)) +} + +func (d *Device) getString(l uint8, err error) (string, error) { + if err != nil { + return "", err + } + return string(d.buf[0:l]), err +} + +func (d *Device) getUint8(l uint8, err error) (uint8, error) { + if err != nil { + return 0, err + } + if l != 1 { + if _debug { + println("expected length 1, was actually", l, "\r") + } + return 0, ErrUnexpectedLength + } + return d.buf[0], err +} + +func (d *Device) getUint16(l uint8, err error) (uint16, error) { + if err != nil { + return 0, err + } + if l != 2 { + if _debug { + println("expected length 2, was actually", l, "\r") + } + return 0, ErrUnexpectedLength + } + return binary.BigEndian.Uint16(d.buf[0:2]), err +} + +func (d *Device) getUint32(l uint8, err error) (uint32, error) { + if err != nil { + return 0, err + } + if l != 4 { + return 0, ErrUnexpectedLength + } + return binary.LittleEndian.Uint32(d.buf[0:4]), err +} + +func (d *Device) getInt32(l uint8, err error) (int32, error) { + i, err := d.getUint32(l, err) + return int32(i), err +} + +func (d *Device) getFloat32(l uint8, err error) (float32, error) { + i, err := d.getUint32(l, err) + return float32(i), err +} + +func (d *Device) getMACAddress(l uint8, err error) (MACAddress, error) { + if err != nil { + return 0, err + } + if l != 6 { + return 0, ErrUnexpectedLength + } + return MACAddress(binary.LittleEndian.Uint64(d.buf[0:8]) >> 16), err +} + +// req0 sends a command to the device with no request parameters +func (d *Device) req0(cmd uint8) (l uint8, err error) { + if err := d.sendCmd0(cmd); err != nil { + return 0, err + } + return d.waitRspCmd1(cmd) +} + +// req1 sends a command to the device with a single dummy parameters of 0xFF +func (d *Device) req1(cmd uint8) (l uint8, err error) { + return d.reqUint8(cmd, dummyData) +} + +// reqUint8 sends a command to the device with a single uint8 parameter +func (d *Device) reqUint8(cmd uint8, data uint8) (l uint8, err error) { + if err := d.sendCmdPadded1(cmd, data); err != nil { + return 0, err + } + return d.waitRspCmd1(cmd) +} + +// reqStr sends a command to the device with a single string parameter +func (d *Device) reqStr(cmd uint8, p1 string) (uint8, error) { + if err := d.sendCmdStr(cmd, p1); err != nil { + return 0, err + } + return d.waitRspCmd1(cmd) +} + +// reqStr sends a command to the device with 2 string parameters +func (d *Device) reqStr2(cmd uint8, p1 string, p2 string) (uint8, error) { + if err := d.sendCmdStr2(cmd, p1, p2); err != nil { + return 0, err + } + return d.waitRspCmd1(cmd) +} + +// reqStrRsp0 sends a command passing a string slice for the response +func (d *Device) reqRspStr0(cmd uint8, sl []string) (l uint8, err error) { + if err := d.sendCmd0(cmd); err != nil { + return 0, err + } + defer d.spiSlaveDeselect() + if err = d.waitForSlaveSelect(); err != nil { + return + } + return d.waitRspStr(cmd, sl) +} + +// reqStrRsp1 sends a command with a uint8 param and a string slice for the response +func (d *Device) reqRspStr1(cmd uint8, data uint8, sl []string) (uint8, error) { + if err := d.sendCmdPadded1(cmd, data); err != nil { + return 0, err + } + defer d.spiSlaveDeselect() + if err := d.waitForSlaveSelect(); err != nil { + return 0, err + } + return d.waitRspStr(cmd, sl) +} + +func (d *Device) sendCmd0(cmd uint8) error { + defer d.spiSlaveDeselect() + if err := d.waitForSlaveSelect(); err != nil { + return err + } + d.sendCmd(cmd, 0) + return nil +} + +func (d *Device) sendCmdPadded1(cmd uint8, data uint8) error { + defer d.spiSlaveDeselect() + if err := d.waitForSlaveSelect(); err != nil { + return err + } + d.sendCmd(cmd, 1) + d.sendParam8(data, true) + d.SPI.Transfer(dummyData) + d.SPI.Transfer(dummyData) + return nil +} + +func (d *Device) sendCmdStr(cmd uint8, p1 string) (err error) { + defer d.spiSlaveDeselect() + if err := d.waitForSlaveSelect(); err != nil { + return err + } + l := d.sendCmd(cmd, 1) + l += d.sendParamStr(p1, false) + d.addPadding(l) + return nil +} + +func (d *Device) sendCmdStr2(cmd uint8, p1 string, p2 string) (err error) { + defer d.spiSlaveDeselect() + if err := d.waitForSlaveSelect(); err != nil { + return err + } + l := d.sendCmd(cmd, 2) + l += d.sendParamStr(p1, false) + l += d.sendParamStr(p2, true) + d.addPadding(l) + return nil +} + +func (d *Device) waitRspCmd1(cmd uint8) (l uint8, err error) { + defer d.spiSlaveDeselect() + if err = d.waitForSlaveSelect(); err != nil { + return + } + return d.waitRspCmd(cmd, 1) +} + +func (d *Device) sendCmd(cmd uint8, numParam uint8) (l int) { + if _debug { + fmt.Printf( + "sendCmd: %02X %02X %02X", + CmdStart, cmd & ^(uint8(FlagReply)), numParam) + } + l = 3 + d.SPI.Transfer(CmdStart) + d.SPI.Transfer(cmd & ^(uint8(FlagReply))) + d.SPI.Transfer(numParam) + if numParam == 0 { + d.SPI.Transfer(CmdEnd) + l += 1 + if _debug { + fmt.Printf(" %02X", CmdEnd) + } + } + if _debug { + fmt.Printf(" (%d)\r\n", l) + } + return +} + +func (d *Device) sendParamLen16(p uint16) (l int) { + d.SPI.Transfer(uint8(p >> 8)) + d.SPI.Transfer(uint8(p & 0xFF)) + if _debug { + fmt.Printf(" %02X %02X", uint8(p>>8), uint8(p&0xFF)) + } + return 2 +} + +func (d *Device) sendParamBuf(p []byte, isLastParam bool) (l int) { + if _debug { + println("sendParamBuf:") + } + l += d.sendParamLen16(uint16(len(p))) + for _, b := range p { + if _debug { + fmt.Printf(" %02X", b) + } + d.SPI.Transfer(b) + l += 1 + } + if isLastParam { + if _debug { + fmt.Printf(" %02X", CmdEnd) + } + d.SPI.Transfer(CmdEnd) + l += 1 + } + if _debug { + fmt.Printf(" (%d) \r\n", l) + } + return +} + +func (d *Device) sendParamStr(p string, isLastParam bool) (l int) { + l = len(p) + d.SPI.Transfer(uint8(l)) + d.SPI.Tx([]byte(p), nil) + if isLastParam { + d.SPI.Transfer(CmdEnd) + l += 1 + } + return +} + +func (d *Device) sendParam8(p uint8, isLastParam bool) (l int) { + if _debug { + println("sendParam8:", p, "lastParam:", isLastParam, "\r") + } + l = 2 + d.SPI.Transfer(1) + d.SPI.Transfer(p) + if isLastParam { + d.SPI.Transfer(CmdEnd) + l += 1 + } + return +} + +func (d *Device) sendParam16(p uint16, isLastParam bool) (l int) { + l = 3 + d.SPI.Transfer(2) + d.SPI.Transfer(uint8(p >> 8)) + d.SPI.Transfer(uint8(p & 0xFF)) + if isLastParam { + d.SPI.Transfer(CmdEnd) + l += 1 + } + return +} + +func (d *Device) sendParam32(p uint32, isLastParam bool) (l int) { + l = 5 + d.SPI.Transfer(4) + d.SPI.Transfer(uint8(p >> 24)) + d.SPI.Transfer(uint8(p >> 16)) + d.SPI.Transfer(uint8(p >> 8)) + d.SPI.Transfer(uint8(p & 0xFF)) + if isLastParam { + d.SPI.Transfer(CmdEnd) + l += 1 + } + return +} + +func (d *Device) checkStartCmd() (bool, error) { + check, err := d.waitSpiChar(CmdStart) + if err != nil { + return false, err + } + if !check { + return false, ErrCheckStartCmd + } + return true, nil +} + +func (d *Device) waitForSlaveSelect() (err error) { + err = d.waitForSlaveReady() + if err == nil { + err = d.spiSlaveSelect() + } + return +} + +func (d *Device) waitForSlaveReady() error { + if _debug { + println("waitForSlaveReady()\r") + } + for t := newTimer(10 * time.Second); !(d.ACK.Get() == false); { + if t.Expired() { + return ErrTimeoutSlaveReady + } + } + return nil +} + +func (d *Device) spiSlaveSelect() error { + if _debug { + println("spiSlaveSelect()\r") + } + d.CS.Low() + for t := newTimer(5 * time.Millisecond); !t.Expired(); { + if d.ACK.Get() { + return nil + } + } + return ErrTimeoutSlaveSelect +} + +func (d *Device) spiSlaveDeselect() { + if _debug { + println("spiSlaveDeselect\r") + } + d.CS.High() +} + +func (d *Device) waitSpiChar(wait byte) (bool, error) { + var timeout = 1000 + var read byte + for first := true; first || (timeout > 0 && read != wait); timeout-- { + first = false + d.readParam(&read) + if read == CmdErr { + return false, ErrCmdErrorReceived + } + } + if _debug && read != wait { + fmt.Printf("read: %02X, wait: %02X\r\n", read, wait) + } + return read == wait, nil +} + +func (d *Device) waitRspCmd(cmd uint8, np uint8) (l uint8, err error) { + if _debug { + println("waitRspCmd") + } + var check bool + var data byte + if check, err = d.checkStartCmd(); !check { + return + } + if check = d.readAndCheckByte(cmd|FlagReply, &data); !check { + return + } + if check = d.readAndCheckByte(np, &data); check { + d.readParam(&l) + for i := uint8(0); i < l; i++ { + d.readParam(&d.buf[i]) + } + } + if !d.readAndCheckByte(CmdEnd, &data) { + err = ErrIncorrectSentinel + } + return +} + +func (d *Device) waitRspBuf16(cmd uint8, buf []byte) (l uint16, err error) { + if _debug { + println("waitRspBuf16") + } + var check bool + var data byte + if check, err = d.checkStartCmd(); !check { + return + } + if check = d.readAndCheckByte(cmd|FlagReply, &data); !check { + return + } + if check = d.readAndCheckByte(1, &data); check { + l, _ = d.readParamLen16() + for i := uint16(0); i < l; i++ { + d.readParam(&buf[i]) + } + } + if !d.readAndCheckByte(CmdEnd, &data) { + err = ErrIncorrectSentinel + } + return +} + +func (d *Device) waitRspStr(cmd uint8, sl []string) (numRead uint8, err error) { + if _debug { + println("waitRspStr") + } + var check bool + var data byte + if check, err = d.checkStartCmd(); !check { + return + } + if check = d.readAndCheckByte(cmd|FlagReply, &data); !check { + return + } + numRead, _ = d.SPI.Transfer(dummyData) + if numRead == 0 { + return 0, ErrNoParamsReturned + } + maxNumRead := uint8(len(sl)) + for j, l := uint8(0), uint8(0); j < numRead; j++ { + d.readParam(&l) + for i := uint8(0); i < l; i++ { + d.readParam(&d.buf[i]) + } + if j < maxNumRead { + sl[j] = string(d.buf[0:l]) + if _debug { + fmt.Printf("str %d (%d) - %08X\r\n", j, l, []byte(sl[j])) + } + } + } + for j := numRead; j < maxNumRead; j++ { + if _debug { + println("str", j, "\"\"\r") + } + sl[j] = "" + } + if !d.readAndCheckByte(CmdEnd, &data) { + err = ErrIncorrectSentinel + } + if numRead > maxNumRead { + numRead = maxNumRead + } + return +} + +func (d *Device) readAndCheckByte(check byte, read *byte) bool { + d.readParam(read) + return (*read == check) +} + +// readParamLen16 reads 2 bytes from the SPI bus (MSB first), returning uint16 +func (d *Device) readParamLen16() (v uint16, err error) { + if b, err := d.SPI.Transfer(0xFF); err == nil { + v |= uint16(b << 8) + if b, err = d.SPI.Transfer(0xFF); err == nil { + v |= uint16(b) + } + } + return +} + +func (d *Device) readParam(b *byte) (err error) { + *b, err = d.SPI.Transfer(0xFF) + return +} + +func (d *Device) addPadding(l int) { + if _debug { + println("addPadding", l, "\r") + } + for i := (4 - (l % 4)) & 3; i > 0; i-- { + if _debug { + println("padding\r") + } + d.SPI.Transfer(dummyData) + } +}