diff --git a/espat/commands.go b/espat/commands.go new file mode 100644 index 0000000..f5bd02b --- /dev/null +++ b/espat/commands.go @@ -0,0 +1,104 @@ +package espat + +// Basic AT commands +const ( + // Test that the device is working. + Test = "" + + // Restart module + Restart = "+RST" + + // Version show info about the current software version. + Version = "+GMR" + + // Enter deep-sleep mode + Sleep = "+GSLP" + + // Configure echo. + EchoConfig = "E" + + // EchoConfigOn + EchoConfigOn = EchoConfig + "1" + + // EchoConfigOff + EchoConfigOff = EchoConfig + "0" + + // Configure UART + UARTConfig = "+UART" +) + +// WiFi commands. +const ( + // WiFi mode (sta/AP/sta+AP) + WifiMode = "+CWMODE" + + // Connect to an access point. + ConnectAP = "+CWJAP" + + // List available AP's + ListAP = "+CWLAP" + + // Disconnect from the current AP + Disconnect = "+CWQAP" + + // Set softAP configuration. This also activates the ESP8266/ESP32 to act as an access point. + // The settings will not be saved in flash memory, so they will be forgotten on next reset. + SoftAPConfigCurrent = "+CWSAP_CUR" + + // Set softAP configuration as saved in flash. This also activates the ESP8266/ESP32 to act as an + // access point. The settings will be saved in flash memory, so they will be used on next reset. + SoftAPConfigFlash = "+CWSAP_DEF" + + // List station IP's connected to softAP + ListConnectedIP = "+CWLIF" + + // Enable/disable DHCP + DHCPConfig = "+CWDHCP" + + // Set MAC address of station + SetStationMACAddress = "+CIPSTAMAC" + + // Set MAC address of softAP + SetAPMACAddress = "+CIPAPMAC" + + // Set IP address of ESP8266/ESP32 station + SetStationIP = "+CIPSTA" + + // Set IP address of ESP8266/ESP32 when acting as access point. + // The IP address will not be saved in flash memory, so it will be forgotten on next reset. + SetSoftAPIPCurrent = "+CIPAP_CUR" + + // Set IP address of ESP8266/ESP32 when acting as access point. + // The IP address will be saved in flash memory, so they will be used on next reset. + SetSoftAPIPFlash = "+CIPAP_DEF" +) + +// TCP/IP commands +const ( + // Get connection status + TCPStatus = "+CIPSTATUS" + + // Establish TCP connection or register UDP port + TCPConnect = "+CIPSTART" + + // Send Data + TCPSend = "+CIPSEND" + + // Close TCP/UDP connection + TCPClose = "+CIPCLOSE" + + // Get local IP address + GetLocalIP = "+CIFSR" + + // Set multiple connections mode + TCPMultiple = "+CIPMUX" + + // Configure as server + ServerConfig = "+CIPSERVER" + + // Set transmission mode + TransmissionMode = "+CIPMODE" + + // Set timeout when ESP8266/ESP32 runs as TCP server + SetServerTimeout = "+CIPSTO" +) diff --git a/espat/espat.go b/espat/espat.go new file mode 100644 index 0000000..5aee294 --- /dev/null +++ b/espat/espat.go @@ -0,0 +1,252 @@ +// Package espat implements TCP/UDP wireless communication over serial +// with a separate ESP8266 or ESP32 board using the Espressif AT command set +// across a UART interface. +// +// In order to use this driver, the ESP8266/ESP32 must be flashed with firmware +// supporting the AT command set. Many ESP8266/ESP32 chips already have this firmware +// installed by default. You will need to install this firmware if you have an +// ESP8266 that has been flashed with NodeMCU (Lua) or Arduino firmware. +// +// AT Command Core repository: +// https://github.com/espressif/esp32-at +// +// Datasheet: +// https://www.espressif.com/sites/default/files/documentation/0a-esp8266ex_datasheet_en.pdf +// +// AT command set: +// https://www.espressif.com/sites/default/files/documentation/4a-esp8266_at_instruction_set_en.pdf +// +package espat + +import ( + "machine" + "strconv" + "strings" + "time" +) + +// Device wraps UART connection to the ESP8266/ESP32. +type Device struct { + bus machine.UART + + // command responses that come back from the ESP8266/ESP32 + response []byte + + // data received from a TCP/UDP connection forwarded by the ESP8266/ESP32 + socketdata []byte +} + +// New returns a new espat driver. Pass in a fully configured UART bus. +func New(b machine.UART) *Device { + return &Device{bus: b, response: make([]byte, 512), socketdata: make([]byte, 0, 1024)} +} + +// Configure sets up the device for communication. +func (d Device) Configure() { +} + +// Connected checks if there is communication with the ESP8266/ESP32. +func (d *Device) Connected() bool { + d.Execute(Test) + + // handle response here, should include "OK" + r := d.Response() + if strings.Contains(string(r), "OK") { + return true + } + return false +} + +// Write raw bytes to the UART. +func (d *Device) Write(b []byte) (n int, err error) { + return d.bus.Write(b) +} + +// Read raw bytes from the UART. +func (d *Device) Read(b []byte) (n int, err error) { + return d.bus.Read(b) +} + +// how long in milliseconds to pause after sending AT commands +const pause = 100 + +// Execute sends an AT command to the ESP8266/ESP32. +func (d Device) Execute(cmd string) error { + _, err := d.Write([]byte("AT" + cmd + "\r\n")) + return err +} + +// Query sends an AT command to the ESP8266/ESP32 that returns the +// current value for some configuration parameter. +func (d Device) Query(cmd string) (string, error) { + _, err := d.Write([]byte("AT" + cmd + "?\r\n")) + return "", err +} + +// Set sends an AT command with params to the ESP8266/ESP32 for a +// configuration value to be set. +func (d Device) Set(cmd, params string) error { + _, err := d.Write([]byte("AT" + cmd + "=" + params + "\r\n")) + return err +} + +// Version returns the ESP8266/ESP32 firmware version info. +func (d Device) Version() []byte { + d.Execute(Version) + return d.Response() +} + +// Echo sets the ESP8266/ESP32 echo setting. +func (d Device) Echo(set bool) { + if set { + d.Execute(EchoConfigOn) + } else { + d.Execute(EchoConfigOff) + } + // TODO: check for success + d.Response() +} + +// Reset restarts the ESP8266/ESP32 firmware. Due to how the baud rate changes, +// this messes up communication with the ESP8266/ESP32 module. So make sure you know +// what you are doing when you call this. +func (d Device) Reset() { + d.Execute(Restart) + d.Response() +} + +// ReadSocket returns the data that has already been read in from the responses. +func (d *Device) ReadSocket(b []byte) (n int, err error) { + // make sure no data in buffer + d.Response() + + count := len(b) + if len(b) >= len(d.socketdata) { + // copy it all, then clear socket data + count = len(d.socketdata) + copy(b, d.socketdata[:count]) + d.socketdata = d.socketdata[:0] + } else { + // copy all we can, then keep the remaining socket data around + copy(b, d.socketdata[:count]) + copy(d.socketdata, d.socketdata[count:]) + d.socketdata = d.socketdata[:len(d.socketdata)-count] + } + + return count, nil +} + +// Response gets the next response bytes from the ESP8266/ESP32. +func (d *Device) Response() []byte { + var i, retries int + + header := make([]byte, 2) + for { + for d.bus.Buffered() > 0 { + // get the first 2 bytes + header[0], _ = d.bus.ReadByte() + header[1], _ = d.bus.ReadByte() + + if d.isLeadingCRLF(header) { + // skip it + header[0], _ = d.bus.ReadByte() + header[1], _ = d.bus.ReadByte() + } + + if d.isIPD(header) { + // is socket data packet + d.parseIPD() + } else { + // no, so put into response + d.response[i] = header[0] + i++ + d.response[i] = header[1] + i++ + } + + // read the rest of normal command response + for d.bus.Buffered() > 0 { + data, err := d.bus.ReadByte() + if err != nil { + return nil + } + d.response[i] = data + i++ + } + } + retries++ + if retries > 2 { + break + } + + // pause to make sure is no more data to be read + time.Sleep(10 * time.Millisecond) + } + return d.response[:i] +} + +func (d *Device) isLeadingCRLF(b []byte) bool { + if len(b) < 2 { + return false + } + if b[0] == 13 && b[1] == 10 { + return true + } + return false +} + +func (d *Device) isIPD(b []byte) bool { + if len(b) < 2 { + return false + } + if b[0] == '+' && b[1] == 'I' { + return true + } + return false +} + +func (d *Device) parseIPD() bool { + data, _ := d.bus.ReadByte() + if data != 'P' { + // error + return false + } + data, _ = d.bus.ReadByte() + if data != 'D' { + // error + return false + } + data, _ = d.bus.ReadByte() + if data != ',' { + // error + return false + } + + // get the expected data length + // skip remaining header up to the ":" + buf := []byte{} + data, _ = d.bus.ReadByte() + for data != ':' { + // put into the buffer with int value here + buf = append(buf, data) + + // read next value + data, _ = d.bus.ReadByte() + } + + val := string(buf) + count, err := strconv.Atoi(val) + if err != nil { + // not expected data here. what to do? + return false + } + + // load up the socket data + // only read the expected amount of data + for m := 0; m < count; m++ { + data, _ = d.bus.ReadByte() + d.socketdata = append(d.socketdata, data) + } + + return true +} diff --git a/espat/net.go b/espat/net.go new file mode 100644 index 0000000..ca4bda9 --- /dev/null +++ b/espat/net.go @@ -0,0 +1,143 @@ +package espat + +import ( + "strconv" + "time" +) + +// DialUDP makes a UDP network connection. raadr is the port that the messages will +// be sent to, and laddr is the port that will be listened to in order to +// receive incoming messages. +func (d Device) DialUDP(network string, laddr, raddr *UDPAddr) (*SerialConn, error) { + addr := raddr.IP.String() + sendport := strconv.Itoa(raddr.Port) + listenport := strconv.Itoa(laddr.Port) + + // disconnect any old socket + d.DisconnectSocket() + + // connect new socket + d.ConnectUDPSocket(addr, sendport, listenport) + + return &SerialConn{Adaptor: &d, laddr: laddr, raddr: raddr}, nil +} + +// ListenUDP listens for UDP connections on the port listed in laddr. +func (d Device) ListenUDP(network string, laddr *UDPAddr) (*SerialConn, error) { + addr := "0" + sendport := "0" + listenport := strconv.Itoa(laddr.Port) + + // disconnect any old socket + d.DisconnectSocket() + + // connect new socket + d.ConnectUDPSocket(addr, sendport, listenport) + + return &SerialConn{Adaptor: &d, laddr: laddr}, nil +} + +// SerialConn is a loosely net.Conn compatible intended to support +// TCP/UDP over serial. +type SerialConn struct { + Adaptor *Device + laddr *UDPAddr + raddr *UDPAddr +} + +// Read reads data from the connection. +// TODO: implement the full method functionality: +// Read can be made to time out and return an Error with Timeout() == true +// after a fixed time limit; see SetDeadline and SetReadDeadline. +func (c *SerialConn) Read(b []byte) (n int, err error) { + // read only the data that has been received via "+IPD" socket + return c.Adaptor.ReadSocket(b) +} + +// Write writes data to the connection. +// TODO: implement the full method functionality for timeouts. +// Write can be made to time out and return an Error with Timeout() == true +// after a fixed time limit; see SetDeadline and SetWriteDeadline. +func (c *SerialConn) Write(b []byte) (n int, err error) { + // specify that is a data transfer to the + // currently open socket, not commands to the ESP8266/ESP32. + c.Adaptor.StartSocketSend(len(b)) + return c.Adaptor.Write(b) +} + +// Close closes the connection. +// Currently only supports a single Read or Write operations without blocking. +func (c *SerialConn) Close() error { + c.Adaptor.DisconnectSocket() + return nil +} + +// LocalAddr returns the local network address. +func (c *SerialConn) LocalAddr() UDPAddr { + return *c.laddr +} + +// RemoteAddr returns the remote network address. +func (c *SerialConn) RemoteAddr() UDPAddr { + return *c.laddr +} + +// SetDeadline sets the read and write deadlines associated +// with the connection. It is equivalent to calling both +// SetReadDeadline and SetWriteDeadline. +// +// A deadline is an absolute time after which I/O operations +// fail with a timeout (see type Error) instead of +// blocking. The deadline applies to all future and pending +// I/O, not just the immediately following call to Read or +// Write. After a deadline has been exceeded, the connection +// can be refreshed by setting a deadline in the future. +// +// An idle timeout can be implemented by repeatedly extending +// the deadline after successful Read or Write calls. +// +// A zero value for t means I/O operations will not time out. +func (c *SerialConn) SetDeadline(t time.Time) error { + return nil +} + +// SetReadDeadline sets the deadline for future Read calls +// and any currently-blocked Read call. +// A zero value for t means Read will not time out. +func (c *SerialConn) SetReadDeadline(t time.Time) error { + return nil +} + +// SetWriteDeadline sets the deadline for future Write calls +// and any currently-blocked Write call. +// Even if write times out, it may return n > 0, indicating that +// some of the data was successfully written. +// A zero value for t means Write will not time out. +func (c *SerialConn) SetWriteDeadline(t time.Time) error { + return nil +} + +// The following definitions are here to support a Golang standard package +// net-compatible interface for IP until TinyGo can compile the net package. + +// IP is an IP address. Unlike the standard implementation, it is only +// a buffer of bytes that contains the string form of the IP address, not the +// full byte format used by the Go standard . +type IP []byte + +// UDPAddr here to serve as compatible type. until TinyGo can compile the net package. +type UDPAddr struct { + IP IP + Port int + Zone string // IPv6 scoped addressing zone; added in Go 1.1 +} + +// ParseIP parses s as an IP address, returning the result. +func ParseIP(s string) IP { + return IP([]byte(s)) +} + +// String returns the string form of the IP address ip. +func (ip IP) String() string { + return string(ip) +} diff --git a/espat/tcp.go b/espat/tcp.go new file mode 100644 index 0000000..8a4ee78 --- /dev/null +++ b/espat/tcp.go @@ -0,0 +1,97 @@ +package espat + +import ( + "strconv" + "time" +) + +const ( + TCPMuxSingle = 0 + TCPMuxMultiple = 1 + + TCPTransferModeNormal = 0 + TCPTransferModeUnvarnished = 1 +) + +// ConnectTCPSocket creates a new TCP socket connection for the ESP8266/ESP32. +// Currently only supports single connection mode. +func (d *Device) ConnectTCPSocket(addr, port string) error { + protocol := "TCP" + val := "\"" + protocol + "\",\"" + addr + "\"," + port + d.Set(TCPConnect, val) + time.Sleep(100 * time.Millisecond) + d.Response() + return nil +} + +// ConnectUDPSocket creates a new UDP connection for the ESP8266/ESP32. +func (d *Device) ConnectUDPSocket(addr, sendport, listenport string) error { + protocol := "UDP" + val := "\"" + protocol + "\",\"" + addr + "\"," + sendport + "," + listenport + ",2" + d.Set(TCPConnect, val) + time.Sleep(pause * time.Millisecond) + d.Response() + return nil +} + +// DisconnectSocket disconnects the ESP8266/ESP32 from the current TCP/UDP connection. +func (d *Device) DisconnectSocket() error { + d.Execute(TCPClose) + time.Sleep(pause * time.Millisecond) + d.Response() + return nil +} + +// SetMux sets the ESP8266/ESP32 current client TCP/UDP configuration for concurrent connections +// either single TCPMuxSingle or multiple TCPMuxMultiple (up to 4). +func (d *Device) SetMux(mode int) error { + val := strconv.Itoa(mode) + d.Set(TCPMultiple, val) + time.Sleep(pause * time.Millisecond) + d.Response() + return nil +} + +// GetMux returns the ESP8266/ESP32 current client TCP/UDP configuration for concurrent connections. +func (d *Device) GetMux() ([]byte, error) { + d.Query(TCPMultiple) + return d.Response(), nil +} + +// SetTCPTransferMode sets the ESP8266/ESP32 current client TCP/UDP transfer mode. +// Either TCPTransferModeNormal or TCPTransferModeUnvarnished. +func (d *Device) SetTCPTransferMode(mode int) error { + val := strconv.Itoa(mode) + d.Set(TransmissionMode, val) + time.Sleep(pause * time.Millisecond) + d.Response() + return nil +} + +// GetTCPTransferMode returns the ESP8266/ESP32 current client TCP/UDP transfer mode. +func (d *Device) GetTCPTransferMode() []byte { + d.Query(TransmissionMode) + return d.Response() +} + +// StartSocketSend gets the ESP8266/ESP32 ready to receive TCP/UDP socket data. +func (d *Device) StartSocketSend(size int) error { + val := strconv.Itoa(size) + d.Set(TCPSend, val) + + // TODO: wait until ">" is received, which indicates + // ready to receive data + d.Response() + return nil +} + +// EndSocketSend tell the ESP8266/ESP32 the TCP/UDP socket data sending is complete, +// and to return to command mode. This is only used in "unvarnished" raw mode. +func (d *Device) EndSocketSend() error { + d.Write([]byte("+++")) + + // TODO: wait until ">" is received, which indicates + // ready to receive data + d.Response() + return nil +} diff --git a/espat/wifi.go b/espat/wifi.go new file mode 100644 index 0000000..78bf2d3 --- /dev/null +++ b/espat/wifi.go @@ -0,0 +1,154 @@ +package espat + +import ( + "strconv" + "time" +) + +const ( + WifiModeClient = 1 + WifiModeAP = 2 + WifiModeDual = 3 + + WifiAPSecurityOpen = 1 + WifiAPSecurityWPA_PSK = 2 + WifiAPSecurityWPA2_PSK = 3 + WifiAPSecurityWPA_WPA2_PSK = 4 +) + +// GetWifiMode returns the ESP8266/ESP32 wifi mode. +func (d *Device) GetWifiMode() []byte { + d.Query(WifiMode) + return d.Response() +} + +// SetWifiMode sets the ESP8266/ESP32 wifi mode. +func (d *Device) SetWifiMode(mode int) error { + val := strconv.Itoa(mode) + d.Set(WifiMode, val) + time.Sleep(pause * time.Millisecond) + d.Response() + return nil +} + +// Wifi Client + +// GetConnectedAP returns the ESP8266/ESP32 is currently connected to as a client. +func (d *Device) GetConnectedAP() []byte { + d.Query(ConnectAP) + return d.Response() +} + +// ConnectToAP connects the ESP8266/ESP32 to an access point. +// ws is the number of seconds to wait for connection. +func (d *Device) ConnectToAP(ssid, pwd string, ws int) error { + val := "\"" + ssid + "\",\"" + pwd + "\"" + d.Set(ConnectAP, val) + // TODO: a better way to wait for connect and check for up to ws seconds. + time.Sleep(time.Duration(ws) * time.Second) + d.Response() + return nil +} + +// DisconnectFromAP disconnects the ESP8266/ESP32 from the current access point. +func (d *Device) DisconnectFromAP() error { + d.Execute(Disconnect) + time.Sleep(1000 * time.Millisecond) + d.Response() + return nil +} + +// GetClientIP returns the ESP8266/ESP32 current client IP addess when connected to an Access Point. +func (d *Device) GetClientIP() string { + d.Query(SetStationIP) + return string(d.Response()) +} + +// SetClientIP sets the ESP8266/ESP32 current client IP addess when connected to an Access Point. +func (d *Device) SetClientIP(ipaddr string) []byte { + val := "\"" + ipaddr + "\"" + d.Set(ConnectAP, val) + time.Sleep(500 * time.Millisecond) + d.Response() + return nil +} + +// Access Point + +// GetAPConfig returns the ESP8266/ESP32 current configuration when acting as an Access Point. +func (d *Device) GetAPConfig() string { + d.Query(SoftAPConfigCurrent) + return string(d.Response()) +} + +// SetAPConfig sets the ESP8266/ESP32 current configuration when acting as an Access Point. +// ch indicates which radiochannel to use. security should be one of the const values +// such as WifiAPSecurityOpen etc. +func (d *Device) SetAPConfig(ssid, pwd string, ch, security int) error { + chval := strconv.Itoa(ch) + ecnval := strconv.Itoa(security) + val := "\"" + ssid + "\",\"" + pwd + "\"," + chval + "," + ecnval + d.Set(SoftAPConfigCurrent, val) + time.Sleep(1000 * time.Millisecond) + d.Response() + return nil +} + +// GetAPClients returns the ESP8266/ESP32 current clients when acting as an Access Point. +func (d *Device) GetAPClients() string { + d.Query(ListConnectedIP) + return string(d.Response()) +} + +// GetAPIP returns the ESP8266/ESP32 current IP addess when configured as an Access Point. +func (d *Device) GetAPIP() string { + d.Query(SetSoftAPIPCurrent) + return string(d.Response()) +} + +// SetAPIP sets the ESP8266/ESP32 current IP addess when configured as an Access Point. +func (d *Device) SetAPIP(ipaddr string) error { + val := "\"" + ipaddr + "\"" + d.Set(SetSoftAPIPCurrent, val) + time.Sleep(500 * time.Millisecond) + d.Response() + return nil +} + +// GetAPConfigFlash returns the ESP8266/ESP32 current configuration acting as an Access Point +// from flash storage. These settings are those used after a reset. +func (d *Device) GetAPConfigFlash() string { + d.Query(SoftAPConfigFlash) + return string(d.Response()) +} + +// SetAPConfigFlash sets the ESP8266/ESP32 current configuration acting as an Access Point, +// and saves them to flash storage. These settings will be used after a reset. +// ch indicates which radiochannel to use. security should be one of the const values +// such as WifiAPSecurityOpen etc. +func (d *Device) SetAPConfigFlash(ssid, pwd string, ch, security int) error { + chval := strconv.Itoa(ch) + ecnval := strconv.Itoa(security) + val := "\"" + ssid + "\",\"" + pwd + "\"," + chval + "," + ecnval + d.Set(SoftAPConfigFlash, val) + time.Sleep(1000 * time.Millisecond) + d.Response() + return nil +} + +// GetAPIPFlash returns the ESP8266/ESP32 IP address as saved to flash storage. +// This is the IP address that will be used after a reset. +func (d *Device) GetAPIPFlash() string { + d.Query(SetSoftAPIPFlash) + return string(d.Response()) +} + +// SetAPIPFlash sets the ESP8266/ESP32 current IP addess when configured as an Access Point. +// The IP will be saved to flash storage, and will be used after a reset. +func (d *Device) SetAPIPFlash(ipaddr string) error { + val := "\"" + ipaddr + "\"" + d.Set(SetSoftAPIPFlash, val) + time.Sleep(500 * time.Millisecond) + d.Response() + return nil +} diff --git a/examples/espat/espconsole/main.go b/examples/espat/espconsole/main.go new file mode 100644 index 0000000..9c58f94 --- /dev/null +++ b/examples/espat/espconsole/main.go @@ -0,0 +1,129 @@ +// This is a console to a ESP8266/ESP32 running on the device UART1. +// Allows you to type AT commands from your computer via the microcontroller. +// +// In other words: +// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> INTERNET +// +// More information on the Espressif AT command set at: +// https://www.espressif.com/sites/default/files/documentation/4a-esp8266_at_instruction_set_en.pdf +// +package main + +import ( + "machine" + "time" + + "github.com/tinygo-org/drivers/espat" +) + +// change actAsAP to true to act as an access point instead of connecting to one. +const actAsAP = false + +// access point info +const ssid = "YOURSSID" +const pass = "YOURPASS" + +// change these to connect to a different UART or pins for the ESP8266/ESP32 +var ( + uart = machine.UART1 + tx uint8 = machine.D10 + rx uint8 = machine.D11 + + console = machine.UART0 + + adaptor *espat.Device +) + +func main() { + uart.Configure(machine.UARTConfig{TX: tx, RX: rx}) + + // Init esp8266 + adaptor = espat.New(uart) + adaptor.Configure() + + // first check if connected + if adaptor.Connected() { + adaptor.Echo(false) + console.Write([]byte("\r\n")) + console.Write([]byte("ESP-AT console enabled.\r\n")) + console.Write([]byte("Firmware version:\r\n")) + console.Write(adaptor.Version()) + console.Write([]byte("\r\n")) + + if actAsAP { + provideAP() + } else { + connectToAP() + } + + console.Write([]byte("Type an AT command then press enter:\r\n")) + prompt() + } else { + console.Write([]byte("\r\n")) + console.Write([]byte("Unable to connect to wifi adaptor.\r\n")) + return + } + + input := make([]byte, 64) + i := 0 + for { + if console.Buffered() > 0 { + data, _ := console.ReadByte() + + switch data { + case 13: + // return key + console.Write([]byte("\r\n")) + + // send command to ESP8266 + input[i] = byte('\r') + input[i+1] = byte('\n') + adaptor.Write(input[:i+2]) + + // give the ESP8266 a chance to respond. + time.Sleep(10 * time.Millisecond) + + // display response + console.Write(adaptor.Response()) + + // prompt + prompt() + + i = 0 + continue + default: + // just echo the character + console.WriteByte(data) + input[i] = data + i++ + } + } + time.Sleep(10 * time.Millisecond) + } +} + +func prompt() { + console.Write([]byte("ESPAT>")) +} + +// connect to access point +func connectToAP() { + console.Write([]byte("Connecting to wifi network...\r\n")) + adaptor.SetWifiMode(espat.WifiModeClient) + adaptor.ConnectToAP(ssid, pass, 10) + console.Write([]byte("Connected.\r\n")) + console.Write([]byte(adaptor.GetClientIP())) + console.Write([]byte("\r\n")) +} + +// provide access point +func provideAP() { + console.Write([]byte("Starting wifi network as access point '")) + console.Write([]byte(ssid)) + console.Write([]byte("'...\r\n")) + adaptor.SetWifiMode(espat.WifiModeAP) + adaptor.SetAPConfig(ssid, pass, 7, espat.WifiAPSecurityWPA2_PSK) + console.Write([]byte("Ready.\r\n")) + console.Write([]byte(adaptor.GetAPIP())) + console.Write([]byte("\r\n")) +} diff --git a/examples/espat/esphub/main.go b/examples/espat/esphub/main.go new file mode 100644 index 0000000..5f36f94 --- /dev/null +++ b/examples/espat/esphub/main.go @@ -0,0 +1,111 @@ +// This is a sensor hub that uses a ESP8266/ESP32 running on the device UART1. +// It creates a UDP "server" you can use to get info to/from your computer via the microcontroller. +// +// In other words: +// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 <--> INTERNET +// +package main + +import ( + "machine" + "time" + + "github.com/tinygo-org/drivers/espat" +) + +// change actAsAP to true to act as an access point instead of connecting to one. +const actAsAP = false + +// access point info +const ssid = "YOURSSID" +const pass = "YOURPASS" + +// change these to connect to a different UART or pins for the ESP8266/ESP32 +var ( + uart = machine.UART1 + tx uint8 = machine.D10 + rx uint8 = machine.D11 + + console = machine.UART0 + + adaptor *espat.Device +) + +func main() { + uart.Configure(machine.UARTConfig{TX: tx, RX: rx}) + + // Init esp8266 + adaptor = espat.New(uart) + adaptor.Configure() + + readyled := machine.GPIO{machine.LED} + readyled.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT}) + readyled.High() + + // first check if connected + if adaptor.Connected() { + console.Write([]byte("Connected to wifi adaptor.\r\n")) + adaptor.Echo(false) + + if actAsAP { + provideAP() + } else { + connectToAP() + } + } else { + console.Write([]byte("\r\n")) + console.Write([]byte("Unable to connect to wifi adaptor.\r\n")) + return + } + + // now make UDP connection + laddr := &espat.UDPAddr{Port: 2222} + console.Write([]byte("Loading UDP listener...\r\n")) + conn, _ := adaptor.ListenUDP("UDP", laddr) + + console.Write([]byte("Waiting for data...\r\n")) + data := make([]byte, 50) + blink := true + for { + n, _ := conn.Read(data) + if n > 0 { + console.Write(data[:n]) + console.Write([]byte("\r\n")) + conn.Write([]byte("hello back\r\n")) + } + blink = !blink + if blink { + readyled.High() + } else { + readyled.Low() + } + time.Sleep(500 * time.Millisecond) + } + + // Right now this code is never reached. Need a way to trigger it... + console.Write([]byte("Disconnecting UDP...\r\n")) + conn.Close() + console.Write([]byte("Done.\r\n")) +} + +// connect to access point +func connectToAP() { + console.Write([]byte("Connecting to wifi network...\r\n")) + adaptor.SetWifiMode(espat.WifiModeClient) + adaptor.ConnectToAP(ssid, pass, 10) + console.Write([]byte("Connected.\r\n")) + console.Write([]byte(adaptor.GetClientIP())) + console.Write([]byte("\r\n")) +} + +// provide access point +func provideAP() { + console.Write([]byte("Starting wifi network as access point '")) + console.Write([]byte(ssid)) + console.Write([]byte("'...\r\n")) + adaptor.SetWifiMode(espat.WifiModeAP) + adaptor.SetAPConfig(ssid, pass, 7, espat.WifiAPSecurityWPA2_PSK) + console.Write([]byte("Ready.\r\n")) + console.Write([]byte(adaptor.GetAPIP())) + console.Write([]byte("\r\n")) +} diff --git a/examples/espat/espstation/main.go b/examples/espat/espstation/main.go new file mode 100644 index 0000000..5c5bedc --- /dev/null +++ b/examples/espat/espstation/main.go @@ -0,0 +1,83 @@ +// This is a sensor station that uses a ESP8266 or ESP32 running on the device UART1. +// It creates a UDP connection you can use to get info to/from your computer via the microcontroller. +// +// In other words: +// Your computer <--> UART0 <--> MCU <--> UART1 <--> ESP8266 +// +package main + +import ( + "machine" + "time" + + "github.com/tinygo-org/drivers/espat" +) + +// access point info +const ssid = "YOURSSID" +const pass = "YOURPASS" + +// IP address of the listener aka "hub". Replace with your own info. +const hubIP = "0.0.0.0" + +// change these to connect to a different UART or pins for the ESP8266/ESP32 +var ( + uart = machine.UART1 + tx uint8 = machine.D10 + rx uint8 = machine.D11 + + console = machine.UART0 + + adaptor *espat.Device +) + +func main() { + uart.Configure(machine.UARTConfig{TX: tx, RX: rx}) + + // Init esp8266/esp32 + adaptor = espat.New(uart) + adaptor.Configure() + + // first check if connected + if adaptor.Connected() { + console.Write([]byte("Connected to wifi adaptor.\r\n")) + adaptor.Echo(false) + + connectToAP() + } else { + console.Write([]byte("\r\n")) + console.Write([]byte("Unable to connect to wifi adaptor.\r\n")) + return + } + + // now make UDP connection + ip := espat.ParseIP(hubIP) + raddr := &espat.UDPAddr{IP: ip, Port: 2222} + laddr := &espat.UDPAddr{Port: 2222} + + console.Write([]byte("Dialing UDP connection...\r\n")) + conn, _ := adaptor.DialUDP("udp", laddr, raddr) + + for { + // send data + console.Write([]byte("Sending data...\r\n")) + conn.Write([]byte("hello\r\n")) + + time.Sleep(1000 * time.Millisecond) + } + + // Right now this code is never reached. Need a way to trigger it... + console.Write([]byte("Disconnecting UDP...\r\n")) + conn.Close() + console.Write([]byte("Done.\r\n")) +} + +// connect to access point +func connectToAP() { + console.Write([]byte("Connecting to wifi network...\r\n")) + adaptor.SetWifiMode(espat.WifiModeClient) + adaptor.ConnectToAP(ssid, pass, 10) + console.Write([]byte("Connected.\r\n")) + console.Write([]byte(adaptor.GetClientIP())) + console.Write([]byte("\r\n")) +}