mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-24 16:39:02 +00:00
Add network device driver model, netdev
This commit is contained in:
+1
-1
@@ -6,7 +6,7 @@ The way this driver works is by using the SPI interface of your microcontroller
|
||||
|
||||
## Using the WiFiNINA Driver
|
||||
|
||||
For information on how to use this driver, please take a look at the examples located in the [examples/wifinina](../examples/wifinina) directory.
|
||||
For information on how to use this driver, please take a look at the examples located in the [examples/net](../examples/net) directory.
|
||||
|
||||
## Firmware
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package wifinina
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/net"
|
||||
)
|
||||
|
||||
func (d *Device) ConnectToAccessPoint(ssid, pass string, timeout time.Duration) error {
|
||||
if len(ssid) == 0 {
|
||||
return net.ErrWiFiMissingSSID
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
d.SetPassphrase(ssid, pass)
|
||||
|
||||
for time.Since(start) < timeout {
|
||||
st, _ := d.GetConnectionStatus()
|
||||
if st == StatusConnected {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
return net.ErrWiFiConnectTimeout
|
||||
}
|
||||
|
||||
func (d *Device) GetClientIP() (string, error) {
|
||||
ip, _, _, err := d.GetIP()
|
||||
return ip.String(), err
|
||||
}
|
||||
+15
-3
@@ -1,5 +1,17 @@
|
||||
//go:build wifidebug
|
||||
|
||||
package wifinina
|
||||
|
||||
var _debug = true
|
||||
type debug uint8
|
||||
|
||||
const (
|
||||
debugBasic debug = 1 << iota // show fw version, mac addr, etc
|
||||
debugNetdev // show netdev entry points
|
||||
debugCmd // show non-chatty wifinina cmds
|
||||
debugDetail // show chatty wifinina cmds
|
||||
|
||||
debugOff = 0
|
||||
debugAll = debugBasic | debugNetdev | debugCmd | debugDetail
|
||||
)
|
||||
|
||||
func debugging(want debug) bool {
|
||||
return (_debug & want) != 0
|
||||
}
|
||||
|
||||
@@ -1,323 +0,0 @@
|
||||
package wifinina
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/net/http"
|
||||
)
|
||||
|
||||
func (d *Device) ListenAndServe(addr string, handler http.Handler) error {
|
||||
|
||||
if handler == nil {
|
||||
handler = http.DefaultServeMux
|
||||
}
|
||||
|
||||
server := newServer(d, handler)
|
||||
|
||||
if err := server.listen(addr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
client, err := server.accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := client.handleHTTP(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = client.stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Server stuff
|
||||
|
||||
type server struct {
|
||||
device *Device
|
||||
handler http.Handler
|
||||
sock uint8
|
||||
clients map[uint8]*client // keyed by client sock
|
||||
}
|
||||
|
||||
func newServer(device *Device, handler http.Handler) *server {
|
||||
return &server{
|
||||
device: device,
|
||||
handler: handler,
|
||||
sock: NoSocketAvail,
|
||||
clients: make(map[uint8]*client),
|
||||
}
|
||||
}
|
||||
|
||||
func portFromAddr(addr string) (uint16, error) {
|
||||
// ignore anything before ':' in address
|
||||
i := strings.LastIndex(addr, ":")
|
||||
if i < 0 {
|
||||
return 0, fmt.Errorf("Missing ':' in address")
|
||||
}
|
||||
v, err := strconv.ParseUint(addr[i+1:], 10, 16)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Parsing address err: %s", err)
|
||||
}
|
||||
return uint16(v), nil
|
||||
}
|
||||
|
||||
func (s *server) listen(addr string) error {
|
||||
port, err := portFromAddr(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Getting port err: %s", err)
|
||||
}
|
||||
|
||||
s.sock, err = s.device.GetSocket()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Getting socket err: %s", err)
|
||||
}
|
||||
if s.sock == NoSocketAvail {
|
||||
return fmt.Errorf("No socket available")
|
||||
}
|
||||
|
||||
return s.device.StartServer(port, s.sock, ProtoModeTCP)
|
||||
}
|
||||
|
||||
func (s *server) availServer(sock uint8) (uint8, error) {
|
||||
d := s.device
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
if err := d.waitForChipSelect(); err != nil {
|
||||
d.spiChipDeselect()
|
||||
return NoSocketAvail, fmt.Errorf("Wait for CS: %s", err)
|
||||
}
|
||||
|
||||
l := d.sendCmd(CmdAvailDataTCP, 1)
|
||||
l += d.sendParam8(sock, true)
|
||||
d.addPadding(l)
|
||||
d.spiChipDeselect()
|
||||
_, err := d.waitRspCmd1(CmdAvailDataTCP)
|
||||
if err != nil {
|
||||
return NoSocketAvail, fmt.Errorf("Wait for Rsp: %s", err)
|
||||
}
|
||||
newsock, err := d.getUint16(2, err)
|
||||
if err != nil {
|
||||
return NoSocketAvail, fmt.Errorf("getUint16: %s", err)
|
||||
}
|
||||
return uint8(newsock >> 8), nil
|
||||
}
|
||||
|
||||
func (s *server) accept() (*client, error) {
|
||||
|
||||
for {
|
||||
sock, err := s.availServer(s.sock)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("accept: %w", err)
|
||||
}
|
||||
|
||||
if sock == NoSocketAvail {
|
||||
continue
|
||||
}
|
||||
|
||||
if client, ok := s.clients[sock]; ok {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
client := newClient(s, sock)
|
||||
s.clients[sock] = client
|
||||
|
||||
return client, nil
|
||||
}
|
||||
}
|
||||
|
||||
// client stuff
|
||||
|
||||
type client struct {
|
||||
server *server
|
||||
device *Device
|
||||
sock uint8
|
||||
|
||||
// HTTP request
|
||||
req *http.Request
|
||||
reqBuf bytes.Buffer
|
||||
readBuf [256]byte
|
||||
|
||||
// HTTP response
|
||||
res bytes.Buffer
|
||||
resHdr http.Header
|
||||
resBuf bytes.Buffer
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func newClient(server *server, sock uint8) *client {
|
||||
return &client{
|
||||
server: server,
|
||||
device: server.device,
|
||||
sock: sock,
|
||||
}
|
||||
}
|
||||
|
||||
// client implements http.ResponseWriter interface
|
||||
|
||||
func (c *client) Header() http.Header {
|
||||
return c.resHdr
|
||||
}
|
||||
|
||||
func (c *client) Write(b []byte) (int, error) {
|
||||
return c.resBuf.Write(b)
|
||||
}
|
||||
|
||||
func (c *client) WriteHeader(statusCode int) {
|
||||
c.statusCode = statusCode
|
||||
}
|
||||
|
||||
func (c *client) status() uint8 {
|
||||
d := c.device
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
if err := d.waitForChipSelect(); err != nil {
|
||||
d.spiChipDeselect()
|
||||
return 0
|
||||
}
|
||||
|
||||
l := d.sendCmd(CmdGetClientStateTCP, 1)
|
||||
l += d.sendParam8(c.sock, true)
|
||||
d.addPadding(l)
|
||||
d.spiChipDeselect()
|
||||
_, err := d.waitRspCmd1(CmdGetClientStateTCP)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
status, err := d.getUint8(1, err)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
func (c *client) stop() error {
|
||||
if err := c.device.StopClient(c.sock); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait max 5 secs for the connection to close
|
||||
for i := 0; i < 50 && c.status() != uint8(TCPStateClosed); i++ {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
if c.status() != uint8(TCPStateClosed) {
|
||||
return fmt.Errorf("stop failed, client status %x", c.status())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) handleHTTP() error {
|
||||
|
||||
c.reqBuf.Reset()
|
||||
end := -1
|
||||
|
||||
// read the request
|
||||
|
||||
start := time.Now()
|
||||
for {
|
||||
|
||||
// TODO use Server.ReadTimeout
|
||||
if time.Since(start) > 1*time.Second {
|
||||
return fmt.Errorf("ReadTimeout")
|
||||
}
|
||||
|
||||
n, err := c.device.GetDataBuf(c.sock, c.readBuf[:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetDataBuf: %s", err)
|
||||
}
|
||||
if n == 0 {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
c.reqBuf.Write(c.readBuf[:n])
|
||||
bytesSoFar := c.reqBuf.Bytes()
|
||||
|
||||
if end == -1 {
|
||||
|
||||
// search for blank line marking end-of-header
|
||||
end = bytes.Index(bytesSoFar, []byte("\r\n\r\n"))
|
||||
if end == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
// found end-of-header; parse header
|
||||
end += len([]byte("\r\n\r\n"))
|
||||
bufio := bufio.NewReader(bytes.NewReader(bytesSoFar[:end]))
|
||||
c.req, err = http.ReadRequest(bufio)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
v := c.req.Header.Get("Content-Length")
|
||||
if v == "" {
|
||||
// no body; we're done reading request
|
||||
break
|
||||
}
|
||||
|
||||
length, _ := strconv.Atoi(v)
|
||||
if end+length == len(bytesSoFar) {
|
||||
// got the whole body
|
||||
body := bytes.NewReader(bytesSoFar[end:])
|
||||
c.req.Body = io.NopCloser(body)
|
||||
break
|
||||
}
|
||||
|
||||
// continue reading request...
|
||||
}
|
||||
|
||||
// build the response
|
||||
|
||||
c.statusCode = 200
|
||||
|
||||
c.resHdr = http.Header{}
|
||||
c.resHdr.Add(`Content-Type`, `text/html; charset=UTF-8`)
|
||||
c.resHdr.Add(`Connection`, `close`)
|
||||
|
||||
c.resBuf.Reset()
|
||||
c.server.handler.ServeHTTP(c, c.req)
|
||||
|
||||
c.resHdr.Add(`Content-Length`, fmt.Sprintf("%d", c.resBuf.Len()))
|
||||
|
||||
c.res.Reset()
|
||||
fmt.Fprintf(&c.res, "HTTP/1.1 %d %s\r\n", c.statusCode,
|
||||
http.StatusText(c.statusCode))
|
||||
if err := c.resHdr.Write(&c.res); err != nil {
|
||||
return err
|
||||
}
|
||||
c.res.WriteByte(byte('\n'))
|
||||
c.res.Write(c.resBuf.Bytes())
|
||||
|
||||
// send the response
|
||||
|
||||
written, err := c.device.SendData(c.res.Bytes(), c.sock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if written == 0 {
|
||||
return ErrDataNotWritten
|
||||
}
|
||||
if sent, _ := c.device.CheckDataSent(c.sock); !sent {
|
||||
return ErrCheckDataError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+7
-5
@@ -32,17 +32,18 @@ var (
|
||||
ErrPinNoDevice = errors.New("wifinina pin: device not set")
|
||||
)
|
||||
|
||||
var pinDevice *Device
|
||||
var pinDevice *wifinina
|
||||
|
||||
func pinUseDevice(d *Device) {
|
||||
pinDevice = d
|
||||
func pinUseDevice(w *wifinina) {
|
||||
pinDevice = w
|
||||
}
|
||||
|
||||
func (p Pin) Configure(config PinConfig) error {
|
||||
if pinDevice == nil {
|
||||
return ErrPinNoDevice
|
||||
}
|
||||
return pinDevice.PinMode(uint8(p), uint8(config.Mode))
|
||||
pinDevice.PinMode(uint8(p), uint8(config.Mode))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p Pin) Set(high bool) error {
|
||||
@@ -53,7 +54,8 @@ func (p Pin) Set(high bool) error {
|
||||
if high {
|
||||
value = PinHigh
|
||||
}
|
||||
return pinDevice.DigitalWrite(uint8(p), value)
|
||||
pinDevice.DigitalWrite(uint8(p), value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p Pin) High() error {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
WiFiNINA protocol
|
||||
=================
|
||||
|
||||
-270
@@ -1,270 +0,0 @@
|
||||
package wifinina
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ReadBufferSize = 128
|
||||
)
|
||||
|
||||
type readBuffer struct {
|
||||
data [ReadBufferSize]byte
|
||||
head int
|
||||
size int
|
||||
}
|
||||
|
||||
func (d *Device) GetDNS(domain string) (string, error) {
|
||||
ipAddr, err := d.GetHostByName(domain)
|
||||
return ipAddr.String(), err
|
||||
}
|
||||
|
||||
func (d *Device) ConnectTCPSocket(addr, portStr string) error {
|
||||
return d.connectSocket(addr, portStr, ProtoModeTCP)
|
||||
}
|
||||
|
||||
func (d *Device) ConnectSSLSocket(addr, portStr string) error {
|
||||
return d.connectSocket(addr, portStr, ProtoModeTLS)
|
||||
}
|
||||
|
||||
func (d *Device) connectSocket(addr, portStr string, mode uint8) error {
|
||||
|
||||
d.proto, d.ip, d.port = mode, 0, 0
|
||||
|
||||
// convert port to uint16
|
||||
port, err := convertPort(portStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hostname := addr
|
||||
ip := uint32(0)
|
||||
|
||||
if mode != ProtoModeTLS {
|
||||
// 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 := d.GetHostByName(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hostname = ""
|
||||
ip = ipAddr.AsUint32()
|
||||
}
|
||||
|
||||
// check to see if socket is already set; if so, stop it
|
||||
if d.sock != NoSocketAvail {
|
||||
if err := d.stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// get a socket from the device
|
||||
if d.sock, err = d.GetSocket(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// attempt to start the client
|
||||
if err := d.StartClient(hostname, ip, port, d.sock, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// FIXME: this 4 second timeout is simply mimicking the Arduino driver
|
||||
start := time.Now()
|
||||
for time.Since(start) < 4*time.Second {
|
||||
connected, err := d.IsConnected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if connected {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
|
||||
return ErrConnectionTimeout
|
||||
}
|
||||
|
||||
func convertPort(portStr string) (uint16, error) {
|
||||
p64, err := strconv.ParseUint(portStr, 10, 16)
|
||||
if err != nil {
|
||||
return 0, errors.New("could not convert port to uint16: " + err.Error())
|
||||
}
|
||||
return uint16(p64), nil
|
||||
}
|
||||
|
||||
func (d *Device) ConnectUDPSocket(addr, portStr, lportStr string) (err error) {
|
||||
|
||||
d.proto, d.ip, d.port = ProtoModeUDP, 0, 0
|
||||
|
||||
// convert remote port to uint16
|
||||
if d.port, err = convertPort(portStr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// convert local port to uint16
|
||||
var lport uint16
|
||||
if lport, err = convertPort(lportStr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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 := d.GetHostByName(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.ip = ipAddr.AsUint32()
|
||||
|
||||
// check to see if socket is already set; if so, stop it
|
||||
// TODO: we can probably have more than one socket at once right?
|
||||
if d.sock != NoSocketAvail {
|
||||
if err := d.stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// get a socket from the device
|
||||
if d.sock, err = d.GetSocket(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// start listening for UDP packets on the local port
|
||||
if err := d.StartServer(lport, d.sock, d.proto); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) DisconnectSocket() error {
|
||||
return d.stop()
|
||||
}
|
||||
|
||||
func (d *Device) StartSocketSend(size int) error {
|
||||
// not needed for WiFiNINA???
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) Response(timeout int) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d *Device) Write(b []byte) (n int, err error) {
|
||||
if d.sock == NoSocketAvail {
|
||||
return 0, ErrNoSocketAvail
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return 0, ErrNoData
|
||||
}
|
||||
if d.proto == ProtoModeUDP {
|
||||
if err := d.StartClient("", d.ip, d.port, d.sock, d.proto); err != nil {
|
||||
return 0, errors.New("error in startClient: " + err.Error())
|
||||
}
|
||||
if _, err := d.InsertDataBuf(b, d.sock); err != nil {
|
||||
return 0, errors.New("error in insertDataBuf: " + err.Error())
|
||||
}
|
||||
if _, err := d.SendUDPData(d.sock); err != nil {
|
||||
return 0, errors.New("error in sendUDPData: " + err.Error())
|
||||
}
|
||||
return len(b), nil
|
||||
} else {
|
||||
written, err := d.SendData(b, d.sock)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if written == 0 {
|
||||
return 0, ErrDataNotWritten
|
||||
}
|
||||
if sent, _ := d.CheckDataSent(d.sock); !sent {
|
||||
return 0, ErrCheckDataError
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (d *Device) ReadSocket(b []byte) (n int, err error) {
|
||||
avail, err := d.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, d.readBuf.data[d.readBuf.head:d.readBuf.head+length])
|
||||
d.readBuf.head += length
|
||||
d.readBuf.size -= length
|
||||
return length, nil
|
||||
}
|
||||
|
||||
// IsSocketDataAvailable returns of there is socket data available
|
||||
func (d *Device) IsSocketDataAvailable() bool {
|
||||
n, err := d.available()
|
||||
return err == nil && n > 0
|
||||
}
|
||||
|
||||
func (d *Device) available() (int, error) {
|
||||
if d.readBuf.size == 0 {
|
||||
n, err := d.GetDataBuf(d.sock, d.readBuf.data[:])
|
||||
if n > 0 {
|
||||
d.readBuf.head = 0
|
||||
d.readBuf.size = n
|
||||
}
|
||||
if err != nil {
|
||||
return int(n), err
|
||||
}
|
||||
}
|
||||
return d.readBuf.size, nil
|
||||
}
|
||||
|
||||
func (d *Device) IsConnected() (bool, error) {
|
||||
if d.sock == NoSocketAvail {
|
||||
return false, nil
|
||||
}
|
||||
s, err := d.status()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
isConnected := !(s == uint8(TCPStateListen) || s == uint8(TCPStateClosed) ||
|
||||
s == uint8(TCPStateFinWait1) || s == uint8(TCPStateFinWait2) || s == uint8(TCPStateTimeWait) ||
|
||||
s == uint8(TCPStateSynSent) || s == uint8(TCPStateSynRcvd) || s == uint8(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 (d *Device) status() (uint8, error) {
|
||||
if d.sock == NoSocketAvail {
|
||||
return uint8(TCPStateClosed), nil
|
||||
}
|
||||
return d.GetClientState(d.sock)
|
||||
}
|
||||
|
||||
func (d *Device) stop() error {
|
||||
if d.sock == NoSocketAvail {
|
||||
return nil
|
||||
}
|
||||
d.StopClient(d.sock)
|
||||
start := time.Now()
|
||||
for time.Since(start) < 5*time.Second {
|
||||
st, _ := d.status()
|
||||
if st == uint8(TCPStateClosed) {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
d.sock = NoSocketAvail
|
||||
return nil
|
||||
}
|
||||
+1680
-869
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user