espradio

package module
v0.0.0-...-449fb45 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 16, 2026 License: Apache-2.0, BSD-3-Clause Imports: 15 Imported by: 0

README

espradio

PkgGoDev Build

TinyGo package for wireless communication on Espressif ESP32xx microcontrollers.

Currently supports WiFi on the esp32c3 single-core 32-bit RISC-V MCU and esp32s3 dual-core XTensa LX7 MCU. Bluetooth is in progress, along with more processors.

Features
  • WiFi station (STA) and soft-AP modes
  • WiFi scanning
  • Go stdlib net support using the TinyGo netdev/netlink interface
  • Pure-Go TCP/IP stack with DHCP, DNS, and NTP (uses lneto)
  • TCP and UDP Berkeley sockets API
  • Raw Ethernet frame send/receive
  • MQTT client support (uses natiu-mqtt)
  • QEMU simulation target for ESP32-C3

How to use

This code starts a basic webserver running on a Seeed Studio XIAO-ESP32C3 using espradio along with the Go stdlib net/http package:

package main

import (
	"io"
	"log"
	"net/http"
	"time"

	"tinygo.org/x/drivers/netdev"
	nl "tinygo.org/x/drivers/netlink"
	link "tinygo.org/x/espradio/netlink"
)

var (
	ssid     string
	password string
	port     string = ":80"
)

func main() {
	// use ESP32 radio
	link := link.Esplink{}
	netdev.UseNetdev(&link)

	println("Connecting to WiFi...")
	err := link.NetConnect(&nl.ConnectParams{
		Ssid:       ssid,
		Passphrase: password,
	})
	if err != nil {
		log.Fatal(err)
	}

	println("Connected to WiFi.")

	// now setup the web server using the Go "net/http" package:
	http.HandleFunc("/", hello)

	h, _ := link.Addr()
	host := h.String()
	println("HTTP server listening on http://" + host + port)
	err = http.ListenAndServe(host+port, nil)
	for err != nil {
		println("error:", err.Error())
		time.Sleep(5 * time.Second)
	}
}

func hello(w http.ResponseWriter, r *http.Request) {
	println(r.Method, r.URL.Path)
	w.Header().Set(`Content-Type`, `text/plain; charset=UTF-8`)
	io.WriteString(w, "hello")
}

Flash it using TinyGo like this:

$ tinygo flash -target xiao-esp32s3 -ldflags="-X main.ssid=yourssid -X main.password=yourpassword" -size short -monitor ./examples/hello
   code    data     bss |   flash     ram
 860073   33552  345392 |  893625  378944
Connecting to /dev/ttyACM0...
Connected.      
Detected chip: ESP32-S3
...

Then you can test it by using curl:

$ curl -w "\n" http://192.168.1.241/
hello

Features

How it works

espradio uses the binary blobs provided by Espressif and calls them directly using TinyGo's built-in CGo support. This allows them to be fast and utilize the well-tested existing binaries for low level radio communication.

On top of that espradio then uses the lneto package, a pure Go layer 2 networking stack.

See the architecture diagram for more details.

Examples - net package calling lneto

hello

Runs a minimal webserver using the Go net/http package using the netlink interface.

tinygo flash -target xiao-esp32s3 -ldflags="-X main.ssid=yourssid -X main.password=yourpassword" -size short -monitor ./examples/hello
mqtt

Uses the MQTT machine to machine protocol to publish and subscribe to messages with the broker.hivemq.com test server. Uses the Go stdlib and the natiu-mqtt package with the netlink interface.

$ tinygo flash -target xiao-esp32s3 -ldflags="-X main.ssid=yourssid -X main.password=yourpassword" -size short -monitor ./examples/mqtt/
   code    data     bss |   flash     ram
 672497   22956  284464 |  695453  307420
