Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
CGO_CFLAGS_ALLOW_PATTERN = -fno-short-enums

fmt-check:
test -z "$(shell gofmt -l .)"

unit-test:
tinygo test -target=esp32c3-qemu.json ./...
CGO_CFLAGS_ALLOW='$(CGO_CFLAGS_ALLOW_PATTERN)' tinygo test -target=esp32c3-qemu.json ./...

update: update-esp-wifi
rm -rf blobs/headers
Expand All @@ -22,8 +24,8 @@ smoke-test:
rm -rf build/*
@for example in ./examples/*/; do \
for target in xiao-esp32c3 xiao-esp32s3; do \
echo "tinygo build -target=$$target -size short -o build/$$(basename $$example) $$example"; \
tinygo build -target=$$target -size short -o build/$$(basename $$example) $$example || exit 1; \
echo "CGO_CFLAGS_ALLOW='$(CGO_CFLAGS_ALLOW_PATTERN)' tinygo build -target=$$target -size short -o build/$$(basename $$example) $$example"; \
CGO_CFLAGS_ALLOW='$(CGO_CFLAGS_ALLOW_PATTERN)' tinygo build -target=$$target -size short -o build/$$(basename $$example) $$example || exit 1; \
done; \
done

Expand Down
23 changes: 23 additions & 0 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,29 @@ func (e Error) Error() string {
return "espradio: unknown flash error"
case e >= C.ESP_ERR_MESH_BASE:
return "espradio: unknown mesh error"
case e >= C.ESP_ERR_ESPNOW_BASE:
switch e {
case C.ESP_ERR_ESPNOW_NOT_INIT:
return "espradio: esp-now not initialized"
case C.ESP_ERR_ESPNOW_ARG:
return "espradio: esp-now invalid argument"
case C.ESP_ERR_ESPNOW_NO_MEM:
return "espradio: esp-now out of memory"
case C.ESP_ERR_ESPNOW_FULL:
return "espradio: esp-now peer list full"
case C.ESP_ERR_ESPNOW_NOT_FOUND:
return "espradio: esp-now peer not found"
case C.ESP_ERR_ESPNOW_INTERNAL:
return "espradio: esp-now internal error"
case C.ESP_ERR_ESPNOW_EXIST:
return "espradio: esp-now peer already exists"
case C.ESP_ERR_ESPNOW_IF:
return "espradio: esp-now interface mismatch"
case C.ESP_ERR_ESPNOW_CHAN:
return "espradio: esp-now channel mismatch"
default:
return "espradio: esp-now error " + strconv.FormatInt(int64(int32(e)), 10)
}
case e >= C.ESP_ERR_WIFI_BASE:
code := int32(e)
switch code {
Expand Down
61 changes: 61 additions & 0 deletions espnow.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include "espradio.h"

static void espradio_esp_now_recv_cb(const esp_now_recv_info_t *info, const uint8_t *data, int data_len) {
const uint8_t *src_addr = NULL;
const uint8_t *dest_addr = NULL;
int rssi = 0;
uint8_t channel = 0;
uint8_t secondary_channel = 0;
int noise_floor = 0;
uint32_t timestamp = 0;

if (info != NULL) {
src_addr = info->src_addr;
dest_addr = info->des_addr;
if (info->rx_ctrl != NULL) {
rssi = info->rx_ctrl->rssi;
channel = info->rx_ctrl->channel;
secondary_channel = info->rx_ctrl->secondary_channel;
noise_floor = info->rx_ctrl->noise_floor;
timestamp = info->rx_ctrl->timestamp;
}
}

espradio_on_esp_now_recv(src_addr, dest_addr, rssi, channel, secondary_channel, noise_floor, timestamp, data, data_len);
}

static void espradio_esp_now_send_cb(const esp_now_send_info_t *tx_info, esp_now_send_status_t status) {
const uint8_t *dest_addr = NULL;
const uint8_t *src_addr = NULL;
wifi_interface_t ifidx = WIFI_IF_STA;
wifi_phy_rate_t rate = 0;
wifi_tx_status_t tx_status = WIFI_SEND_FAIL;

if (tx_info != NULL) {
dest_addr = tx_info->des_addr;
src_addr = tx_info->src_addr;
ifidx = tx_info->ifidx;
rate = tx_info->rate;
tx_status = tx_info->tx_status;
}

espradio_on_esp_now_send(dest_addr, src_addr, ifidx, rate, tx_status, status);
}

esp_err_t espradio_esp_now_register_recv_cb(void) {
return esp_now_register_recv_cb(espradio_esp_now_recv_cb);
}

esp_err_t espradio_esp_now_register_send_cb(void) {
return esp_now_register_send_cb(espradio_esp_now_send_cb);
}

esp_err_t espradio_esp_now_fetch_peer(int from_head, esp_now_peer_info_t *peer) {
return esp_now_fetch_peer(from_head != 0, peer);
}

void espradio_esp_now_peer_set_encrypt(esp_now_peer_info_t *peer, int encrypt) {
if (peer != NULL) {
peer->encrypt = encrypt != 0;
}
}
279 changes: 279 additions & 0 deletions espnow.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
//go:build esp32c3 || esp32c3_qemu_target || esp32s3

package espradio

/*
#cgo CFLAGS: -Iblobs/include
#cgo CFLAGS: -Iblobs/include/local
#cgo CFLAGS: -Iblobs/headers
#cgo CFLAGS: -DCONFIG_SOC_WIFI_NAN_SUPPORT=0
#cgo CFLAGS: -DESPRADIO_PHY_PATCH_ROMFUNCS=0
#cgo CFLAGS: -fno-short-enums

#include "espradio.h"
*/
import "C"

import (
"sync"
"unsafe"
)

const (
ESPNowAddressLength = C.ESP_NOW_ETH_ALEN
ESPNowKeyLength = C.ESP_NOW_KEY_LEN
ESPNowMaxDataLength = C.ESP_NOW_MAX_DATA_LEN
)

type WiFiInterface uint8

const (
WiFiInterfaceSTA WiFiInterface = C.WIFI_IF_STA
WiFiInterfaceAP WiFiInterface = C.WIFI_IF_AP
)

type ESPNowSendStatus uint8

const (
ESPNowSendSuccess ESPNowSendStatus = C.ESP_NOW_SEND_SUCCESS
ESPNowSendFail ESPNowSendStatus = C.ESP_NOW_SEND_FAIL
)

type ESPNowPeer struct {
Address [ESPNowAddressLength]byte
Key [ESPNowKeyLength]byte
Channel uint8
If WiFiInterface
Encrypt bool
}

type ESPNowPeerCount struct {
Total int
Encrypted int
}

type ESPNowReceive struct {
SourceAddress [ESPNowAddressLength]byte
DestinationAddress [ESPNowAddressLength]byte
RSSI int8
Channel uint8
SecondaryChannel uint8
NoiseFloor int8
Timestamp uint32
Data []byte
}

type ESPNowSendReport struct {
DestinationAddress [ESPNowAddressLength]byte
SourceAddress [ESPNowAddressLength]byte
If WiFiInterface
Rate uint32
TxStatus ESPNowSendStatus
Status ESPNowSendStatus
}

var (
espNowMu sync.RWMutex
espNowRecvHandler func(ESPNowReceive)
espNowSendHandler func(ESPNowSendReport)
)

// ESPNowInit initializes the ESP-NOW subsystem and registers callback trampolines.
func ESPNowInit() error {
if code := C.esp_now_init(); code != C.ESP_OK {
return makeError(code)
}
if code := C.espradio_esp_now_register_recv_cb(); code != C.ESP_OK {
_ = C.esp_now_deinit()
return makeError(code)
}
if code := C.espradio_esp_now_register_send_cb(); code != C.ESP_OK {
_ = C.esp_now_unregister_recv_cb()
_ = C.esp_now_deinit()
return makeError(code)
}
return nil
}

// ESPNowDeinit deinitializes ESP-NOW and unregisters any callback trampolines.
func ESPNowDeinit() error {
if code := C.esp_now_unregister_recv_cb(); code != C.ESP_OK {
return makeError(code)
}
if code := C.esp_now_unregister_send_cb(); code != C.ESP_OK {
return makeError(code)
}
if code := C.esp_now_deinit(); code != C.ESP_OK {
return makeError(code)
}
return nil
}

// ESPNowVersion returns the underlying ESP-NOW version reported by the SDK.
func ESPNowVersion() (uint32, error) {
var version C.uint32_t
if code := C.esp_now_get_version(&version); code != C.ESP_OK {
return 0, makeError(code)
}
return uint32(version), nil
}

// ESPNowSetPrimaryMasterKey configures the 16-byte PMK used to encrypt LMKs.
func ESPNowSetPrimaryMasterKey(key [ESPNowKeyLength]byte) error {
return makeError(C.esp_now_set_pmk((*C.uint8_t)(unsafe.Pointer(&key[0]))))
}

// ESPNowSetReceiveHandler installs the Go callback for incoming ESP-NOW packets.
func ESPNowSetReceiveHandler(handler func(ESPNowReceive)) {
espNowMu.Lock()
espNowRecvHandler = handler
espNowMu.Unlock()
}

// ESPNowSetSendHandler installs the Go callback for ESP-NOW send completion reports.
func ESPNowSetSendHandler(handler func(ESPNowSendReport)) {
espNowMu.Lock()
espNowSendHandler = handler
espNowMu.Unlock()
}

// ESPNowSend sends a packet to one peer, or to all peers when peer is nil.
func ESPNowSend(peer *[ESPNowAddressLength]byte, data []byte) error {
var peerPtr *C.uint8_t
if peer != nil {
peerPtr = (*C.uint8_t)(unsafe.Pointer(&peer[0]))
}
var dataPtr *C.uint8_t
if len(data) > 0 {
dataPtr = (*C.uint8_t)(unsafe.Pointer(&data[0]))
}
return makeError(C.esp_now_send(peerPtr, dataPtr, C.size_t(len(data))))
}

// ESPNowAddPeer adds a peer to the SDK-maintained peer table.
func ESPNowAddPeer(peer ESPNowPeer) error {
cpeer := cESPNowPeer(peer)
return makeError(C.esp_now_add_peer(&cpeer))
}

// ESPNowDeletePeer removes a peer from the SDK-maintained peer table.
func ESPNowDeletePeer(addr [ESPNowAddressLength]byte) error {
return makeError(C.esp_now_del_peer((*C.uint8_t)(unsafe.Pointer(&addr[0]))))
}

// ESPNowModifyPeer updates an existing peer record.
func ESPNowModifyPeer(peer ESPNowPeer) error {
cpeer := cESPNowPeer(peer)
return makeError(C.esp_now_mod_peer(&cpeer))
}

// ESPNowGetPeer looks up a peer by MAC address.
func ESPNowGetPeer(addr [ESPNowAddressLength]byte) (ESPNowPeer, error) {
var cpeer C.esp_now_peer_info_t
if code := C.esp_now_get_peer((*C.uint8_t)(unsafe.Pointer(&addr[0])), &cpeer); code != C.ESP_OK {
return ESPNowPeer{}, makeError(code)
}
return goESPNowPeer(cpeer), nil
}

// ESPNowFetchPeer fetches the next peer from the peer table.
func ESPNowFetchPeer(fromHead bool) (ESPNowPeer, error) {
var cpeer C.esp_now_peer_info_t
if code := C.espradio_esp_now_fetch_peer(C.int(boolToInt(fromHead)), &cpeer); code != C.ESP_OK {
return ESPNowPeer{}, makeError(code)
}
return goESPNowPeer(cpeer), nil
}

// ESPNowPeerExists reports whether a peer exists in the peer table.
func ESPNowPeerExists(addr [ESPNowAddressLength]byte) bool {
return bool(C.esp_now_is_peer_exist((*C.uint8_t)(unsafe.Pointer(&addr[0]))))
}

// ESPNowGetPeerCount returns the total and encrypted peer counts.
func ESPNowGetPeerCount() (ESPNowPeerCount, error) {
var counts C.esp_now_peer_num_t
if code := C.esp_now_get_peer_num(&counts); code != C.ESP_OK {
return ESPNowPeerCount{}, makeError(code)
}
return ESPNowPeerCount{
Total: int(counts.total_num),
Encrypted: int(counts.encrypt_num),
}, nil
}

func cESPNowPeer(peer ESPNowPeer) C.esp_now_peer_info_t {
var cpeer C.esp_now_peer_info_t
copy(cArrayToBytes((*C.uint8_t)(unsafe.Pointer(&cpeer.peer_addr[0])), ESPNowAddressLength), peer.Address[:])
copy(cArrayToBytes((*C.uint8_t)(unsafe.Pointer(&cpeer.lmk[0])), ESPNowKeyLength), peer.Key[:])
cpeer.channel = C.uint8_t(peer.Channel)
cpeer.ifidx = C.wifi_interface_t(peer.If)
C.espradio_esp_now_peer_set_encrypt(&cpeer, C.int(boolToInt(peer.Encrypt)))
cpeer.priv = nil
return cpeer
}

func goESPNowPeer(peer C.esp_now_peer_info_t) ESPNowPeer {
var out ESPNowPeer
copy(out.Address[:], cArrayToBytes((*C.uint8_t)(unsafe.Pointer(&peer.peer_addr[0])), ESPNowAddressLength))
copy(out.Key[:], cArrayToBytes((*C.uint8_t)(unsafe.Pointer(&peer.lmk[0])), ESPNowKeyLength))
out.Channel = uint8(peer.channel)
out.If = WiFiInterface(peer.ifidx)
out.Encrypt = bool(peer.encrypt)
return out
}

func cArrayToBytes(ptr *C.uint8_t, n int) []byte {
return unsafe.Slice((*byte)(unsafe.Pointer(ptr)), n)
}

func copyMAC(ptr *C.uint8_t) [ESPNowAddressLength]byte {
var out [ESPNowAddressLength]byte
if ptr != nil {
copy(out[:], cArrayToBytes(ptr, ESPNowAddressLength))
}
return out
}

//export espradio_on_esp_now_recv
func espradio_on_esp_now_recv(srcAddr, destAddr *C.uint8_t, rssi C.int, channel, secondaryChannel C.uint8_t, noiseFloor C.int, timestamp C.uint32_t, data *C.uint8_t, dataLen C.int) {
espNowMu.RLock()
handler := espNowRecvHandler
espNowMu.RUnlock()
if handler == nil {
return
}

event := ESPNowReceive{
SourceAddress: copyMAC(srcAddr),
DestinationAddress: copyMAC(destAddr),
RSSI: int8(rssi),
Channel: uint8(channel),
SecondaryChannel: uint8(secondaryChannel),
NoiseFloor: int8(noiseFloor),
Timestamp: uint32(timestamp),
}
if data != nil && dataLen > 0 {
event.Data = C.GoBytes(unsafe.Pointer(data), dataLen)
}
handler(event)
}

//export espradio_on_esp_now_send
func espradio_on_esp_now_send(destAddr, srcAddr *C.uint8_t, ifidx C.wifi_interface_t, rate C.wifi_phy_rate_t, txStatus C.wifi_tx_status_t, status C.esp_now_send_status_t) {
espNowMu.RLock()
handler := espNowSendHandler
espNowMu.RUnlock()
if handler == nil {
return
}

handler(ESPNowSendReport{
DestinationAddress: copyMAC(destAddr),
SourceAddress: copyMAC(srcAddr),
If: WiFiInterface(ifidx),
Rate: uint32(rate),
TxStatus: ESPNowSendStatus(txStatus),
Status: ESPNowSendStatus(status),
})
}
Loading
Loading