Merge remote-tracking branch 'origin/dev-esp32' into matrix-keyboard
This commit is contained in:
commit
ba990c89a1
|
@ -132,10 +132,11 @@ static const char *upvalname (Proto *p, int uv) {
|
|||
|
||||
static const char *findvararg (CallInfo *ci, int n, StkId *pos) {
|
||||
int nparams = getnumparams(clLvalue(ci->func)->p);
|
||||
if (n >= cast_int(ci->u.l.base - ci->func) - nparams)
|
||||
int nvararg = cast_int(ci->u.l.base - ci->func) - nparams;
|
||||
if (n <= -nvararg)
|
||||
return NULL; /* no such vararg */
|
||||
else {
|
||||
*pos = ci->func + nparams + n;
|
||||
*pos = ci->func + nparams - n;
|
||||
return "(*vararg)"; /* generic name for any vararg */
|
||||
}
|
||||
}
|
||||
|
@ -147,7 +148,7 @@ static const char *findlocal (lua_State *L, CallInfo *ci, int n,
|
|||
StkId base;
|
||||
if (isLua(ci)) {
|
||||
if (n < 0) /* access to vararg values? */
|
||||
return findvararg(ci, -n, pos);
|
||||
return findvararg(ci, n, pos);
|
||||
else {
|
||||
base = ci->u.l.base;
|
||||
name = luaF_getlocalname(ci_func(ci)->p, n, currentpc(ci));
|
||||
|
|
|
@ -8,6 +8,7 @@ set(module_srcs
|
|||
"dht.c"
|
||||
"encoder.c"
|
||||
"eromfs.c"
|
||||
"espnow.c"
|
||||
"file.c"
|
||||
"gpio.c"
|
||||
"heaptrace.c"
|
||||
|
|
|
@ -82,6 +82,12 @@ menu "NodeMCU modules"
|
|||
store the directory path as part of the filename just as SPIFFS
|
||||
does.
|
||||
|
||||
config NODEMCU_CMODULE_ESPNOW
|
||||
bool "ESP-NOW module"
|
||||
default "n"
|
||||
help
|
||||
Includes the espnow module.
|
||||
|
||||
config NODEMCU_CMODULE_ETH
|
||||
depends on IDF_TARGET_ESP32
|
||||
select ETH_USE_ESP32_EMAC
|
||||
|
|
|
@ -0,0 +1,390 @@
|
|||
/*
|
||||
* Copyright 2024 Dius Computing Pty Ltd. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* - Neither the name of the copyright holders nor the names of
|
||||
* its contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
||||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @author Jade Mattsson <jmattsson@dius.com.au>
|
||||
*/
|
||||
#include "module.h"
|
||||
#include "platform.h"
|
||||
#include "ip_fmt.h"
|
||||
#include "task/task.h"
|
||||
#include "lauxlib.h"
|
||||
#include "esp_now.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#if ESP_NOW_ETH_ALEN != 6
|
||||
# error "MAC address length assumption broken"
|
||||
#endif
|
||||
#if ESP_NOW_MAX_DATA_LEN > 0xffff
|
||||
# error "Update len field in received_packet_t"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint8_t src[6];
|
||||
uint8_t dst[6];
|
||||
int16_t rssi;
|
||||
uint16_t len; // ESP_NOW_MAX_DATA_LEN is currently 250
|
||||
char data[0];
|
||||
} received_packet_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t dst[6];
|
||||
esp_now_send_status_t status;
|
||||
} sent_packet_t;
|
||||
|
||||
|
||||
static task_handle_t espnow_task = 0;
|
||||
|
||||
static int recv_ref = LUA_NOREF;
|
||||
static int sent_ref = LUA_NOREF;
|
||||
|
||||
|
||||
// --- Helper functions -----------------------------------
|
||||
|
||||
static int *cb_ref_for_event(const char *name)
|
||||
{
|
||||
if (strcmp("receive", name) == 0)
|
||||
return &recv_ref;
|
||||
else if (strcmp("sent", name) == 0)
|
||||
return &sent_ref;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int hexval(lua_State *L, char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
else if (c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
else if (c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
else
|
||||
return luaL_error(L, "invalid hex digit '%c'", c);
|
||||
}
|
||||
|
||||
// TODO: share with wifi_sta.c
|
||||
static bool parse_mac(const char *str, uint8_t out[6])
|
||||
{
|
||||
const char *fmts[] = {
|
||||
"%hhx%hhx%hhx%hhx%hhx%hhx",
|
||||
"%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
|
||||
"%hhx-%hhx-%hhx-%hhx-%hhx-%hhx",
|
||||
"%hhx %hhx %hhx %hhx %hhx %hhx",
|
||||
NULL
|
||||
};
|
||||
for (unsigned i = 0; fmts[i]; ++i)
|
||||
{
|
||||
if (sscanf (str, fmts[i],
|
||||
&out[0], &out[1], &out[2], &out[3], &out[4], &out[5]) == 6)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static void on_receive(const esp_now_recv_info_t *info, const uint8_t *data, int len)
|
||||
{
|
||||
if (len < 0)
|
||||
return; // Don't do that.
|
||||
|
||||
received_packet_t *p = malloc(sizeof(received_packet_t) + len);
|
||||
if (!p)
|
||||
{
|
||||
NODE_ERR("out of memory\n");
|
||||
return;
|
||||
}
|
||||
memcpy(p->src, info->src_addr, sizeof(p->src));
|
||||
memcpy(p->dst, info->des_addr, sizeof(p->dst));
|
||||
p->rssi = info->rx_ctrl->rssi;
|
||||
p->len = len;
|
||||
memcpy(p->data, data, len);
|
||||
|
||||
if (!task_post_high(espnow_task, (task_param_t)p))
|
||||
{
|
||||
NODE_ERR("lost esp-now packet; task queue full\n");
|
||||
free(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void on_sent(const uint8_t *mac, esp_now_send_status_t status)
|
||||
{
|
||||
sent_packet_t *p = malloc(sizeof(sent_packet_t));
|
||||
if (!p)
|
||||
{
|
||||
NODE_ERR("out of memory\n");
|
||||
return;
|
||||
}
|
||||
memcpy(p->dst, mac, sizeof(p->dst));
|
||||
p->status = status;
|
||||
|
||||
if (!task_post_medium(espnow_task, (task_param_t)p))
|
||||
{
|
||||
NODE_ERR("lost esp-now packet; task queue full\n");
|
||||
free(p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void espnow_task_fn(task_param_t param, task_prio_t prio)
|
||||
{
|
||||
lua_State* L = lua_getstate();
|
||||
int top = lua_gettop(L);
|
||||
luaL_checkstack(L, 3, "");
|
||||
|
||||
if (prio == TASK_PRIORITY_HIGH) // received packet
|
||||
{
|
||||
received_packet_t *p = (received_packet_t *)param;
|
||||
if (recv_ref != LUA_NOREF)
|
||||
{
|
||||
lua_rawgeti (L, LUA_REGISTRYINDEX, recv_ref);
|
||||
lua_createtable(L, 0, 4); // src, dst, rssi, data
|
||||
char mac[MAC_STR_SZ];
|
||||
macstr(mac, p->src);
|
||||
lua_pushstring(L, mac);
|
||||
lua_setfield(L, -2, "src");
|
||||
macstr(mac, p->dst);
|
||||
lua_pushstring(L, mac);
|
||||
lua_setfield(L, -2, "dst");
|
||||
lua_pushinteger(L, p->rssi);
|
||||
lua_setfield(L, -2, "rssi");
|
||||
lua_pushlstring(L, p->data, p->len);
|
||||
lua_setfield(L, -2, "data");
|
||||
luaL_pcallx(L, 1, 0);
|
||||
}
|
||||
free(p);
|
||||
}
|
||||
else if (prio == TASK_PRIORITY_MEDIUM) // sent
|
||||
{
|
||||
sent_packet_t *p = (sent_packet_t *)param;
|
||||
if (sent_ref != LUA_NOREF)
|
||||
{
|
||||
lua_rawgeti (L, LUA_REGISTRYINDEX, sent_ref);
|
||||
char dst[MAC_STR_SZ];
|
||||
macstr(dst, p->dst);
|
||||
lua_pushstring(L, dst);
|
||||
if (p->status == ESP_NOW_SEND_SUCCESS)
|
||||
lua_pushinteger(L, 1);
|
||||
else
|
||||
lua_pushnil(L);
|
||||
luaL_pcallx(L, 2, 0);
|
||||
}
|
||||
free(p);
|
||||
}
|
||||
|
||||
lua_settop(L, top); // restore original before exit
|
||||
}
|
||||
|
||||
|
||||
static void err_check(lua_State *L, esp_err_t err)
|
||||
{
|
||||
if (err != ESP_OK)
|
||||
luaL_error(L, "%s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
|
||||
// --- Lua interface functions -----------------------------------
|
||||
|
||||
static int lespnow_start(lua_State *L)
|
||||
{
|
||||
err_check(L, esp_now_init());
|
||||
err_check(L, esp_now_register_recv_cb(on_receive));
|
||||
err_check(L, esp_now_register_send_cb(on_sent));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lespnow_stop(lua_State *L)
|
||||
{
|
||||
err_check(L, esp_now_unregister_send_cb());
|
||||
err_check(L, esp_now_unregister_recv_cb());
|
||||
err_check(L, esp_now_deinit());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lespnow_getversion(lua_State *L)
|
||||
{
|
||||
uint32_t ver;
|
||||
err_check(L, esp_now_get_version(&ver));
|
||||
lua_pushinteger(L, ver);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// espnow.on('sent' or 'received', cb)
|
||||
// sent -> cb('ma:ca:dd:00:11:22', status)
|
||||
// received -> cb({ src=, dst=, data=, rssi= })
|
||||
static int lespnow_on(lua_State *L)
|
||||
{
|
||||
const char *evtname = luaL_checkstring(L, 1);
|
||||
int *ref = cb_ref_for_event(evtname);
|
||||
if (!ref)
|
||||
return luaL_error(L, "unknown event type");
|
||||
|
||||
if (lua_isnoneornil(L, 2))
|
||||
{
|
||||
if (*ref != LUA_NOREF)
|
||||
{
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, *ref);
|
||||
*ref = LUA_NOREF;
|
||||
}
|
||||
}
|
||||
else if (lua_isfunction(L, 2))
|
||||
{
|
||||
if (*ref != LUA_NOREF)
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, *ref);
|
||||
lua_pushvalue(L, 2);
|
||||
*ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
}
|
||||
else
|
||||
return luaL_error(L, "expected function");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// espnow.send('ma:ca:dd:00:11:22' or nil, str)
|
||||
static int lespnow_send(lua_State *L)
|
||||
{
|
||||
const char *mac = luaL_optstring(L, 1, NULL);
|
||||
size_t payloadlen = 0;
|
||||
const char *payload = luaL_checklstring(L, 2, &payloadlen);
|
||||
|
||||
uint8_t peer_addr[6];
|
||||
if (mac && !parse_mac(mac, peer_addr))
|
||||
return luaL_error(L, "bad peer address");
|
||||
|
||||
err_check(L, esp_now_send(
|
||||
mac ? peer_addr : NULL, (const uint8_t *)payload, payloadlen));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// espnow.addpeer('ma:ca:dd:00:11:22', { lmk=, channel=, encrypt= }
|
||||
static int lespnow_addpeer(lua_State *L)
|
||||
{
|
||||
esp_now_peer_info_t peer_info;
|
||||
memset(&peer_info, 0, sizeof(peer_info));
|
||||
|
||||
const char *mac = luaL_checkstring(L, 1);
|
||||
|
||||
wifi_mode_t mode = WIFI_MODE_NULL;
|
||||
err_check(L, esp_wifi_get_mode(&mode));
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case WIFI_MODE_STA: peer_info.ifidx = WIFI_IF_STA; break;
|
||||
case WIFI_MODE_APSTA: // fall-through
|
||||
case WIFI_MODE_AP: peer_info.ifidx = WIFI_IF_AP; break;
|
||||
default: return luaL_error(L, "No wifi interface found");
|
||||
}
|
||||
|
||||
if (!parse_mac(mac, peer_info.peer_addr))
|
||||
return luaL_error(L, "bad peer address");
|
||||
|
||||
lua_settop(L, 2); // Discard excess parameters, to ensure we have space
|
||||
if (lua_istable(L, 2))
|
||||
{
|
||||
lua_getfield(L, 2, "encrypt");
|
||||
peer_info.encrypt = luaL_optint(L, -1, 0);
|
||||
lua_pop(L, 1);
|
||||
if (peer_info.encrypt)
|
||||
{
|
||||
lua_getfield(L, 2, "lmk");
|
||||
size_t lmklen = 0;
|
||||
const char *lmkstr = luaL_checklstring(L, -1, &lmklen);
|
||||
lua_pop(L, 1);
|
||||
if (lmklen != 2*sizeof(peer_info.lmk))
|
||||
return luaL_error(L, "LMK must be %d hex digits", 2*ESP_NOW_KEY_LEN);
|
||||
for (unsigned i = 0; i < sizeof(peer_info.lmk); ++i)
|
||||
peer_info.lmk[i] =
|
||||
(hexval(L, lmkstr[i*2]) << 4) + hexval(L, lmkstr[i*2 +1]);
|
||||
}
|
||||
|
||||
lua_getfield(L, 2, "channel");
|
||||
peer_info.channel = luaL_optint(L, -1, 0);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
err_check(L, esp_now_add_peer(&peer_info));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// espnow.delpeer('ma:ca:dd:00:11:22')
|
||||
static int lespnow_delpeer(lua_State *L)
|
||||
{
|
||||
const char *mac = luaL_checkstring(L, 1);
|
||||
uint8_t peer_addr[6];
|
||||
if (!parse_mac(mac, peer_addr))
|
||||
return luaL_error(L, "bad peer address");
|
||||
|
||||
err_check(L, esp_now_del_peer(peer_addr));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lespnow_setpmk(lua_State *L)
|
||||
{
|
||||
uint8_t pmk[ESP_NOW_KEY_LEN] = { 0, };
|
||||
size_t len = 0;
|
||||
const char *str = luaL_checklstring(L, 1, &len);
|
||||
if (len != sizeof(pmk) * 2)
|
||||
return luaL_error(L, "PMK must be %d hex digits", 2*ESP_NOW_KEY_LEN);
|
||||
for (unsigned i = 0; i < sizeof(pmk); ++i)
|
||||
pmk[i] = (hexval(L, str[i*2]) << 4) + hexval(L, str[i*2 +1]);
|
||||
|
||||
err_check(L, esp_now_set_pmk(pmk));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lespnow_setwakewindow(lua_State *L)
|
||||
{
|
||||
int n = luaL_checkint(L, 1);
|
||||
if (n < 0 || n > 0xffff)
|
||||
return luaL_error(L, "wake window out of bounds");
|
||||
err_check(L, esp_now_set_wake_window(n));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lespnow_init(lua_State *L)
|
||||
{
|
||||
espnow_task = task_get_id(espnow_task_fn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
LROT_BEGIN(espnow, NULL, 0)
|
||||
LROT_FUNCENTRY( start, lespnow_start )
|
||||
LROT_FUNCENTRY( stop, lespnow_stop )
|
||||
LROT_FUNCENTRY( getversion, lespnow_getversion )
|
||||
LROT_FUNCENTRY( on, lespnow_on ) // 'receive', 'sent'
|
||||
LROT_FUNCENTRY( send, lespnow_send )
|
||||
LROT_FUNCENTRY( addpeer, lespnow_addpeer )
|
||||
LROT_FUNCENTRY( delpeer, lespnow_delpeer )
|
||||
LROT_FUNCENTRY( setpmk, lespnow_setpmk )
|
||||
LROT_FUNCENTRY( setwakewindow, lespnow_setwakewindow )
|
||||
LROT_END(espnow, NULL, 0)
|
||||
|
||||
NODEMCU_MODULE(ESPNOW, "espnow", espnow, lespnow_init);
|
|
@ -93,12 +93,14 @@ typedef struct Header {
|
|||
} Header;
|
||||
|
||||
|
||||
static int getnum (const char **fmt, int df) {
|
||||
static int getnum (lua_State *L, const char **fmt, int df) {
|
||||
if (!isdigit((unsigned char)**fmt)) /* no number? */
|
||||
return df; /* return default value */
|
||||
else {
|
||||
int a = 0;
|
||||
do {
|
||||
if (a > (INT_MAX / 10) || a * 10 > (INT_MAX - (**fmt - '0')))
|
||||
luaL_error(L, "integral size overflow");
|
||||
a = a*10 + *((*fmt)++) - '0';
|
||||
} while (isdigit((unsigned char)**fmt));
|
||||
return a;
|
||||
|
@ -121,9 +123,9 @@ static size_t optsize (lua_State *L, char opt, const char **fmt) {
|
|||
case 'd': return sizeof(double);
|
||||
#endif
|
||||
case 'x': return 1;
|
||||
case 'c': return getnum(fmt, 1);
|
||||
case 'c': return getnum(L, fmt, 1);
|
||||
case 'i': case 'I': {
|
||||
int sz = getnum(fmt, sizeof(int));
|
||||
int sz = getnum(L, fmt, sizeof(int));
|
||||
if (sz > MAXINTSIZE)
|
||||
luaL_error(L, "integral size %d is larger than limit of %d",
|
||||
sz, MAXINTSIZE);
|
||||
|
@ -156,7 +158,7 @@ static void controloptions (lua_State *L, int opt, const char **fmt,
|
|||
case '>': h->endian = BIG; return;
|
||||
case '<': h->endian = LITTLE; return;
|
||||
case '!': {
|
||||
int a = getnum(fmt, MAXALIGN);
|
||||
int a = getnum(L, fmt, MAXALIGN);
|
||||
if (!isp2(a))
|
||||
luaL_error(L, "alignment %d is not a power of 2", a);
|
||||
h->align = a;
|
||||
|
|
|
@ -11,6 +11,14 @@
|
|||
#define SHIFT_LOGICAL 0
|
||||
#define SHIFT_CIRCULAR 1
|
||||
|
||||
// The default bit H & L durations in multiples of 100ns.
|
||||
#define WS2812_DURATION_T0H 4
|
||||
#define WS2812_DURATION_T0L 7
|
||||
#define WS2812_DURATION_T1H 8
|
||||
#define WS2812_DURATION_T1L 6
|
||||
// The default reset duration in multiples of 100ns.
|
||||
#define WS2812_DURATION_RESET 512
|
||||
|
||||
|
||||
typedef struct {
|
||||
int size;
|
||||
|
@ -33,6 +41,7 @@ static void ws2812_cleanup( lua_State *L, int pop )
|
|||
// ws2812.write({pin = 4, data = string.char(255, 0, 0, 255, 255, 255)}) first LED green, second LED white.
|
||||
static int ws2812_write( lua_State* L )
|
||||
{
|
||||
int type;
|
||||
int top = lua_gettop( L );
|
||||
|
||||
for (int stack = 1; stack <= top; stack++) {
|
||||
|
@ -56,6 +65,106 @@ static int ws2812_write( lua_State* L )
|
|||
int gpio_num = luaL_checkint( L, -1 );
|
||||
lua_pop( L, 1 );
|
||||
|
||||
//
|
||||
// retrieve reset
|
||||
// This is an optional parameter which defaults to WS2812_DURATION_RESET.
|
||||
//
|
||||
int reset = WS2812_DURATION_RESET;
|
||||
type = lua_getfield( L, stack, "reset" );
|
||||
if (type!=LUA_TNIL )
|
||||
{
|
||||
if (!lua_isnumber( L, -1 )) {
|
||||
ws2812_cleanup( L, 1 );
|
||||
return luaL_argerror( L, stack, "invalid reset" );
|
||||
}
|
||||
reset = luaL_checkint( L, -1 );
|
||||
if ((reset<0) || (reset>0xfffe)) {
|
||||
ws2812_cleanup( L, 1 );
|
||||
return luaL_argerror( L, stack, "reset must be 0<=reset<=65534" );
|
||||
}
|
||||
}
|
||||
lua_pop( L, 1 );
|
||||
|
||||
//
|
||||
// retrieve t0h
|
||||
// This is an optional parameter which defaults to WS2812_DURATION_T0H.
|
||||
//
|
||||
int t0h = WS2812_DURATION_T0H;
|
||||
type = lua_getfield( L, stack, "t0h" );
|
||||
if (type!=LUA_TNIL )
|
||||
{
|
||||
if (!lua_isnumber( L, -1 )) {
|
||||
ws2812_cleanup( L, 1 );
|
||||
return luaL_argerror( L, stack, "invalid t0h" );
|
||||
}
|
||||
t0h = luaL_checkint( L, -1 );
|
||||
if ((t0h<1) || (t0h>0x7fff)) {
|
||||
ws2812_cleanup( L, 1 );
|
||||
return luaL_argerror( L, stack, "t0h must be 1<=t0h<=32767" );
|
||||
}
|
||||
}
|
||||
lua_pop( L, 1 );
|
||||
|
||||
//
|
||||
// retrieve t0l
|
||||
// This is an optional parameter which defaults to WS2812_DURATION_T0L.
|
||||
//
|
||||
int t0l = WS2812_DURATION_T0L;
|
||||
type = lua_getfield( L, stack, "t0l" );
|
||||
if (type!=LUA_TNIL )
|
||||
{
|
||||
if (!lua_isnumber( L, -1 )) {
|
||||
ws2812_cleanup( L, 1 );
|
||||
return luaL_argerror( L, stack, "invalid t0l" );
|
||||
}
|
||||
t0l = luaL_checkint( L, -1 );
|
||||
if ((t0l<1) || (t0l>0x7fff)) {
|
||||
ws2812_cleanup( L, 1 );
|
||||
return luaL_argerror( L, stack, "t0l must be 1<=t0l<=32767" );
|
||||
}
|
||||
}
|
||||
lua_pop( L, 1 );
|
||||
|
||||
//
|
||||
// retrieve t1h
|
||||
// This is an optional parameter which defaults to WS2812_DURATION_T1H.
|
||||
//
|
||||
int t1h = WS2812_DURATION_T1H;
|
||||
type = lua_getfield( L, stack, "t1h" );
|
||||
if (type!=LUA_TNIL )
|
||||
{
|
||||
if (!lua_isnumber( L, -1 )) {
|
||||
ws2812_cleanup( L, 1 );
|
||||
return luaL_argerror( L, stack, "invalid t1h" );
|
||||
}
|
||||
t1h = luaL_checkint( L, -1 );
|
||||
if ((t1h<1) || (t1h>0x7fff)) {
|
||||
ws2812_cleanup( L, 1 );
|
||||
return luaL_argerror( L, stack, "t1h must be 1<=t1h<=32767" );
|
||||
}
|
||||
}
|
||||
lua_pop( L, 1 );
|
||||
|
||||
//
|
||||
// retrieve t1l
|
||||
// This is an optional parameter which defaults to WS2812_DURATION_T1L.
|
||||
//
|
||||
int t1l = WS2812_DURATION_T1L;
|
||||
type = lua_getfield( L, stack, "t1l" );
|
||||
if (type!=LUA_TNIL )
|
||||
{
|
||||
if (!lua_isnumber( L, -1 )) {
|
||||
ws2812_cleanup( L, 1 );
|
||||
return luaL_argerror( L, stack, "invalid t1l" );
|
||||
}
|
||||
t1l = luaL_checkint( L, -1 );
|
||||
if ((t1l<1) || (t1l>0x7fff)) {
|
||||
ws2812_cleanup( L, 1 );
|
||||
return luaL_argerror( L, stack, "t1l must be 1<=t1l<=32767" );
|
||||
}
|
||||
}
|
||||
lua_pop( L, 1 );
|
||||
|
||||
//
|
||||
// retrieve data
|
||||
//
|
||||
|
@ -83,7 +192,7 @@ static int ws2812_write( lua_State* L )
|
|||
lua_pop( L, 1 );
|
||||
|
||||
// prepare channel
|
||||
if (platform_ws2812_setup( gpio_num, 1, (const uint8_t *)data, length ) != PLATFORM_OK) {
|
||||
if (platform_ws2812_setup( gpio_num, reset, t0h, t0l, t1h, t1l, (const uint8_t *)data, length ) != PLATFORM_OK) {
|
||||
ws2812_cleanup( L, 0 );
|
||||
return luaL_argerror( L, stack, "can't set up chain" );
|
||||
}
|
||||
|
|
|
@ -209,7 +209,7 @@ int platform_dht_read( uint8_t gpio_num, uint8_t wakeup_ms, uint8_t *data );
|
|||
// WS2812 platform interface
|
||||
|
||||
void platform_ws2812_init( void );
|
||||
int platform_ws2812_setup( uint8_t gpio_num, uint8_t num_mem, const uint8_t *data, size_t len );
|
||||
int platform_ws2812_setup( uint8_t gpio_num, uint32_t reset, uint32_t bit0h, uint32_t bit0l, uint32_t bit1h, uint32_t bit1l, const uint8_t *data, size_t len );
|
||||
int platform_ws2812_release( void );
|
||||
int platform_ws2812_send( void );
|
||||
|
||||
|
|
|
@ -32,112 +32,180 @@
|
|||
#include "soc/periph_defs.h"
|
||||
#include "rom/gpio.h" // for gpio_matrix_out()
|
||||
#include "soc/gpio_periph.h"
|
||||
#include "soc/rmt_reg.h"
|
||||
|
||||
#undef WS2812_DEBUG
|
||||
|
||||
// If either of these fails, the reset logic in ws2812_sample_to_rmt will need revisiting.
|
||||
_Static_assert(SOC_RMT_MEM_WORDS_PER_CHANNEL % 8 == 0,
|
||||
"SOC_RMT_MEM_WORDS_PER_CHANNEL is assumed to be a multiple of 8");
|
||||
_Static_assert(SOC_RMT_MEM_WORDS_PER_CHANNEL >= 16,
|
||||
"SOC_RMT_MEM_WORDS_PER_CHANNEL is assumed to be >= 16");
|
||||
|
||||
// divider to generate 100ns base period from 80MHz APB clock
|
||||
#define WS2812_CLKDIV (100 * 80 /1000)
|
||||
// bit H & L durations in multiples of 100ns
|
||||
#define WS2812_DURATION_T0H 4
|
||||
#define WS2812_DURATION_T0L 7
|
||||
#define WS2812_DURATION_T1H 8
|
||||
#define WS2812_DURATION_T1L 6
|
||||
#define WS2812_DURATION_RESET (50000 / 100)
|
||||
|
||||
// 0 bit in rmt encoding
|
||||
const rmt_item32_t ws2812_rmt_bit0 = {
|
||||
.level0 = 1,
|
||||
.duration0 = WS2812_DURATION_T0H,
|
||||
.level1 = 0,
|
||||
.duration1 = WS2812_DURATION_T0L
|
||||
};
|
||||
// 1 bit in rmt encoding
|
||||
const rmt_item32_t ws2812_rmt_bit1 = {
|
||||
.level0 = 1,
|
||||
.duration0 = WS2812_DURATION_T1H,
|
||||
.level1 = 0,
|
||||
.duration1 = WS2812_DURATION_T1L
|
||||
};
|
||||
|
||||
// This is one eighth of 512 * 100ns, ie in total a bit above the requisite 50us
|
||||
const rmt_item32_t ws2812_rmt_reset = { .level0 = 0, .duration0 = 32, .level1 = 0, .duration1 = 32 };
|
||||
|
||||
// descriptor for a ws2812 chain
|
||||
typedef struct {
|
||||
bool valid;
|
||||
bool needs_reset;
|
||||
uint8_t gpio;
|
||||
rmt_item32_t reset;
|
||||
rmt_item32_t bits[2];
|
||||
const uint8_t *data;
|
||||
size_t len;
|
||||
uint8_t bitpos;
|
||||
} ws2812_chain_t;
|
||||
|
||||
// chain descriptor array
|
||||
static ws2812_chain_t ws2812_chains[RMT_CHANNEL_MAX];
|
||||
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
|
||||
static void ws2812_sample_to_rmt(const void *src, rmt_item32_t *dest, size_t src_size, size_t wanted_num, size_t *translated_size, size_t *item_num)
|
||||
{
|
||||
// Note: enabling these commented-out logs will ruin the timing so nothing
|
||||
// will actually work when they're enabled. But I've kept them in as comments
|
||||
// because they were useful in debugging the buffer management.
|
||||
// ESP_DRAM_LOGW("ws2812", "ws2812_sample_to_rmt wanted=%u src_size=%u", wanted_num, src_size);
|
||||
size_t cnt_in;
|
||||
size_t cnt_out;
|
||||
const uint8_t *pucData;
|
||||
uint8_t ucData;
|
||||
uint8_t ucBitPos;
|
||||
esp_err_t tStatus;
|
||||
void *pvContext;
|
||||
ws2812_chain_t *ptContext;
|
||||
uint8_t ucBit;
|
||||
|
||||
void *ctx;
|
||||
rmt_translator_get_context(item_num, &ctx);
|
||||
ws2812_chain_t *chain = (ws2812_chain_t *)ctx;
|
||||
cnt_in = 0;
|
||||
cnt_out = 0;
|
||||
if( dest!=NULL && wanted_num>0 )
|
||||
{
|
||||
tStatus = rmt_translator_get_context(item_num, &pvContext);
|
||||
if( tStatus==ESP_OK )
|
||||
{
|
||||
ptContext = (ws2812_chain_t *)pvContext;
|
||||
|
||||
size_t reset_num = 0;
|
||||
if (chain->needs_reset) {
|
||||
// Haven't sent reset yet
|
||||
if( ptContext->needs_reset==true )
|
||||
{
|
||||
dest[cnt_out++] = ptContext->reset;
|
||||
ptContext->needs_reset = false;
|
||||
}
|
||||
if( src!=NULL && src_size>0 )
|
||||
{
|
||||
ucBitPos = ptContext->bitpos;
|
||||
|
||||
// We split the reset into 8 even though it would fit in a single
|
||||
// rmt_item32_t, simply so that dest stays 8-item aligned which means we
|
||||
// don't have to worry about having to split a byte of src across multiple
|
||||
// blocks (assuming the static asserts at the top of this file are true).
|
||||
for (int i = 0; i < 8; i++) {
|
||||
dest[i] = ws2812_rmt_reset;
|
||||
}
|
||||
dest += 8;
|
||||
wanted_num -= 8;
|
||||
reset_num = 8;
|
||||
chain->needs_reset = false;
|
||||
}
|
||||
/* Each bit of the input data is converted into one RMT item. */
|
||||
|
||||
// Now write the actual data from src
|
||||
const uint8_t *data = (const uint8_t *)src;
|
||||
size_t data_num = MIN(wanted_num, src_size * 8) / 8;
|
||||
for (size_t idx = 0; idx < data_num; idx++) {
|
||||
uint8_t byte = data[idx];
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
dest[idx * 8 + i] = (byte & 0x80) ? ws2812_rmt_bit1 : ws2812_rmt_bit0;
|
||||
byte <<= 1;
|
||||
pucData = (const uint8_t*)src;
|
||||
/* Get the current byte. */
|
||||
ucData = pucData[cnt_in] << ucBitPos;
|
||||
|
||||
while( cnt_in<src_size && cnt_out<wanted_num )
|
||||
{
|
||||
/* Get the current bit. */
|
||||
ucBit = (ucData & 0x80U) >> 7U;
|
||||
/* Translate the bit to a WS2812 input code. */
|
||||
dest[cnt_out++] = ptContext->bits[ucBit];
|
||||
/* Move to the next bit. */
|
||||
++ucBitPos;
|
||||
if( ucBitPos<8U )
|
||||
{
|
||||
ucData <<= 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
ucBitPos = 0U;
|
||||
++cnt_in;
|
||||
ucData = pucData[cnt_in];
|
||||
}
|
||||
}
|
||||
|
||||
ptContext->bitpos = ucBitPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*translated_size = data_num;
|
||||
*item_num = reset_num + data_num * 8;
|
||||
// ESP_DRAM_LOGW("ws2812", "src bytes consumed: %u total rmt items: %u", *translated_size, *item_num);
|
||||
*translated_size = cnt_in;
|
||||
*item_num = cnt_out;
|
||||
}
|
||||
|
||||
int platform_ws2812_setup( uint8_t gpio_num, uint8_t num_mem, const uint8_t *data, size_t len )
|
||||
int platform_ws2812_setup( uint8_t gpio_num, uint32_t reset, uint32_t t0h, uint32_t t0l, uint32_t t1h, uint32_t t1l, const uint8_t *data, size_t len )
|
||||
{
|
||||
int channel;
|
||||
|
||||
if ((channel = platform_rmt_allocate( num_mem, RMT_MODE_TX )) >= 0) {
|
||||
if ((channel = platform_rmt_allocate( 1, RMT_MODE_TX )) >= 0) {
|
||||
ws2812_chain_t *chain = &(ws2812_chains[channel]);
|
||||
rmt_item32_t tRmtItem;
|
||||
uint32_t half;
|
||||
|
||||
chain->valid = true;
|
||||
chain->gpio = gpio_num;
|
||||
chain->len = len;
|
||||
chain->data = data;
|
||||
chain->needs_reset = true;
|
||||
chain->bitpos = 0;
|
||||
|
||||
// Send a reset if "reset" is not 0.
|
||||
chain->needs_reset = (reset != 0);
|
||||
|
||||
// Construct the RMT item for a reset.
|
||||
tRmtItem.level0 = 0;
|
||||
tRmtItem.level1 = 0;
|
||||
// The reset duration must fit into one RMT item. This leaves 2*15 bit,
|
||||
// which results in a maximum of 0xfffe .
|
||||
if (reset>0xfffe)
|
||||
{
|
||||
reset = 0xfffe;
|
||||
}
|
||||
if (reset>0x7fff)
|
||||
{
|
||||
tRmtItem.duration0 = 0x7fff;
|
||||
tRmtItem.duration1 = reset - 0x7fff;
|
||||
}
|
||||
else
|
||||
{
|
||||
half = reset >> 1U;
|
||||
tRmtItem.duration0 = half;
|
||||
tRmtItem.duration1 = reset - half;
|
||||
}
|
||||
chain->reset = tRmtItem;
|
||||
|
||||
// Limit the bit times to the available 15 bits.
|
||||
// The values must not be 0.
|
||||
if( t0h==0 )
|
||||
{
|
||||
t0h = 1;
|
||||
}
|
||||
else if( t0h>0x7fffU )
|
||||
{
|
||||
t0h = 0x7fffU;
|
||||
}
|
||||
if( t0l==0 )
|
||||
{
|
||||
t0l = 1;
|
||||
}
|
||||
else if( t0l>0x7fffU )
|
||||
{
|
||||
t0l = 0x7fffU;
|
||||
}
|
||||
if( t1h==0 )
|
||||
{
|
||||
t1h = 1;
|
||||
}
|
||||
else if( t1h>0x7fffU )
|
||||
{
|
||||
t1h = 0x7fffU;
|
||||
}
|
||||
if( t1l==0 )
|
||||
{
|
||||
t1l = 1;
|
||||
}
|
||||
else if( t1l>0x7fffU )
|
||||
{
|
||||
t1l = 0x7fffU;
|
||||
}
|
||||
|
||||
// Construct the RMT item for a 0 bit.
|
||||
tRmtItem.level0 = 1;
|
||||
tRmtItem.duration0 = t0h;
|
||||
tRmtItem.level1 = 0;
|
||||
tRmtItem.duration1 = t0l;
|
||||
chain->bits[0] = tRmtItem;
|
||||
|
||||
// Construct the RMT item for a 1 bit.
|
||||
tRmtItem.level0 = 1;
|
||||
tRmtItem.duration0 = t1h;
|
||||
tRmtItem.level1 = 0;
|
||||
tRmtItem.duration1 = t1l;
|
||||
chain->bits[1] = tRmtItem;
|
||||
|
||||
#ifdef WS2812_DEBUG
|
||||
ESP_LOGI("ws2812", "Setup done for gpio %d on RMT channel %d", gpio_num, channel);
|
||||
|
@ -210,6 +278,19 @@ int platform_ws2812_send( void )
|
|||
}
|
||||
}
|
||||
|
||||
// Try to add all channels to a group. This moves the start of all RMT sequences closer
|
||||
// together.
|
||||
#if SOC_RMT_SUPPORT_TX_SYNCHRO
|
||||
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX && res == PLATFORM_OK; channel++) {
|
||||
if (ws2812_chains[channel].valid) {
|
||||
if (rmt_add_channel_to_group( channel ) != ESP_OK) {
|
||||
res = PLATFORM_ERR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// start selected channels one by one
|
||||
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX && res == PLATFORM_OK; channel++) {
|
||||
if (ws2812_chains[channel].valid) {
|
||||
|
@ -229,6 +310,17 @@ int platform_ws2812_send( void )
|
|||
}
|
||||
}
|
||||
|
||||
#if SOC_RMT_SUPPORT_TX_SYNCHRO
|
||||
for (rmt_channel_t channel = 0; channel < RMT_CHANNEL_MAX; channel++) {
|
||||
if (ws2812_chains[channel].valid) {
|
||||
if (rmt_remove_channel_from_group( channel ) != ESP_OK) {
|
||||
res = PLATFORM_ERR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
# NodeMCU Documentation
|
||||
|
||||
NodeMCU is an open source [Lua](https://www.lua.org/) based firmware for the [ESP32](http://espressif.com/en/products/hardware/esp32/overview) and [ESP8266 WiFi SOC from Espressif](http://espressif.com/en/products/esp8266/) and uses an on-module flash-based [SPIFFS](https://github.com/pellepl/spiffs) file system. NodeMCU is implemented in C and is layered on the [Espressif ESP-IDF](https://github.com/espressif/ESP-IDF).
|
||||
NodeMCU is an open source [Lua](https://www.lua.org/) based firmware for the [ESP32](https://www.espressif.com/en/products/socs/esp32) and [ESP8266](https://www.espressif.com/en/products/socs/esp8266) WiFi SOCs from Espressif. It uses an on-module flash-based [SPIFFS](https://github.com/pellepl/spiffs) file system. NodeMCU is implemented in C and the ESP32 version is layered on the [Espressif ESP-IDF](https://github.com/espressif/ESP-IDF).
|
||||
|
||||
The firmware was initially developed as is a companion project to the popular ESP8266-based [NodeMCU development modules](https://github.com/nodemcu/nodemcu-devkit-v1.0), but the project is now community-supported, and the firmware can now be run on _any_ ESP module.
|
||||
|
||||
Support for the new [ESP32 WiFi/BlueTooth SOC from Espressif](http://www.espressif.com/en/products/hardware/esp32/overview) is under way.
|
||||
!!! important
|
||||
|
||||
The NodeMCU [`release`](https://github.com/nodemcu/nodemcu-firmware/tree/release) and [`dev`](https://github.com/nodemcu/nodemcu-firmware/tree/dev) branches target the ESP8266. The [`dev-esp32`](https://github.com/nodemcu/nodemcu-firmware/tree/dev-esp32) branch targets the ESP32.
|
||||
|
||||
## Up-To-Date Documentation
|
||||
At the moment the only up-to-date documentation maintained by the current NodeMCU team is in English. It is part of the source code repository (`/docs` subfolder) and kept in sync with the code.
|
||||
|
|
|
@ -27,7 +27,7 @@ var nodemcu = nodemcu || {};
|
|||
}
|
||||
function isModulePage() {
|
||||
// if the breadcrumb contains 'Modules »' it must be an API page
|
||||
return $("ul.wy-breadcrumbs li:contains('Modules »')").size() > 0;
|
||||
return $("ul.wy-breadcrumbs li:contains('C Modules')").length > 0;
|
||||
}
|
||||
function createTocTableRow(func, intro) {
|
||||
// fragile attempt to auto-create the in-page anchor
|
||||
|
|
|
@ -0,0 +1,248 @@
|
|||
# ESP-NOW Module
|
||||
| Since | Origin / Contributor | Maintainer | Source |
|
||||
| :----- | :-------------------- | :---------- | :------ |
|
||||
| 2024-03-07 | [DiUS](https://github.com/DiUS) [Jade Mattsson](https://github.com/jmattsson) |[Jade Mattsson](https://github.com/jmattsson) | [espnow.c](../../components/modules/espnow.c)|
|
||||
|
||||
The `espnow` module provides an interface to Espressif's [ESP-NOW functionality](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html). To quote their documentation directly:
|
||||
|
||||
"ESP-NOW is a kind of connectionless Wi-Fi communication protocol that is defined by Espressif. In ESP-NOW, application data is encapsulated in a vendor-specific action frame and then transmitted from one Wi-Fi device to another without connection."
|
||||
|
||||
Packets can be sent to either individual peers, the whole list of defined peers, or broadcast to everyone in range. For non-broadcast packets, ESP-NOW provides optional encryption support to prevent eavesdropping. To use encryption, a "Primary Master Key" (PMK) should first be set. When registering a peer, a peer-specific "Local Master Key" (LMK) is then given, which is further encrypted by the PMK. All packets sent to that peer will be encrypted with the resulting key.
|
||||
|
||||
To broadcast packets, a peer with address 'ff:ff:ff:ff:ff:ff' must first be registered. Broadcast packets do not support encryption, and attempting to enable encrypting when registering the broadcast peer will result in an error.
|
||||
|
||||
ESP-NOW uses a WiFi vendor-specific action frame to transmit data, and as such it requires the WiFi stack to have been started before ESP-NOW packets can be sent and received.
|
||||
|
||||
|
||||
## espnow.start
|
||||
|
||||
Starts the ESP-NOW stack. While this may be called prior to `wifi.start()`, packet transmission and reception will not be possible until after the WiFi stack has been started.
|
||||
|
||||
#### Syntax
|
||||
```lua
|
||||
espnow.start()
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
None.
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
||||
An error will be raised if the ESP-NOW stack cannot be started.
|
||||
|
||||
|
||||
## espnow.stop
|
||||
|
||||
Stops the ESP-NOW stack.
|
||||
|
||||
#### Syntax
|
||||
```lua
|
||||
espnow.stop()
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
None.
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
||||
An error will be raised if the ESP-NOW stack cannot be stopped.
|
||||
|
||||
|
||||
## espnow.getversion
|
||||
|
||||
Returns the raw version number enum value. Currently, it is `1`. Might be useful for checking version compatibility in the future.
|
||||
|
||||
#### Syntax
|
||||
```lua
|
||||
ver = espnow.getversion()
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
None.
|
||||
|
||||
#### Returns
|
||||
An integer representing the ESP-NOW version.
|
||||
|
||||
|
||||
## espnow.setpmk
|
||||
|
||||
Sets the Primary Master Key (PMK). When using security, this should be done prior to adding any peers, as their LMKs will be encrypted by the current PMK.
|
||||
|
||||
#### Syntax
|
||||
```lua
|
||||
espnow.setpmk(pmk)
|
||||
```
|
||||
#### Parameters
|
||||
`pmk` The Primary Master Key, given as a hex-encoding of a 16-byte key (i.e. the `pmk` should consist of 32 hex digits.
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
||||
An error will be raised if the PMK cannot be set.
|
||||
|
||||
#### Example
|
||||
```lua
|
||||
espnow.setpmk('00112233445566778899aabbccddeeff')
|
||||
```
|
||||
|
||||
|
||||
## espnow.setwakewindow
|
||||
|
||||
Controls the wake window during which ESP-NOW listens. In most cases this should never need to be changed from the default. Refer to the Espressif documentation for further details.
|
||||
|
||||
#### Syntax
|
||||
```lua
|
||||
espnow.setwakewindow(window)
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
`window` An integer between 0 and 65535.
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
||||
|
||||
## espnow.addpeer
|
||||
|
||||
Registers a peer MAC address. Optionally parameters for the peer may be included, such as encryption key.
|
||||
|
||||
#### Syntax
|
||||
```lua
|
||||
espnow.addpeer(mac, options)
|
||||
```
|
||||
#### Parameters
|
||||
- `mac` The peer mac address, given as a string in `00:11:22:33:44:55` format (colons optional, and may also be replaced by '-' or ' ').
|
||||
- `options` A table with with following entries:
|
||||
- `channel` An integer indicating the WiFi channel to be used. The default is `0`, indicating that the current WiFi channel should be used. If non-zero, must match the current WiFi channel.
|
||||
- `lmk` The LMK for the peer, if encryption is to be used.
|
||||
- `encrypt` A non-zero integer to indicate encryption should be enabled. When set, makes `lmk` a required field.
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
||||
An error will be raised if a peer cannot be added, such as if the peer list if full, or the peer has already been added.
|
||||
|
||||
#### Examples
|
||||
|
||||
Adding a peer without encryption enabled.
|
||||
```lua
|
||||
espnow.addpeer('7c:df:a1:c1:4c:71')
|
||||
```
|
||||
|
||||
Adding a peer with encryption enabled. Please use randomly generated keys instead of these easily guessable placeholders.
|
||||
```lua
|
||||
espnow.setpmk('ffeeddccbbaa99887766554433221100')
|
||||
espnow.addpeer('7c:df:a1:c1:4c:71', { encrypt = 1, lmk = '00112233445566778899aabbccddeeff' })
|
||||
```
|
||||
|
||||
## espnow.delpeer
|
||||
|
||||
Deletes a previously added peer from the internal peer list.
|
||||
|
||||
#### Syntax
|
||||
```lua
|
||||
espnow.delpeer(mac)
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
`mac` The MAC address of the peer to delete.
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
||||
Returns an error if the peer cannot be deleted.
|
||||
|
||||
|
||||
## espnow.on
|
||||
|
||||
Registers or unregisters callback handlers for the ESP-NOW events.
|
||||
|
||||
There are two events available, `sent` which is issued in response to a packet send request and which reports the status of the send attempt, and 'receive' which is issued when an ESP-NOW packet is successfully received.
|
||||
|
||||
Only a single callback function can be registered for each event.
|
||||
|
||||
The callback function for the `sent` event is invoked with two parameters, the destination MAC address, and a `1`/`nil` to indicate whether the send was believed to be successful or not.
|
||||
|
||||
The callback function for the `receive` event is invoked with a single parameter, a table with the following keys:
|
||||
|
||||
- `src` The sender MAC address
|
||||
- `dst` The destination MAC address (likely either the local MAC of the receiver, or the broadcast address)
|
||||
- `rssi` The RSSI value from the packet, indicating signal strength between the two devices
|
||||
- `data` The actual payload data, as a string. The string may contain binary data.
|
||||
|
||||
#### Syntax
|
||||
```lua
|
||||
espnow.on(event, callbackfn)
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
- `event` The event name, one of `sent` or `receive`.
|
||||
- `callbackfn` The callback function to register, or `nil` to unregister the previously set callback function for the event.
|
||||
|
||||
#### Returns
|
||||
`nil`
|
||||
|
||||
Raises an error if invalid arguments are given.
|
||||
|
||||
#### Example
|
||||
Registering callback handlers.
|
||||
```lua
|
||||
espnow.on('sent',
|
||||
function(mac, success) print(mac, success and 'Yay!' or 'Noooo') end)
|
||||
espnow.on('receive',
|
||||
function(t) print(t.src, '->', t.dst, '@', t.rssi, ':', t.data) end)
|
||||
```
|
||||
|
||||
Unregistering callback handlers.
|
||||
```lua
|
||||
espnow.on('sent') -- implicit nil
|
||||
espnow.on('receive', nil)
|
||||
```
|
||||
|
||||
|
||||
## espnow.send
|
||||
|
||||
Attempts to send an ESP-NOW packet to one or more peers.
|
||||
|
||||
In general it is strongly recommended to use the encryption functionality, as this ensures not only secrecy but also prevent unintentional interference between different users of ESP-NOW.
|
||||
|
||||
If you do need to use broadcasts or multicasts, you should make sure to have a unique, recognisable marker at the start of the payload to make filtering out unwanted messages easy, both for you and other ESP-NOW users.
|
||||
|
||||
#### Syntax
|
||||
```lua
|
||||
espnow.send(peer, data)
|
||||
```
|
||||
|
||||
#### Parameters
|
||||
- `peer` The peer MAC address to send to. Must have previously been added via `espnow.addpeer()`. If `peer` is given as `nil`, the packet is sent to all registered non-broadcast/multicast peers, and the `sent` callback is invoked for each of those peers.
|
||||
- `data` A string of data to send. May contain binary bytes. Maximum supported length at the time of writing is 250 bytes.
|
||||
|
||||
#### Returns
|
||||
`nil`, but the `sent` callback is invoked with the status afterwards.
|
||||
|
||||
Raises an error if the peer is not valid, or other fatal errors preventing a send attempt from even being made. The `sent` callback will not be invoked in this case.
|
||||
|
||||
#### Example
|
||||
Broadcasting a message to every single ESP-NOW device in range.
|
||||
```lua
|
||||
bcast='ff:ff:ff:ff:ff:ff'
|
||||
espnow.addpeer(bcast)
|
||||
espnow.send(bcast, '[NodeMCU] Hello, world!')
|
||||
```
|
||||
|
||||
Sending a directed message to one specific ESP-NOW device.
|
||||
```lua
|
||||
peer='7c:df:a1:c1:4c:71'
|
||||
espnow.addpeer(peer)
|
||||
espnow.send(peer, 'Hello, you!')
|
||||
```
|
||||
|
||||
Sending a message to all registered peers.
|
||||
```lua
|
||||
espnow.addpeer('7c:df:a1:c1:4c:71')
|
||||
espnow.addpeer('7c:df:a1:c1:4c:47')
|
||||
espnow.addpeer('7c:df:a1:c1:4f:12')
|
||||
espnow.send(nil, 'Hello, peers!')
|
||||
```
|
|
@ -1,7 +1,7 @@
|
|||
# RTC User Memory Module
|
||||
| Since | Origin / Contributor | Maintainer | Source |
|
||||
| :----- | :-------------------- | :---------- | :------ |
|
||||
| 2015-06-25 | [DiUS](https://github.com/DiUS), [Johny Mattsson](https://github.com/jmattsson) | [PJSG](https://github.com/pjsg) | [rtcmem.c](../../app/modules/rtcmem.c)|
|
||||
| 2015-06-25 | [DiUS](https://github.com/DiUS), [Jade Mattsson](https://github.com/jmattsson) | [PJSG](https://github.com/pjsg) | [rtcmem.c](../../components/modules/rtcmem.c)|
|
||||
|
||||
The rtcmem module provides basic access to the RTC memory.
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# WS2812 Module
|
||||
| Since | Origin / Contributor | Maintainer | Source |
|
||||
| :----- | :-------------------- | :---------- | :------ |
|
||||
| 2015-02-05 | [Till Klocke](https://github.com/dereulenspiegel), [Thomas Soëte](https://github.com/Alkorin) | [Arnim Läuger](https://github.com/devsaurus) | [ws2812.c](../../components/modules/ws2812.c)|
|
||||
| 2015-02-05 | [Till Klocke](https://github.com/dereulenspiegel), [Thomas Soëte](https://github.com/Alkorin), [Christoph Thelen](https://github.com/docbacardi) | [Arnim Läuger](https://github.com/devsaurus) | [ws2812.c](../../components/modules/ws2812.c)|
|
||||
|
||||
ws2812 is a library to handle ws2812-like led strips.
|
||||
It works at least on WS2812, WS2812b, APA104, SK6812 (RGB or RGBW).
|
||||
|
@ -22,6 +22,14 @@ Variable number of tables, each describing a single strip. Required elements are
|
|||
- `pin` IO index, see [GPIO Overview](gpio.md#gpio-overview)
|
||||
- `data` payload to be sent to one or more WS2812 like leds through GPIO2
|
||||
|
||||
Optional elements are:
|
||||
|
||||
- `reset` duration of the reset signal in multiples of 100ns. A duration of 0 generates no reset. The minimum possible value is 0. The maximum is 65534. The default value is 512 which generates a reset of 51.2us.
|
||||
- `t0h` duration of the high period for a 0 code in multiples of 100ns. The minimum possible value is 1. The maximum is 32767. The default value is 4 which results in a high period of 400ns for each 0 code.
|
||||
- `t0l` duration of the low period for a 0 code in multiples of 100ns. The minimum possible value is 1. The maximum is 32767. The default value is 7 which results in a low period of 700ns for each 0 code.
|
||||
- `t1h` duration of the high period for a 1 code in multiples of 100ns. The minimum possible value is 1. The maximum is 32767. The default value is 8 which results in a high period of 800ns for each 1 code.
|
||||
- `t1l` duration of the low period for a 1 code in multiples of 100ns. The minimum possible value is 1. The maximum is 32767. The default value is 6 which results in a low period of 600ns for each 1 code.
|
||||
|
||||
Payload type could be:
|
||||
|
||||
- `string` representing bytes to send
|
||||
|
@ -44,6 +52,10 @@ ws2812.write({pin = 4, data = string.char(255, 0, 0, 255, 0, 0)},
|
|||
{pin = 14, data = string.char(0, 255, 0, 0, 255, 0)}) -- turn the two first RGB leds to green on the first strip and red on the second strip
|
||||
```
|
||||
|
||||
```lua
|
||||
ws2812.write({pin = 8, reset = 800, t0h = 3, t0l = 9, t1h = 6, t1l = 6, data = string.char(1, 0, 0)}) -- turn the SK6812 GRB led on the ESP32-C3-DevKitM-1 to green
|
||||
```
|
||||
|
||||
# Buffer module
|
||||
For more advanced animations, it is useful to keep a "framebuffer" of the strip,
|
||||
interact with it and flush it to the strip.
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
mkdocs==1.2.2
|
||||
jinja2<3.1
|
||||
mkdocs>=1.5.3
|
||||
jinja2>=3.1.0
|
||||
|
|
|
@ -20,7 +20,7 @@ extra_css:
|
|||
extra_javascript:
|
||||
- js/extra.js
|
||||
|
||||
pages:
|
||||
nav:
|
||||
- Overview: 'index.md'
|
||||
- Basics:
|
||||
- Building the firmware: 'build.md'
|
||||
|
@ -48,6 +48,7 @@ pages:
|
|||
- 'dht': 'modules/dht.md'
|
||||
- 'encoder': 'modules/encoder.md'
|
||||
- 'eromfs': 'modules/eromfs.md'
|
||||
- 'espnow': 'modules/espnow.md'
|
||||
- 'eth': 'modules/eth.md'
|
||||
- 'file': 'modules/file.md'
|
||||
- 'gpio': 'modules/gpio.md'
|
||||
|
@ -66,6 +67,7 @@ pages:
|
|||
- 'pulsecnt': 'modules/pulsecnt.md'
|
||||
- 'qrcodegen': 'modules/qrcodegen.md'
|
||||
- 'rmt': 'modules/rmt.md'
|
||||
- 'rtcmem': 'modules/rtcmem.md'
|
||||
- 'sdmmc': 'modules/sdmmc.md'
|
||||
- 'sigma delta': 'modules/sigma-delta.md'
|
||||
- 'sjson': 'modules/sjson.md'
|
||||
|
|
Loading…
Reference in New Issue