nodemcu-firmware/components/modules/serial_common.c

198 lines
4.7 KiB
C

// Common routines for handling serial input data
#include "serial_common.h"
#include "lauxlib.h"
#include <string.h>
// This is the historical max value
#define MAX_SERIAL_INPUT 255
struct serial_input_cfg {
int receive_ref;
int error_ref;
char *line_buffer;
size_t line_buffer_size;
size_t line_position;
uint16_t need_len;
int16_t end_char;
};
static const char nostack[] = "out of stack";
static bool serial_input_invoke(int ref, const char *buf, size_t len)
{
if(ref == LUA_NOREF || !buf || len == 0)
return false;
lua_State *L = lua_getstate();
int top = lua_gettop(L);
luaL_checkstack(L, 2, nostack);
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
lua_pushlstring(L, buf, len);
luaL_pcallx(L, 1, 0);
lua_settop(L, top);
return true;
}
serial_input_cfg_t *serial_input_new(void)
{
serial_input_cfg_t *cfg = calloc(1, sizeof(serial_input_cfg_t));
if (!cfg)
return NULL;
cfg->receive_ref = cfg->error_ref = LUA_NOREF;
cfg->end_char = -1;
return cfg;
}
void serial_input_free(lua_State *L, serial_input_cfg_t *cfg)
{
if (cfg->receive_ref != LUA_NOREF)
luaL_unref(L, LUA_REGISTRYINDEX, cfg->receive_ref);
if (cfg->error_ref != LUA_NOREF)
luaL_unref(L, LUA_REGISTRYINDEX, cfg->error_ref);
free(cfg->line_buffer);
free(cfg);
}
bool serial_input_dispatch_data(serial_input_cfg_t *cfg, const char *buf, size_t len)
{
if (!cfg)
return false;
else
return serial_input_invoke(cfg->receive_ref, buf, len);
}
bool serial_input_report_error(serial_input_cfg_t *cfg, const char *buf, size_t len)
{
if (!cfg)
return false;
else
return serial_input_invoke(cfg->error_ref, buf, len);
}
void serial_input_feed_data(serial_input_cfg_t *cfg, const char *buf, size_t len)
{
if (!cfg || !cfg->line_buffer || !buf || !len)
return;
const uint16_t need_len = cfg->need_len;
const int16_t end_char = cfg->end_char;
const size_t max_wanted =
(end_char >= 0 && need_len == 0) ? cfg->line_buffer_size : need_len;
for (unsigned i = 0; i < len; ++i)
{
char ch = buf[i];
cfg->line_buffer[cfg->line_position] = ch;
cfg->line_position++;
bool at_end = (cfg->line_position >= max_wanted);
bool end_char_found =
(end_char >= 0 && (uint8_t)ch == (uint8_t)end_char);
if (at_end || end_char_found) {
// Reset line position early so callback can resize line_buffer if desired
int n = cfg->line_position;
cfg->line_position = 0;
serial_input_dispatch_data(cfg, cfg->line_buffer, n);
}
}
}
bool serial_input_has_data_cb(serial_input_cfg_t *cfg)
{
return cfg && cfg->receive_ref != LUA_NOREF;
}
// on("method", [number/char], function)
int serial_input_register(lua_State *L, serial_input_cfg_t *cfg)
{
const char *method = luaL_checkstring(L, 1);
const bool is_data = (strcmp(method, "data") == 0);
const bool is_error = (strcmp(method, "error") == 0);
if (!is_data && !is_error)
return luaL_error(L, "method not supported");
int fn_idx = -1;
if (lua_isnumber(L, 2))
{
cfg->need_len = luaL_checkinteger(L, 2);
cfg->end_char = -1;
}
else if (lua_isstring(L, 2))
{
size_t len;
const char *end = luaL_checklstring(L, 2, &len);
if (len != 1)
return luaL_error(L, "only single byte end marker supported");
cfg->need_len = 0;
cfg->end_char = end[0];
}
else if (lua_isfunction(L, 2))
{
fn_idx = 2;
}
if (fn_idx == -1 && lua_isfunction(L, 3))
{
fn_idx = 3;
}
if (is_data)
{
if (cfg->receive_ref != LUA_NOREF)
luaL_unref2(L, LUA_REGISTRYINDEX, cfg->receive_ref); // unref & clear
if (fn_idx != -1) // Register and (re)alloc resources
{
luaL_checkstack(L, 1, nostack);
lua_pushvalue(L, fn_idx);
cfg->receive_ref = luaL_ref(L, LUA_REGISTRYINDEX);
size_t min_size = (cfg->need_len > 0) ? cfg->need_len : MAX_SERIAL_INPUT;
// Prevent dropping input; this should be an exceedingly rare condition
if (cfg->line_position >= min_size)
min_size = cfg->line_position + 1;
if (cfg->line_buffer_size < min_size)
{
cfg->line_buffer = realloc(cfg->line_buffer, min_size);
cfg->line_buffer_size = (cfg->line_buffer) ? min_size : 0;
if (!cfg->line_buffer)
return luaL_error(L, "out of mem");
}
}
else // Free resources
{
free(cfg->line_buffer);
cfg->line_buffer = NULL;
cfg->line_buffer_size = 0;
cfg->line_position = 0;
}
}
else if (is_error)
{
if (cfg->error_ref != LUA_NOREF)
luaL_unref2(L, LUA_REGISTRYINDEX, cfg->error_ref); // unref & clear
if (fn_idx != -1)
{
luaL_checkstack(L, 1, nostack);
lua_pushvalue(L, fn_idx);
cfg->error_ref = luaL_ref(L, LUA_REGISTRYINDEX);
}
}
return 0;
}