From 58106b59427d5549452e95517e0916b91972b67b Mon Sep 17 00:00:00 2001 From: sago35 Date: Thu, 22 Jul 2021 13:25:41 +0900 Subject: [PATCH] Add examples/rtl8720dn/udpstation --- examples/rtl8720dn/ntpclient/main.go | 144 +++++++++++++++++++ examples/rtl8720dn/ntpclient/wioterminal.go | 71 +++++++++ examples/rtl8720dn/udpstation/main.go | 89 ++++++++++++ examples/rtl8720dn/udpstation/wioterminal.go | 71 +++++++++ net/net.go | 12 ++ rtl8720dn/netdriver.go | 86 ++++++++++- rtl8720dn/rtl8720dn.go | 1 + 7 files changed, 472 insertions(+), 2 deletions(-) create mode 100644 examples/rtl8720dn/ntpclient/main.go create mode 100644 examples/rtl8720dn/ntpclient/wioterminal.go create mode 100644 examples/rtl8720dn/udpstation/main.go create mode 100644 examples/rtl8720dn/udpstation/wioterminal.go diff --git a/examples/rtl8720dn/ntpclient/main.go b/examples/rtl8720dn/ntpclient/main.go new file mode 100644 index 0000000..8bdb6b4 --- /dev/null +++ b/examples/rtl8720dn/ntpclient/main.go @@ -0,0 +1,144 @@ +// This is an example of using the rtl8720dn driver to implement a NTP client. +// It creates a UDP connection to request the current time and parse the +// response from a NTP server. +package main + +import ( + "errors" + "fmt" + "runtime" + "time" + + "tinygo.org/x/drivers/net" +) + +// IP address of the server aka "hub". Replace with your own info. +// You can override the setting with the init() in another source code. +// func init() { +// ssid = "your-ssid" +// password = "your-password" +// ntpHost = "129.6.15.29" +// debug = true +// } + +var ( + ssid string + password string + ntpHost = "129.6.15.29" + debug = false +) + +const NTP_PACKET_SIZE = 48 + +var b = make([]byte, NTP_PACKET_SIZE) + +func main() { + err := run() + for err != nil { + fmt.Printf("error: %s\r\n", err.Error()) + time.Sleep(5 * time.Second) + } +} + +func run() error { + rtl, err := setupRTL8720DN() + if err != nil { + return err + } + net.UseDriver(rtl) + + err = rtl.ConnectToAP(ssid, password) + if err != nil { + return err + } + + ip, subnet, gateway, err := rtl.GetIP() + if err != nil { + return err + } + fmt.Printf("IP Address : %s\r\n", ip) + fmt.Printf("Mask : %s\r\n", subnet) + fmt.Printf("Gateway : %s\r\n", gateway) + + // now make UDP connection + hip := net.ParseIP(ntpHost) + raddr := &net.UDPAddr{IP: hip, Port: 123} + laddr := &net.UDPAddr{Port: 2390} + conn, err := net.DialUDP("udp", laddr, raddr) + if err != nil { + return err + } + + for { + // send data + println("Requesting NTP time...") + t, err := getCurrentTime(conn) + if err != nil { + message("Error getting current time: %v", err) + } else { + message("NTP time: %v", t) + } + runtime.AdjustTimeOffset(-1 * int64(time.Since(t))) + for i := 0; i < 10; i++ { + message("Current time: %v", time.Now()) + time.Sleep(1 * time.Second) + } + } + +} + +func getCurrentTime(conn *net.UDPSerialConn) (time.Time, error) { + if err := sendNTPpacket(conn); err != nil { + return time.Time{}, err + } + clearBuffer() + for now := time.Now(); time.Since(now) < time.Second; { + time.Sleep(5 * time.Millisecond) + if n, err := conn.Read(b); err != nil { + return time.Time{}, fmt.Errorf("error reading UDP packet: %w", err) + } else if n == 0 { + continue // no packet received yet + } else if n != NTP_PACKET_SIZE { + if n != NTP_PACKET_SIZE { + return time.Time{}, fmt.Errorf("expected NTP packet size of %d: %d", NTP_PACKET_SIZE, n) + } + } + return parseNTPpacket(), nil + } + return time.Time{}, errors.New("no packet received after 1 second") +} + +func sendNTPpacket(conn *net.UDPSerialConn) error { + clearBuffer() + b[0] = 0b11100011 // LI, Version, Mode + b[1] = 0 // Stratum, or type of clock + b[2] = 6 // Polling Interval + b[3] = 0xEC // Peer Clock Precision + // 8 bytes of zero for Root Delay & Root Dispersion + b[12] = 49 + b[13] = 0x4E + b[14] = 49 + b[15] = 52 + if _, err := conn.Write(b); err != nil { + return err + } + return nil +} + +func parseNTPpacket() time.Time { + // the timestamp starts at byte 40 of the received packet and is four bytes, + // this is NTP time (seconds since Jan 1 1900): + t := uint32(b[40])<<24 | uint32(b[41])<<16 | uint32(b[42])<<8 | uint32(b[43]) + const seventyYears = 2208988800 + return time.Unix(int64(t-seventyYears), 0) +} + +func clearBuffer() { + for i := range b { + b[i] = 0 + } +} + +func message(format string, args ...interface{}) { + println(fmt.Sprintf(format, args...), "\r") +} diff --git a/examples/rtl8720dn/ntpclient/wioterminal.go b/examples/rtl8720dn/ntpclient/wioterminal.go new file mode 100644 index 0000000..7602758 --- /dev/null +++ b/examples/rtl8720dn/ntpclient/wioterminal.go @@ -0,0 +1,71 @@ +// +build wioterminal + +package main + +import ( + "device/sam" + "machine" + "runtime/interrupt" + "time" + + "tinygo.org/x/drivers/rtl8720dn" +) + +var ( + uart UARTx +) + +func handleInterrupt(interrupt.Interrupt) { + // should reset IRQ + uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF))) + uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC) +} + +func setupRTL8720DN() (*rtl8720dn.RTL8720DN, error) { + machine.RTL8720D_CHIP_PU.Configure(machine.PinConfig{Mode: machine.PinOutput}) + machine.RTL8720D_CHIP_PU.Low() + time.Sleep(100 * time.Millisecond) + machine.RTL8720D_CHIP_PU.High() + time.Sleep(1000 * time.Millisecond) + waitSerial() + + uart = UARTx{ + UART: &machine.UART{ + Buffer: machine.NewRingBuffer(), + Bus: sam.SERCOM0_USART_INT, + SERCOM: 0, + }, + } + + uart.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, handleInterrupt) + uart.Configure(machine.UARTConfig{TX: machine.PB24, RX: machine.PC24, BaudRate: 614400}) + + rtl := rtl8720dn.New(uart) + rtl.Debug(debug) + + _, err := rtl.Rpc_tcpip_adapter_init() + if err != nil { + return nil, err + } + + return rtl, nil +} + +// Wait for user to open serial console +func waitSerial() { + for !machine.Serial.DTR() { + time.Sleep(100 * time.Millisecond) + } +} + +type UARTx struct { + *machine.UART +} + +func (u UARTx) Read(p []byte) (n int, err error) { + if u.Buffered() == 0 { + time.Sleep(1 * time.Millisecond) + return 0, nil + } + return u.UART.Read(p) +} diff --git a/examples/rtl8720dn/udpstation/main.go b/examples/rtl8720dn/udpstation/main.go new file mode 100644 index 0000000..3c63d4e --- /dev/null +++ b/examples/rtl8720dn/udpstation/main.go @@ -0,0 +1,89 @@ +package main + +import ( + "fmt" + "strconv" + "time" + + "tinygo.org/x/drivers/net" + "tinygo.org/x/drivers/net/http" +) + +// IP address of the server aka "hub". Replace with your own info. +// You can override the setting with the init() in another source code. +// func init() { +// ssid = "your-ssid" +// password = "your-password" +// hubIP = "192.168.1.118" +// debug = true +// } + +var ( + ssid string + password string + hubIP = "" + debug = false +) + +var buf [0x400]byte + +func main() { + err := run() + for err != nil { + fmt.Printf("error: %s\r\n", err.Error()) + time.Sleep(5 * time.Second) + } +} + +func run() error { + rtl, err := setupRTL8720DN() + if err != nil { + return err + } + net.UseDriver(rtl) + http.SetBuf(buf[:]) + + err = rtl.ConnectToAP(ssid, password) + if err != nil { + return err + } + + ip, subnet, gateway, err := rtl.GetIP() + if err != nil { + return err + } + fmt.Printf("IP Address : %s\r\n", ip) + fmt.Printf("Mask : %s\r\n", subnet) + fmt.Printf("Gateway : %s\r\n", gateway) + + // now make UDP connection + hip := net.ParseIP(hubIP) + raddr := &net.UDPAddr{IP: hip, Port: 2222} + laddr := &net.UDPAddr{Port: 2222} + + println("Dialing UDP connection...") + conn, err := net.DialUDP("udp", laddr, raddr) + if err != nil { + return err + } + + for { + // send data + println("Sending data...") + for i := 0; i < 25; i++ { + conn.Write([]byte("hello " + strconv.Itoa(i) + "\r\n")) + } + time.Sleep(1000 * time.Millisecond) + } + + // Right now this code is never reached. Need a way to trigger it... + println("Disconnecting UDP...") + conn.Close() + println("Done.") + + return nil +} + +func message(msg string) { + println(msg, "\r") +} diff --git a/examples/rtl8720dn/udpstation/wioterminal.go b/examples/rtl8720dn/udpstation/wioterminal.go new file mode 100644 index 0000000..7602758 --- /dev/null +++ b/examples/rtl8720dn/udpstation/wioterminal.go @@ -0,0 +1,71 @@ +// +build wioterminal + +package main + +import ( + "device/sam" + "machine" + "runtime/interrupt" + "time" + + "tinygo.org/x/drivers/rtl8720dn" +) + +var ( + uart UARTx +) + +func handleInterrupt(interrupt.Interrupt) { + // should reset IRQ + uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF))) + uart.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INT_INTFLAG_RXC) +} + +func setupRTL8720DN() (*rtl8720dn.RTL8720DN, error) { + machine.RTL8720D_CHIP_PU.Configure(machine.PinConfig{Mode: machine.PinOutput}) + machine.RTL8720D_CHIP_PU.Low() + time.Sleep(100 * time.Millisecond) + machine.RTL8720D_CHIP_PU.High() + time.Sleep(1000 * time.Millisecond) + waitSerial() + + uart = UARTx{ + UART: &machine.UART{ + Buffer: machine.NewRingBuffer(), + Bus: sam.SERCOM0_USART_INT, + SERCOM: 0, + }, + } + + uart.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, handleInterrupt) + uart.Configure(machine.UARTConfig{TX: machine.PB24, RX: machine.PC24, BaudRate: 614400}) + + rtl := rtl8720dn.New(uart) + rtl.Debug(debug) + + _, err := rtl.Rpc_tcpip_adapter_init() + if err != nil { + return nil, err + } + + return rtl, nil +} + +// Wait for user to open serial console +func waitSerial() { + for !machine.Serial.DTR() { + time.Sleep(100 * time.Millisecond) + } +} + +type UARTx struct { + *machine.UART +} + +func (u UARTx) Read(p []byte) (n int, err error) { + if u.Buffered() == 0 { + time.Sleep(1 * time.Millisecond) + return 0, nil + } + return u.UART.Read(p) +} diff --git a/net/net.go b/net/net.go index 05a35df..aa12f9b 100644 --- a/net/net.go +++ b/net/net.go @@ -342,6 +342,18 @@ func (a *TCPAddr) opAddr() Addr { // ParseIP parses s as an IP address, returning the result. func ParseIP(s string) IP { + b := strings.Split(s, ".") + if len(b) == 4 { + ip := make([]byte, 4) + + for i := range ip { + x, _ := strconv.ParseUint(b[i], 10, 8) + ip[i] = byte(x) + } + + return IP(ip) + } + return IP([]byte(s)) } diff --git a/rtl8720dn/netdriver.go b/rtl8720dn/netdriver.go index fc79b52..3ac9647 100644 --- a/rtl8720dn/netdriver.go +++ b/rtl8720dn/netdriver.go @@ -152,9 +152,66 @@ func (r *RTL8720DN) ConnectSSLSocket(addr, port string) error { func (r *RTL8720DN) ConnectUDPSocket(addr, sendport, listenport string) error { if r.debug { - fmt.Printf("ConnectUDPSocket(%q, %q, %q)\r\n", addr, sendport, listenport) + fmt.Printf("ConnectUDPSocket(\"%d.%d.%d.%d\", %q, %q)\r\n", byte(addr[0]), byte(addr[1]), byte(addr[2]), byte(addr[3]), sendport, listenport) } - fmt.Printf("not implemented yet\r\n") + + socket, err := r.Rpc_lwip_socket(0x02, 0x02, 0x00) + if err != nil { + return err + } + r.socket = socket + r.connectionType = ConnectionTypeUDP + + optval := []byte{0x01, 0x00, 0x00, 0x00} + _, err = r.Rpc_lwip_setsockopt(socket, 0x00000FFF, 0x00000004, optval, uint32(len(optval))) + if err != nil { + return err + } + + port, err := strconv.ParseUint(sendport, 10, 0) + if err != nil { + return err + } + + ip := []byte(addr) + + // remote info + r.udpInfo[0] = byte(port >> 8) + r.udpInfo[1] = byte(port) + r.udpInfo[2] = ip[0] + r.udpInfo[3] = ip[1] + r.udpInfo[4] = ip[2] + r.udpInfo[5] = ip[3] + + port, err = strconv.ParseUint(listenport, 10, 0) + if err != nil { + return err + } + + ip_info := make([]byte, 12) + _, err = r.Rpc_tcpip_adapter_get_ip_info(0, &ip_info) + if err != nil { + return err + } + + name := []byte{0x00, 0x02, 0x0D, 0x05, 0xC0, 0xA8, 0x01, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + name[2] = byte(port >> 8) + name[3] = byte(port) + name[4] = ip_info[0] + name[5] = ip_info[1] + name[6] = ip_info[2] + name[7] = ip_info[3] + + _, err = r.Rpc_lwip_bind(socket, name, uint32(len(name))) + if err != nil { + return err + } + + _, err = r.Rpc_lwip_fcntl(socket, 0x00000004, 0x00000000) + if err != nil { + return err + } + return nil } @@ -204,6 +261,14 @@ func (r *RTL8720DN) Write(b []byte) (n int, err error) { return 0, err } n = int(sn) + case ConnectionTypeUDP: + to := []byte{0x00, 0x02, 0x0D, 0x05, 0xC0, 0xA8, 0x01, 0x76, 0xEB, 0x43, 0x00, 0x00, 0xD5, 0x27, 0x01, 0x00} + copy(to[2:], r.udpInfo[:]) + sn, err := r.Rpc_lwip_sendto(r.socket, b, 0x00000000, to, uint32(len(to))) + if err != nil { + return 0, err + } + n = int(sn) case ConnectionTypeTLS: sn, err := r.Rpc_wifi_send_ssl_data(r.client, b, uint16(len(b))) if err != nil { @@ -242,6 +307,23 @@ func (r *RTL8720DN) ReadSocket(b []byte) (n int, err error) { return 0, r.DisconnectSocket() } n = int(nn) + case ConnectionTypeUDP: + length := len(b) + if length > maxUartRecvSize-32 { + length = maxUartRecvSize - 32 + } + buf := b[:length] + from := make([]byte, 16) + fromLen := uint32(len(from)) + nn, err := r.Rpc_lwip_recvfrom(r.socket, &buf, uint32(length), 0x00000008, &from, &fromLen, 10000) + if err != nil { + return 0, err + } + + if nn == -1 { + return 0, nil + } + n = int(nn) case ConnectionTypeTLS: length := len(b) if length > maxUartRecvSize-16 { diff --git a/rtl8720dn/rtl8720dn.go b/rtl8720dn/rtl8720dn.go index 84508e9..e24e841 100644 --- a/rtl8720dn/rtl8720dn.go +++ b/rtl8720dn/rtl8720dn.go @@ -15,6 +15,7 @@ type RTL8720DN struct { client uint32 length int root_ca *string + udpInfo [6]byte // Port: [2]byte + IP: [4]byte } type ConnectionType int