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
This commit is contained in:
BCG
2019-12-22 04:25:10 -05:00
committed by Ron Evans
parent cc5ecafacf
commit e80a22d0ba
10 changed files with 1888 additions and 0 deletions
+4
View File
@@ -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
+1
View File
@@ -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 |
+149
View File
@@ -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)
}
}
+162
View File
@@ -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)
}
}
+143
View File
@@ -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")
}
+153
View File
@@ -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")
}
+3
View File
@@ -0,0 +1,3 @@
WiFiNINA protocol
=================
+213
View File
@@ -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
}
+28
View File
@@ -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() {
}
}
+1032
View File
File diff suppressed because it is too large Load Diff