Connecting to /dev/ttyACM0...
Connected.
Detected chip: ESP32-S3
Loading stub loader...
Stub running.
Erasing entire flash...
Flash erased.
Attaching SPI flash...
Configuring flash size...
Auto-detected flash size: 8MB
Flash params set to 0x023F
SHA digest in image updated
Attaching SPI flash...
Compressed 695552 bytes to 472382 (68%)
Flash begin: 472382 bytes at 0x00000000 (29 compressed blocks)
[##################################################]  100.0%
Flash complete. Verifying...
MD5 verified: 468b20c64e138c8786f8b0b669a5d717

Device reset.
Connected to /dev/ttyACM0. Press Ctrl-C to exit.
load:0x4202db20,len:0x7c1b8
SHA-256 comparison failed:
Calculated: 716b35e13bffed22f2ce7fc865c81242c3bcf494fb3481bab12494585e0cfac9
Expected: 09eb25d79a2297f382faa0f8b842998881cd4a2474e9e80eb215799c7ddef1a6
Attempting to boot anyway...
entry 0x40386c8c
Connecting to WiFi...
Connected to WiFi.
ClientId: tinygo-client-WYVKRWBJRP
Connecting to MQTT broker at broker.hivemq.com:1883
TCP connected to 3.122.68.120:1883
Sending MQTT CONNECT...
MQTT CONNECT succeeded
Subscribed to topic cpu/usage
Message Random value: 34 received on topic cpu/usage
Message Random value: 8 received on topic cpu/usage
Message Random value: 32 received on topic cpu/usage
...
webserver

webserver image

Runs a webserver using the Go net/http package using the netlink interface:

$ tinygo flash -target xiao-esp32c3 -ldflags="-X main.ssid=yourssid -X main.password=yourpassword" -size short -monitor ./examples/webserver/
   code    data     bss |   flash     ram
 932780   34484  353934 |  967264  388418
Connecting to /dev/ttyACM0...                
Connected.                                                                                                
Detected chip: ESP32-C3
Loading stub loader...                                                                                                                                                                                              
Stub running.                                                                                             
Erasing entire flash...        
Flash erased.                      
Attaching SPI flash...                                                                                    
Configuring flash size...     
Auto-detected flash size: 4MB                                                                             
Flash params set to 0x022F              
SHA digest in image updated                                                                               
Attaching SPI flash...                                                                                    
Compressed 967360 bytes to 624318 (65%)                                                                   
Flash begin: 624318 bytes at 0x00000000 (39 compressed blocks)
[##################################################]  100.0%
Flash complete. Verifying...                                                                              
MD5 verified: 530da6d941a937d248fce4dbb88d420b                                                            
                                                                                                          
Device reset.                                      
Connected to /dev/ttyACM0. Press Ctrl-C to exit.                                                          
load:0x3fc8a7b0,len:0x86b4                                                                                
load:0x40392e64,len:0xa474                                                                                
load:0x4203905c,len:0xb3240
SHA-256 comparison failed:
Calculated: 698a9fd323776b909347315bd8e6dec519318ba17be4685ce367bea2464e5b27
Expected: a8fdd97c926ffa40023343a82e4366b1d7103db2214b1607bd34dac4225c78f7
Attempting to boot anyway...
entry 0x4039d294
Connecting to WiFi...
HTTP server listening on http://192.168.1.46:80

Examples - lneto package only

ap

Shows how to set up a WiFi access point with a DHCP server using the low-level lneto interface.

$ tinygo flash -target xiao-esp32c3 -ldflags="-X main.ssid=tinygoap -X main.password=YourPasswordHere" -monitor ./examples/ap
   code    data     bss |   flash     ram
 582004   21988  257010 |  603992  278998
Connected to ESP32-C3
Flashing: 604096/604096 bytes (100%)
Connected to /dev/ttyACM0. Press Ctrl-C to exit.
SHA-256 comparison failed:
Calculated: 8d1512da059d76b08bbfe99c14ab43d792466ee8fd85a365b338a6ad17aa1e2a
Expected: 06fd1290b7002c716ccf8a9df263fb33d5e22b6892e08038cb6a8e8f6b04be5f
Attempting to boot anyway...
entry 0x40398b3c
ap: enabling radio...
ap: starting AP...
ap: starting L2 netdev (AP)...
ap: creating lneto stack...
ap: configuring DHCP server...
ap: AP is running on 192.168.4.1 - connect to tinygo-ap
ap: rx_cb= 0 rx_drop= 0
ap: rx_cb= 0 rx_drop= 0
ap: rx_cb= 0 rx_drop= 0
...
connect-and-dhcp

Connects to a Wi-Fi network and gets an IP address with DHCP using the low-level lneto interface.

$ tinygo flash -target xiao-esp32s3 -ldflags="-X main.ssid=yourssid -X main.password=YourPasswordHere" -monitor ./examples/connect-and-dhcp
   code    data     bss |   flash     ram
 574233   22304  259184 |  596537  281488
Connected to ESP32-S3
Flashing: 596640/596640 bytes (100%)
Connected to /dev/ttyACM0. Press Ctrl-C to exit.
SHA-256 comparison failed:
Calculated: 84318f99e1f97b458c8a4afc3237908a2d5be760b42c66b39c03367a96e2ae32
Expected: c3a26bf70f12f0ffc686e708a86b10548920a08e166a0b14d9e2a8f80e662d2e
Attempting to boot anyway...
entry 0x4038688c
initializing radio...
starting radio...
connecting to rems ...
connected to rems !
starting L2 netdev...
creating lneto stack...
starting DHCP...
got IP: 192.168.1.241
gateway: 192.168.1.1
DNS: 192.168.1.1
done!
alive
alive
...
http-app

Connects to a WiFi access point, calls NTP to obtain the current date/time, then serves a tiny web application using the low-level lneto interface.

$ tinygo flash -target xiao-esp32c3 -ldflags="-X main.ssid=yourssid -X main.password=YourPasswordHere" -monitor ./examples/http-app/
Connected to ESP32-C3
Flashing: 652848/652848 bytes (100%)
Connected to /dev/ttyACM0. Press Ctrl-C to exit.
SHA-256 comparison failed:
Calculated: 5520848628102c249831cc101bbd042d6311260e697288fb5bf082ad4c912b32
Expected: 8b36e3d705b1ed66d74082f300d35a796d6153344e7e8b39eccdbaa2f0bff23f
Attempting to boot anyway...
entry 0x40395708
initializing radio...
starting radio...
connecting to rems ...
connected to rems !
starting L2 netdev...
creating lneto stack...
starting DHCP...
got IP: 192.168.1.46
resolving ntp host: pool.ntp.org
NTP success: 2026-03-21 08:51:31.136908291 +0000 UTC m=+3.079340401
listening on http://192.168.1.46:80
...
http-static

Minimal HTTP server that serves a static webpage using the low-level lneto interface.

$ tinygo flash -target xiao-esp32c3 -ldflags="-X main.ssid=yourssid -X main.password=YourPasswordHere" -monitor 8kb ./examples/http-static/
Connected to ESP32-C3
Flashing: 627504/627504 bytes (100%)
Connected to /dev/ttyACM0. Press Ctrl-C to exit.
SHA-256 comparison failed:
Calculated: 468629c0cff3cf13345660532bcc748a1a93df46c197d51727d75863bd985195
Expected: 785df5a5bb20c057bcc0580b2870982aa779faeb1f946a5d3c371ad36da077f0
Attempting to boot anyway...
entry 0x40395350
initializing radio...
starting radio...
connecting to rems ...
connected to rems !
starting L2 netdev...
creating lneto stack...
listening on http://192.168.1.46:80
incoming connection: 192.168.1.223 from port 53636
incoming connection: 192.168.1.223 from port 53640
Got webpage request!
scan

Scans for WiFi access points.

$ tinygo flash -target xiao-esp32c3 -monitor ./examples/scan
Connected to ESP32-C3
Flashing: 442736/442736 bytes (100%)
Connected to /dev/ttyACM0. Press Ctrl-C to exit.
SHA-256 comparison failed:
Calculated: 0045ab8467d485eb94005908a8e7f9dd7baf4dfa610f20ed67884c8ae5e98737
Expected: 6be1722a792dec8849a2a9fb26c8faf50ba48294ea6617e125472469e56ea719
Attempting to boot anyway...
entry 0x4038e3d0
initializing radio...
starting radio...
scanning WiFi...
AP: rems RSSI -59
AP: rems RSSI -78

scanning WiFi...
AP: rems RSSI -59
AP: rems RSSI -79
starting

Starts the ESP32 radio.

Architecture

flowchart TD
    A["User Application"]
    B["espradio/netlink"]
    C["espradio Stack"]
    D["espradio NetDev"]
    E["CGo Bridge"]
    F["Espressif Binary Blobs"]
    G["ESP32 Radio Hardware"]

    A --Go net/http or lneto API--> B --Esplink netdev interface--> C --lneto TCP/IP + DHCP/DNS/NTP--> D --EthernetDevice send/recv--> E --radio.c / lib.c / isr.c--> F --WiFi + PHY libs--> G
  • User Application - your code, using Go net/http or the lneto API directly.
  • espradio/netlink - implements the TinyGo netdev/netlink interface so the Go stdlib net package works.
  • espradio Stack - wraps the lneto pure-Go TCP/IP stack with DHCP, DNS, and NTP support.
  • espradio NetDev - L2 Ethernet device that sends/receives raw frames to and from the radio.
  • CGo Bridge - C shim code that translates between Go and the Espressif binary libraries.
  • Espressif Binary Blobs - pre-compiled WiFi and PHY libraries provided by Espressif.
  • ESP32 Radio Hardware - the on-chip 2.4 GHz radio peripheral.

Updating esp-wifi-sys

This package uses files from the esp-wifi-sys package, then copies the needed ones into the blobs directory.

To update these dependencies to the latest version, run the make update command. This will update the submodule, then copy the needed files. Then run make patch-esp32s3 to patch the blobs for the LLD linker. Note that this may break existing functionality requiring changes to TinyGo linker files or other changes.

Documentation

Overview

Package espradio provides support for the ESP32-S2 and ESP32-S3 microcontrollers. It is based on the ESP-IDF framework and provides access to Wi-Fi and Bluetooth. The package is designed to be used with the TinyGo compiler.

Index

Constants

View Source
const (
	LogLevelNone    = C.WIFI_LOG_NONE
	LogLevelError   = C.WIFI_LOG_ERROR
	LogLevelWarning = C.WIFI_LOG_WARNING
	LogLevelInfo    = C.WIFI_LOG_INFO
	LogLevelDebug   = C.WIFI_LOG_DEBUG
	LogLevelVerbose = C.WIFI_LOG_VERBOSE
)
View Source
const MaxFrameSize = 1518
View Source
const Version = "0.1.0-dev"

Version is the current version of the espradio library.

Variables

This section is empty.

Functions

func ArenaStats

func ArenaStats() (used, capacity uint32)

ArenaStats returns the current arena usage and capacity in bytes.

func Connect

func Connect(cfg STAConfig) error

Connect configures STA credentials and initiates association. Blocks until CONNECTED, DISCONNECTED or timeout.

func DebugISRCount

func DebugISRCount() uint32

DebugISRCount returns the number of WiFi ISR invocations (for debugging).

func Enable

func Enable(config Config) error

Enable and configure the radio for WiFi.

func NetifRxStats

func NetifRxStats() (uint32, uint32)

NetifRxStats returns (callback_count, drop_count) from the C ring buffer.

func SniffCountOnChannel

func SniffCountOnChannel(channel uint8, duration time.Duration) (uint32, error)

func Start

func Start() error

Start starts the Wi-Fi driver and connects to the AP if in station mode. Blocks until the driver is ready. Start is separate from Enable to allow configuration (e.g. country code) before starting, and to allow scanning without starting the driver. Start calls schedOnce in a loop to let the blob process its internal startup sequence (posting events, etc.) before Start returns.

func StartAP

func StartAP(cfg APConfig) error

StartAP starts the radio in soft-AP mode with the given configuration.

Types

type APConfig

type APConfig struct {
	SSID     string
	Password string
	Channel  uint8
	AuthOpen bool
}

APConfig configures soft-AP mode parameters.

type AccessPoint

type AccessPoint struct {
	SSID string
	RSSI int
}

AccessPoint represents a Wi-Fi access point discovered during scanning.

func Scan

func Scan() ([]AccessPoint, error)

Scan performs a single Wi-Fi scan pass and returns the list of discovered access points.

type Config

type Config struct {
	Logging LogLevel
	// ArenaPoolSize overrides the default per-target arena pool size (bytes).
	// Zero means use the target default.
	ArenaPoolSize int
}

Config configures the radio and its driver.

type ConnectResult

type ConnectResult struct {
	Connected bool
	SSID      string
	Channel   uint8
	Reason    uint8
}

ConnectResult represents the result of a connection attempt.

type DHCPConfig

type DHCPConfig struct {
	RequestedAddr netip.Addr
}

DHCPConfig configures DHCP address acquisition.

type Error

type Error C.esp_err_t

Error is an error from the radio stack.

func (Error) Error

func (e Error) Error() string

type EthernetDevice

type EthernetDevice interface {
	// SendEthFrame transmits a complete Ethernet frame.
	// The frame includes the Ethernet header but NOT the FCS/CRC
	// trailer (device or stack handles CRC as appropriate).
	// SendEthFrame blocks until the transmission is queued succesfully
	// or finished sending. Should not be called concurrently
	// unless user is sure the driver supports it.
	SendEthFrame(frame []byte) error

	// SetRecvHandler registers the function called when an Ethernet
	// frame is received. After the callback returns the buffer is reused.
	// The callback may or may not be called from an interrupt context
	// so the callback should return fast, ideally copy the packet
	// to a buffer to be processed outside the ISR.
	//
	// We don't use a channel for several reasons:
	//  - Near impossible for channel sender to know lifetime of the buffer;
	//    when is it finished being used?
	//  - Hard to determine best "channel full" semantics
	SetEthRecvHandler(handler func(pkt []byte) error)

	// EthPoll services the device. For poll-based devices (e.g. CYW43439
	// over SPI), reads from the bus and invokes the handler for each
	// received frame. Behaviour for interrupt driven devices is undefined
	// at the moment.
	EthPoll(buf []byte) (bool, error)

	// HardwareAddr6 returns the device's 6-byte MAC address.
	// For PHY-only devices, returns the MAC provided at configuration.
	HardwareAddr6() ([6]byte, error)

	// MaxFrameSize returns the max complete Ethernet frame size
	// (including headers and any overhead) for buffer allocation.
	// MTU can be calculated doing:
	//  // mfu-(14+4+4) for:
	//  // ethernet header+ethernet CRC if present+ethernet VLAN overhead for VLAN support.
	//  mtu := dev.MaxFrameSize() - ethernet.MaxOverheadSize
	MaxFrameSize() int

	// NetFlags offers ability to provide user with notice of the device state.
	// May be also used to encode functioning such as if the device needs FCS/CRC encoding appended
	// to the ethernet packet. WIP.
	NetFlags() net.Flags
}

EthernetDevice is WIP of how ethernet device API design.

Device-specific initialization (WiFi join, PHY auto-negotiation, firmware loading) must complete BEFORE the device is used as a stack endpoint.

type LogLevel

type LogLevel uint8

func (LogLevel) String

func (l LogLevel) String() string

type NetDev

type NetDev struct {
	// contains filtered or unexported fields
}

NetDev provides raw Ethernet frame I/O over the WiFi STA interface.

func StartNetDev

func StartNetDev() (*NetDev, error)

StartNetDev registers the STA RX callback and starts the receive pump.

func StartNetDevAP

func StartNetDevAP() (*NetDev, error)

StartNetDevAP registers the AP RX callback and starts the receive pump.

func (*NetDev) EthPoll

func (nd *NetDev) EthPoll(buf []byte) (bool, error)

EthPoll checks for a received Ethernet frame and calls the receive handler if one is available. EthPoll returns (true, nil) if a frame was received and the handler was called, (false, nil) if no frame was available, or (false, err) if an error occurred.

func (*NetDev) HardwareAddr6

func (nd *NetDev) HardwareAddr6() (mac [6]byte, _ error)

HardwareAddr6 returns the 6-byte MAC address of the WiFi interface.

func (*NetDev) MaxFrameSize

func (nd *NetDev) MaxFrameSize() int

MaxFrameSize returns the maximum Ethernet frame size supported by the driver, including the Ethernet header but excluding CRC.

func (*NetDev) NetFlags

func (nd *NetDev) NetFlags() net.Flags

NetFlags returns the network interface flags for this device. The flags indicate that the interface is up and supports broadcast and multicast.

func (*NetDev) SendEthFrame

func (nd *NetDev) SendEthFrame(frame []byte) error

SendEthFrame sends a raw Ethernet frame out the WiFi interface. The frame must include the Ethernet header and be at least 60 bytes (including CRC, which is not included in the frame). SendEthFrame returns an error if the frame is too short or too long, or if the driver is not ready to send.

func (*NetDev) SetEthRecvHandler

func (nd *NetDev) SetEthRecvHandler(handler func(pkt []byte) error)

SetEthRecvHandler sets the callback to be called when a new Ethernet frame is received.

type STAConfig

type STAConfig struct {
	SSID     string
	Password string
}

STAConfig configures station mode connection parameters.

type Stack

type Stack struct {
	// contains filtered or unexported fields
}

Stack wraps an lneto async network stack on top of a NetDev (EthernetDevice).

func NewStack

func NewStack(dev *NetDev, cfg StackConfig) (*Stack, error)

NewStack creates a new lneto-based TCP/IP stack on top of the given NetDev. The NetDev must already be started (WiFi joined, StartNetDev called).

func (*Stack) Hostname

func (stack *Stack) Hostname() string

Hostname returns the hostname configured on the stack.

func (*Stack) LnetoStack

func (stack *Stack) LnetoStack() *xnet.StackAsync

LnetoStack returns the underlying lneto async stack for advanced use.

func (*Stack) RecvAndSend

func (stack *Stack) RecvAndSend() (send, recv int, err error)

RecvAndSend polls the device for received frames and sends any pending outgoing frames. Returns the number of bytes sent and received.

func (*Stack) SetupWithDHCP

func (stack *Stack) SetupWithDHCP(cfg DHCPConfig) (*xnet.DHCPResults, error)

SetupWithDHCP performs DHCPv4 to obtain an IP address and configures the stack with the results. Blocks until complete or timeout.

type StackConfig

type StackConfig struct {
	StaticAddress netip.Addr
	DNSServer     netip.Addr
	NTPServer     netip.Addr
	Hostname      string
	MaxTCPPorts   int
	MaxUDPPorts   int
	RandSeed      int64
}

StackConfig configures the lneto-based network stack.

Directories

Path Synopsis
examples
ap command
This example shows how to set up an AP with a DHCP server.
This example shows how to set up an AP with a DHCP server.
connect-and-dhcp command
This example shows how to connect to a Wi-Fi network and get an IP address with DHCP.
This example shows how to connect to a Wi-Fi network and get an IP address with DHCP.
hello command
http-app command
This example shows how to create a simple HTTP server that serves a webpage with a button to toggle an LED and a list of recent actions.
This example shows how to create a simple HTTP server that serves a webpage with a button to toggle an LED and a list of recent actions.
http-get command
This example gets an URL using http.Get().
This example gets an URL using http.Get().
http-static command
This example shows how to create a simple HTTP server that serves a static webpage.
This example shows how to create a simple HTTP server that serves a static webpage.
mqtt command
scan command
This example demonstrates how to scan for available Wi-Fi access points using the ESP32 radio.
This example demonstrates how to scan for available Wi-Fi access points using the ESP32 radio.
starting command
webserver command
This example listens on port :80 serving a web page.
This example listens on port :80 serving a web page.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL