Commit fe602d2d authored by Johny Mattsson's avatar Johny Mattsson
Browse files

Removed all currently-unused code & docs.

Heading towards having only ESP32-aware/capable code in this branch.
parent ddeb26c4
#############################################################
# Required variables for each makefile
# Discard this section from all parent makefiles
# Expected variables (with automatic defaults):
# CSRCS (all "C" files in the dir)
# SUBDIRS (all subdirs with a Makefile)
# GEN_LIBS - list of libs to be generated ()
# GEN_IMAGES - list of images to be generated ()
# COMPONENTS_xxx - a list of libs/objs in the form
# subdir/lib to be extracted and rolled up into
# a generated lib/image xxx.a ()
#
ifndef PDIR
GEN_LIBS = libmodules.a
endif
STD_CFLAGS=-std=gnu11 -Wimplicit
#############################################################
# Configuration i.e. compile options etc.
# Target specific stuff (defines etc.) goes in here!
# Generally values applying to a tree are captured in the
# makefile at its root level - these are then overridden
# for a subtree within the makefile rooted therein
#
#DEFINES +=
#############################################################
# Recursion Magic - Don't touch this!!
#
# Each subtree potentially has an include directory
# corresponding to the common APIs applicable to modules
# rooted at that subtree. Accordingly, the INCLUDE PATH
# of a module can only contain the include directories up
# its parent path, and not its siblings
#
# Required for each makefile to inherit from the parent
#
INCLUDES := $(INCLUDES) -I $(PDIR)include
INCLUDES += -I ./
INCLUDES += -I ../libc
INCLUDES += -I ../coap
INCLUDES += -I ../mqtt
INCLUDES += -I ../u8glib
INCLUDES += -I ../ucglib
INCLUDES += -I ../lua
INCLUDES += -I ../platform
INCLUDES += -I ../spiffs
INCLUDES += -I ../smart
INCLUDES += -I ../cjson
INCLUDES += -I ../dhtlib
INCLUDES += -I ../http
PDIR := ../$(PDIR)
sinclude $(PDIR)Makefile
// Module for interfacing with adc
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include "c_types.h"
#include "user_interface.h"
#include <stdlib.h>
// Lua: read(id) , return system adc
static int adc_sample( lua_State* L )
{
unsigned id = luaL_checkinteger( L, 1 );
MOD_CHECK_ID( adc, id );
#if defined(__ESP8266__)
unsigned val = 0xFFFF & system_adc_read();
#elif defined(__ESP32__)
// TODO: support reading the 8 ADC channels via system_adc1_read()
unsigned val = 0;
#endif
lua_pushinteger( L, val );
return 1;
}
// Lua: readvdd33()
static int adc_readvdd33( lua_State* L )
{
lua_pushinteger(L, system_get_vdd33 ());
return 1;
}
// Lua: adc.force_init_mode(x)
static int adc_init107( lua_State *L )
{
uint8_t byte107 = luaL_checkinteger (L, 1);
int erridx = -1;
const char *errmsg[] = {
"out of memory",
"flash read error",
"flash erase error",
"flash write error"
};
#define return_error(idx) do { erridx = idx; goto out; } while (0)
uint32 init_sector = flash_safe_get_sec_num () - 4;
char *init_data = malloc (SPI_FLASH_SEC_SIZE);
if (!init_data)
return_error(0);
if (SPI_FLASH_RESULT_OK != flash_safe_read (
init_sector * SPI_FLASH_SEC_SIZE,
(uint32 *)init_data, sizeof(init_data)))
return_error(1);
// If it's already the correct value, we don't need to force it
if (init_data[107] == byte107)
{
free (init_data);
lua_pushboolean (L, false);
return 1;
}
// Nope, it differs, we need to rewrite it
init_data[107] = byte107;
if (SPI_FLASH_RESULT_OK != flash_safe_erase_sector (init_sector))
return_error(2);
if (SPI_FLASH_RESULT_OK != flash_safe_write (
init_sector * SPI_FLASH_SEC_SIZE,
(uint32 *)init_data, sizeof(init_data)))
return_error(3);
out:
free (init_data);
if (erridx >= 0)
return luaL_error(L, errmsg[erridx]);
else
{
lua_pushboolean (L, true);
return 1;
}
}
// Module function map
static const LUA_REG_TYPE adc_map[] = {
{ LSTRKEY( "read" ), LFUNCVAL( adc_sample ) },
{ LSTRKEY( "readvdd33" ), LFUNCVAL( adc_readvdd33 ) },
{ LSTRKEY( "force_init_mode" ), LFUNCVAL( adc_init107 ) },
{ LSTRKEY( "INIT_ADC" ), LNUMVAL( 0x00 ) },
{ LSTRKEY( "INIT_VDD33" ),LNUMVAL( 0xff ) },
{ LNILKEY, LNILVAL }
};
NODEMCU_MODULE(ADC, "adc", adc_map, NULL);
/*
* app/modules/am2320.c
*
* Copyright (c) 2016 Henk Vergonet <Henk.Vergonet@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include "lwip/udp.h"
#include "esp_misc.h"
#include <errno.h>
static const uint32_t am2320_i2c_id = 0;
static const uint8_t am2320_i2c_addr = 0xb8 >> 1;
static uint16_t crc16(uint8_t *ptr, unsigned int len)
{
uint16_t crc =0xFFFF;
uint8_t i;
while(len--) {
crc ^= *ptr++;
for(i=0;i<8;i++) {
if(crc & 0x01) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
/* make sure buf has lenght len+2 in order to accommodate for extra bytes */
static int _read(uint32_t id, void *buf, uint8_t len, uint8_t off)
{
int i;
uint8_t *b = (uint8_t *)buf;
uint16_t crc;
// step 1: Wake sensor
platform_i2c_send_start(id);
platform_i2c_send_address(id, am2320_i2c_addr, PLATFORM_I2C_DIRECTION_TRANSMITTER);
os_delay_us(800);
platform_i2c_send_stop(id);
// step 2: Send read command
platform_i2c_send_start(id);
platform_i2c_send_address(id, am2320_i2c_addr, PLATFORM_I2C_DIRECTION_TRANSMITTER);
platform_i2c_send_byte(id, 0x03);
platform_i2c_send_byte(id, off);
platform_i2c_send_byte(id, len);
platform_i2c_send_stop(id);
// step 3: Read the data
os_delay_us(1500);
platform_i2c_send_start(id);
platform_i2c_send_address(id, am2320_i2c_addr, PLATFORM_I2C_DIRECTION_RECEIVER);
os_delay_us(30);
for(i=0; i<len+2; i++)
b[i] = platform_i2c_recv_byte(id,1);
crc = platform_i2c_recv_byte(id,1);
crc |= platform_i2c_recv_byte(id,0) << 8;
platform_i2c_send_stop(id);
if(b[0] != 0x3 || b[1] != len)
return -EIO;
if(crc != crc16(b,len+2))
return -EIO;
return 0;
}
static int am2320_init(lua_State* L)
{
uint32_t sda;
uint32_t scl;
int ret;
struct {
uint8_t cmd;
uint8_t len;
uint16_t model;
uint8_t version;
uint32_t id;
} nfo;
if (!lua_isnumber(L, 1) || !lua_isnumber(L, 2)) {
return luaL_error(L, "wrong arg range");
}
sda = luaL_checkinteger(L, 1);
scl = luaL_checkinteger(L, 2);
if (scl == 0 || sda == 0) {
return luaL_error(L, "no i2c for D0");
}
platform_i2c_setup(am2320_i2c_id, sda, scl, PLATFORM_I2C_SPEED_SLOW);
os_delay_us(1500); // give some time to settle things down
ret = _read(am2320_i2c_id, &nfo, sizeof(nfo)-2, 0x08);
if(ret)
return luaL_error(L, "transmission error");
lua_pushinteger(L, ntohs(nfo.model));
lua_pushinteger(L, nfo.version);
lua_pushinteger(L, ntohl(nfo.id));
return 3;
}
static int am2320_read(lua_State* L)
{
int ret;
struct {
uint8_t cmd;
uint8_t len;
uint16_t rh;
uint16_t temp;
} nfo;
ret = _read(am2320_i2c_id, &nfo, sizeof(nfo)-2, 0x00);
if(ret)
return luaL_error(L, "transmission error");
ret = ntohs(nfo.temp);
if(ret & 0x8000)
ret = -(ret & 0x7fff);
lua_pushinteger(L, ntohs(nfo.rh));
lua_pushinteger(L, ret);
return 2;
}
static const LUA_REG_TYPE am2320_map[] = {
{ LSTRKEY( "read" ), LFUNCVAL( am2320_read )},
{ LSTRKEY( "init" ), LFUNCVAL( am2320_init )},
{ LNILKEY, LNILVAL}
};
NODEMCU_MODULE(AM2320, "am2320", am2320_map, NULL);
#include <stdlib.h>
#include <string.h>
#include "lualib.h"
#include "lauxlib.h"
#include "lrotable.h"
#include "module.h"
#include "platform.h"
#include "user_interface.h"
#define NOP asm volatile(" nop \n\t")
static inline void apa102_send_byte(uint32_t data_pin, uint32_t clock_pin, uint8_t byte) {
int i;
for (i = 0; i < 8; i++) {
if (byte & 0x80) {
GPIO_OUTPUT_SET(data_pin, PLATFORM_GPIO_HIGH); // Set pin high
} else {
GPIO_OUTPUT_SET(data_pin, PLATFORM_GPIO_LOW); // Set pin low
}
GPIO_OUTPUT_SET(clock_pin, PLATFORM_GPIO_HIGH); // Set pin high
byte <<= 1;
NOP;
NOP;
GPIO_OUTPUT_SET(clock_pin, PLATFORM_GPIO_LOW); // Set pin low
NOP;
NOP;
}
}
static void apa102_send_buffer(uint32_t data_pin, uint32_t clock_pin, uint32_t *buf, uint32_t nbr_frames) {
int i;
// Send 32-bit Start Frame that's all 0x00
apa102_send_byte(data_pin, clock_pin, 0x00);
apa102_send_byte(data_pin, clock_pin, 0x00);
apa102_send_byte(data_pin, clock_pin, 0x00);
apa102_send_byte(data_pin, clock_pin, 0x00);
// Send 32-bit LED Frames
for (i = 0; i < nbr_frames; i++) {
uint8_t *byte = (uint8_t *) buf++;
// Set the first 3 bits of that byte to 1.
// This makes the lua interface easier to use since you
// don't have to worry about creating invalid LED Frames.
byte[0] |= 0xE0;
apa102_send_byte(data_pin, clock_pin, byte[0]);
apa102_send_byte(data_pin, clock_pin, byte[1]);
apa102_send_byte(data_pin, clock_pin, byte[2]);
apa102_send_byte(data_pin, clock_pin, byte[3]);
}
// Send 32-bit End Frames
uint32_t required_postamble_frames = (nbr_frames + 1) / 2;
for (i = 0; i < required_postamble_frames; i++) {
apa102_send_byte(data_pin, clock_pin, 0xFF);
apa102_send_byte(data_pin, clock_pin, 0xFF);
apa102_send_byte(data_pin, clock_pin, 0xFF);
apa102_send_byte(data_pin, clock_pin, 0xFF);
}
}
// Lua: apa102.write(data_pin, clock_pin, "string")
// Byte quads in the string are interpreted as (brightness, B, G, R) values.
// Only the first 5 bits of the brightness value is actually used (0-31).
// This function does not corrupt your buffer.
//
// apa102.write(1, 3, string.char(31, 0, 255, 0)) uses GPIO5 for DATA and GPIO0 for CLOCK and sets the first LED green, with the brightness 31 (out of 0-32)
// apa102.write(5, 6, string.char(255, 0, 0, 255):rep(10)) uses GPIO14 for DATA and GPIO12 for CLOCK and sets ten LED to red, with the brightness 31 (out of 0-32).
// Brightness values are clamped to 0-31.
static int apa102_write(lua_State* L) {
uint8_t data_pin = luaL_checkinteger(L, 1);
MOD_CHECK_ID(gpio, data_pin);
uint32_t alt_data_pin = pin_num[data_pin];
uint8_t clock_pin = luaL_checkinteger(L, 2);
MOD_CHECK_ID(gpio, clock_pin);
uint32_t alt_clock_pin = pin_num[clock_pin];
size_t buf_len;
const char *buf = luaL_checklstring(L, 3, &buf_len);
uint32_t nbr_frames = buf_len / 4;
if (nbr_frames > 100000) {
return luaL_error(L, "The supplied buffer is too long, and might cause the callback watchdog to bark.");
}
// Initialize the output pins
platform_gpio_mode(data_pin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT);
GPIO_OUTPUT_SET(alt_data_pin, PLATFORM_GPIO_HIGH); // Set pin high
platform_gpio_mode(clock_pin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT);
GPIO_OUTPUT_SET(alt_clock_pin, PLATFORM_GPIO_LOW); // Set pin low
// Send the buffers
apa102_send_buffer(alt_data_pin, alt_clock_pin, (uint32_t *) buf, (uint32_t) nbr_frames);
return 0;
}
const LUA_REG_TYPE apa102_map[] =
{
{ LSTRKEY( "write" ), LFUNCVAL( apa102_write )},
{ LNILKEY, LNILVAL}
};
LUALIB_API int luaopen_apa102(lua_State *L) {
LREGISTER(L, "apa102", apa102_map);
return 0;
}
NODEMCU_MODULE(APA102, "apa102", apa102_map, luaopen_apa102);
/* Bitwise operations library */
/* (c) Reuben Thomas 2000-2008 */
/* See README for license */
// Modified by BogdanM for eLua
#include "module.h"
#include <limits.h>
#include "lauxlib.h"
/* FIXME: Assume size_t is an unsigned lua_Integer */
typedef size_t lua_UInteger;
#define LUA_UINTEGER_MAX SIZE_MAX
/* Define TOBIT to get a bit value */
#define TOBIT(L, n) \
(luaL_checkinteger((L), (n)))
/* Operations
The macros MONADIC and VARIADIC only deal with bitwise operations.
LOGICAL_SHIFT truncates its left-hand operand before shifting so
that any extra bits at the most-significant end are not shifted
into the result.
ARITHMETIC_SHIFT does not truncate its left-hand operand, so that
the sign bits are not removed and right shift work properly.
*/
#define MONADIC(name, op) \
static int bit_ ## name(lua_State *L) { \
lua_pushinteger(L, op TOBIT(L, 1)); \
return 1; \
}
#define VARIADIC(name, op) \
static int bit_ ## name(lua_State *L) { \
int n = lua_gettop(L), i; \
lua_Integer w = TOBIT(L, 1); \
for (i = 2; i <= n; i++) \
w op TOBIT(L, i); \
lua_pushinteger(L, w); \
return 1; \
}
#define LOGICAL_SHIFT(name, op) \
static int bit_ ## name(lua_State *L) { \
lua_pushinteger(L, (lua_UInteger)TOBIT(L, 1) op \
(unsigned)luaL_checknumber(L, 2)); \
return 1; \
}
#define ARITHMETIC_SHIFT(name, op) \
static int bit_ ## name(lua_State *L) { \
lua_pushinteger(L, (lua_Integer)TOBIT(L, 1) op \
(unsigned)luaL_checknumber(L, 2)); \
return 1; \
}
MONADIC(bnot, ~)
VARIADIC(band, &=)
VARIADIC(bor, |=)
VARIADIC(bxor, ^=)
ARITHMETIC_SHIFT(lshift, <<)
LOGICAL_SHIFT(rshift, >>)
ARITHMETIC_SHIFT(arshift, >>)
// Lua: res = bit( position )
static int bit_bit( lua_State* L )
{
lua_pushinteger( L, ( lua_Integer )( 1 << luaL_checkinteger( L, 1 ) ) );
return 1;
}
// Lua: res = isset( value, position )
static int bit_isset( lua_State* L )
{
lua_UInteger val = ( lua_UInteger )luaL_checkinteger( L, 1 );
unsigned pos = ( unsigned )luaL_checkinteger( L, 2 );
lua_pushboolean( L, val & ( 1 << pos ) ? 1 : 0 );
return 1;
}
// Lua: res = isclear( value, position )
static int bit_isclear( lua_State* L )
{
lua_UInteger val = ( lua_UInteger )luaL_checkinteger( L, 1 );
unsigned pos = ( unsigned )luaL_checkinteger( L, 2 );
lua_pushboolean( L, val & ( 1 << pos ) ? 0 : 1 );
return 1;
}
// Lua: res = set( value, pos1, pos2, ... )
static int bit_set( lua_State* L )
{
lua_UInteger val = ( lua_UInteger )luaL_checkinteger( L, 1 );
unsigned total = lua_gettop( L ), i;
for( i = 2; i <= total; i ++ )
val |= 1 << ( unsigned )luaL_checkinteger( L, i );
lua_pushinteger( L, ( lua_Integer )val );
return 1;
}
// Lua: res = clear( value, pos1, pos2, ... )
static int bit_clear( lua_State* L )
{
lua_UInteger val = ( lua_UInteger )luaL_checkinteger( L, 1 );
unsigned total = lua_gettop( L ), i;
for( i = 2; i <= total; i ++ )
val &= ~( 1 << ( unsigned )luaL_checkinteger( L, i ) );
lua_pushinteger( L, ( lua_Integer )val );
return 1;
}
static const LUA_REG_TYPE bit_map[] = {
{ LSTRKEY( "bnot" ), LFUNCVAL( bit_bnot ) },
{ LSTRKEY( "band" ), LFUNCVAL( bit_band ) },
{ LSTRKEY( "bor" ), LFUNCVAL( bit_bor ) },
{ LSTRKEY( "bxor" ), LFUNCVAL( bit_bxor ) },
{ LSTRKEY( "lshift" ), LFUNCVAL( bit_lshift ) },
{ LSTRKEY( "rshift" ), LFUNCVAL( bit_rshift ) },
{ LSTRKEY( "arshift" ), LFUNCVAL( bit_arshift ) },
{ LSTRKEY( "bit" ), LFUNCVAL( bit_bit ) },
{ LSTRKEY( "set" ), LFUNCVAL( bit_set ) },
{ LSTRKEY( "clear" ), LFUNCVAL( bit_clear ) },
{ LSTRKEY( "isset" ), LFUNCVAL( bit_isset ) },
{ LSTRKEY( "isclear" ), LFUNCVAL( bit_isclear ) },
{ LNILKEY, LNILVAL}
};
NODEMCU_MODULE(BIT, "bit", bit_map, NULL);
// ***************************************************************************
// BMP280 module for ESP8266 with nodeMCU
//
// Written by Lukas Voborsky, @voborsky
//
// MIT license, http://opensource.org/licenses/MIT
// ***************************************************************************
//#define NODE_DEBUG
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include <math.h>
#include "user_interface.h"
/****************************************************/
/**\name registers definition */
/***************************************************/
#define BME280_REGISTER_CONTROL (0xF4)
#define BME280_REGISTER_CONTROL_HUM (0xF2)
#define BME280_REGISTER_CONFIG (0xF5)
#define BME280_REGISTER_CHIPID (0xD0)
#define BME280_REGISTER_VERSION (0xD1)
#define BME280_REGISTER_SOFTRESET (0xE0)
#define BME280_REGISTER_CAL26 (0xE1)
#define BME280_REGISTER_TEMP (0xFA)
#define BME280_REGISTER_PRESS (0xF7)
#define BME280_REGISTER_HUM (0xFD)
#define BME280_REGISTER_DIG_T (0x88)
#define BME280_REGISTER_DIG_P (0x8E)
#define BME280_REGISTER_DIG_H1 (0xA1)
#define BME280_REGISTER_DIG_H2 (0xE1)
/****************************************************/
/**\name I2C ADDRESS DEFINITIONS */
/***************************************************/
#define BME280_I2C_ADDRESS1 (0x76)
#define BME280_I2C_ADDRESS2 (0x77)
/****************************************************/
/**\name POWER MODE DEFINITIONS */
/***************************************************/
/* Sensor Specific constants */
#define BME280_SLEEP_MODE (0x00)
#define BME280_FORCED_MODE (0x01)
#define BME280_NORMAL_MODE (0x03)
#define BME280_SOFT_RESET_CODE (0xB6)
/****************************************************/
/**\name OVER SAMPLING DEFINITIONS */
/***************************************************/
#define BME280_OVERSAMP_1X (0x01)
#define BME280_OVERSAMP_2X (0x02)
#define BME280_OVERSAMP_4X (0x03)
#define BME280_OVERSAMP_8X (0x04)
#define BME280_OVERSAMP_16X (0x05)
/****************************************************/
/**\name STANDBY DEFINITIONS */
/***************************************************/
#define BME280_STANDBY_TIME_1_MS (0x00)
#define BME280_STANDBY_TIME_63_MS (0x01)
#define BME280_STANDBY_TIME_125_MS (0x02)
#define BME280_STANDBY_TIME_250_MS (0x03)
#define BME280_STANDBY_TIME_500_MS (0x04)
#define BME280_STANDBY_TIME_1000_MS (0x05)
#define BME280_STANDBY_TIME_10_MS (0x06)
#define BME280_STANDBY_TIME_20_MS (0x07)
/****************************************************/
/**\name FILTER DEFINITIONS */
/***************************************************/
#define BME280_FILTER_COEFF_OFF (0x00)
#define BME280_FILTER_COEFF_2 (0x01)
#define BME280_FILTER_COEFF_4 (0x02)
#define BME280_FILTER_COEFF_8 (0x03)
#define BME280_FILTER_COEFF_16 (0x04)
/****************************************************/
/**\data type definition */
/***************************************************/
#define BME280_S32_t int32_t
#define BME280_U32_t uint32_t
#define BME280_S64_t int64_t
#define BME280_SAMPLING_DELAY 113 //maximum measurement time in ms for maximum oversampling for all measures = 1.25 + 2.3*16 + 2.3*16 + 0.575 + 2.3*16 + 0.575 ms
#define r16s(reg) ((int16_t)r16u(reg))
#define r16sLE(reg) ((int16_t)r16uLE(reg))
#define bme280_adc_T(void) r24u(BME280_REGISTER_TEMP)
#define bme280_adc_P(void) r24u(BME280_REGISTER_PRESS)
#define bme280_adc_H(void) r16u(BME280_REGISTER_HUM)
static const uint32_t bme280_i2c_id = 0;
static uint8_t bme280_i2c_addr = BME280_I2C_ADDRESS1;
static uint8_t bme280_isbme = 0; // 1 if the chip is BME280, 0 for BMP280
static uint8_t bme280_mode = 0; // stores oversampling settings
static uint8_t bme280_ossh = 0; // stores humidity oversampling settings
os_timer_t bme280_timer; // timer for forced mode readout
int lua_connected_readout_ref; // callback when readout is ready
static struct {
uint16_t dig_T1;
int16_t dig_T2;
int16_t dig_T3;
uint16_t dig_P1;
int16_t dig_P2;
int16_t dig_P3;
int16_t dig_P4;
int16_t dig_P5;
int16_t dig_P6;
int16_t dig_P7;
int16_t dig_P8;
int16_t dig_P9;
uint8_t dig_H1;
int16_t dig_H2;
uint8_t dig_H3;
int16_t dig_H4;
int16_t dig_H5;
int8_t dig_H6;
} bme280_data;
static BME280_S32_t bme280_t_fine;
static uint32_t bme280_h = 0;
static double bme280_hc = 0.0;
static uint8_t r8u(uint8_t reg) {
uint8_t ret;
platform_i2c_send_start(bme280_i2c_id);
platform_i2c_send_address(bme280_i2c_id, bme280_i2c_addr, PLATFORM_I2C_DIRECTION_TRANSMITTER);
platform_i2c_send_byte(bme280_i2c_id, reg);
platform_i2c_send_stop(bme280_i2c_id);
platform_i2c_send_start(bme280_i2c_id);
platform_i2c_send_address(bme280_i2c_id, bme280_i2c_addr, PLATFORM_I2C_DIRECTION_RECEIVER);
ret = platform_i2c_recv_byte(bme280_i2c_id, 0);
platform_i2c_send_stop(bme280_i2c_id);
//NODE_DBG("reg:%x, value:%x \n", reg, ret);
return ret;
}
static uint8_t w8u(uint8_t reg, uint8_t val) {
platform_i2c_send_start(bme280_i2c_id);
platform_i2c_send_address(bme280_i2c_id, bme280_i2c_addr, PLATFORM_I2C_DIRECTION_TRANSMITTER);
platform_i2c_send_byte(bme280_i2c_id, reg);
platform_i2c_send_byte(bme280_i2c_id, val);
platform_i2c_send_stop(bme280_i2c_id);
}
static uint16_t r16u(uint8_t reg) {
uint8_t high = r8u(reg);
uint8_t low = r8u(++reg);
return (high << 8) | low;
}
static uint16_t r16uLE(uint8_t reg) {
uint8_t low = r8u(reg);
uint8_t high = r8u(++reg);
return (high << 8) | low;
}
static uint32_t r24u(uint8_t reg) {
uint8_t high = r8u(reg);
uint8_t mid = r8u(++reg);
uint8_t low = r8u(++reg);
return (uint32_t)(((high << 16) | (mid << 8) | low) >> 4);
}
// Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC.
// t_fine carries fine temperature as global value
static BME280_S32_t bme280_compensate_T(BME280_S32_t adc_T) {
BME280_S32_t var1, var2, T;
var1 = ((((adc_T>>3) - ((BME280_S32_t)bme280_data.dig_T1<<1))) * ((BME280_S32_t)bme280_data.dig_T2)) >> 11;
var2 = (((((adc_T>>4) - ((BME280_S32_t)bme280_data.dig_T1)) * ((adc_T>>4) - ((BME280_S32_t)bme280_data.dig_T1))) >> 12) *
((BME280_S32_t)bme280_data.dig_T3)) >> 14;
bme280_t_fine = var1 + var2;
T = (bme280_t_fine * 5 + 128) >> 8;
return T;
}
// Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 integer bits and 8 fractional bits).
// Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa
static BME280_U32_t bme280_compensate_P(BME280_S32_t adc_P) {
BME280_S64_t var1, var2, p;
var1 = ((BME280_S64_t)bme280_t_fine) - 128000;
var2 = var1 * var1 * (BME280_S64_t)bme280_data.dig_P6;
var2 = var2 + ((var1*(BME280_S64_t)bme280_data.dig_P5)<<17);
var2 = var2 + (((BME280_S64_t)bme280_data.dig_P4)<<35);
var1 = ((var1 * var1 * (BME280_S64_t)bme280_data.dig_P3)>>8) + ((var1 * (BME280_S64_t)bme280_data.dig_P2)<<12);
var1 = (((((BME280_S64_t)1)<<47)+var1))*((BME280_S64_t)bme280_data.dig_P1)>>33;
if (var1 == 0) {
return 0; // avoid exception caused by division by zero
}
p = 1048576-adc_P;
p = (((p<<31)-var2)*3125)/var1;
var1 = (((BME280_S64_t)bme280_data.dig_P9) * (p>>13) * (p>>13)) >> 25;
var2 = (((BME280_S64_t)bme280_data.dig_P8) * p) >> 19;
p = ((p + var1 + var2) >> 8) + (((BME280_S64_t)bme280_data.dig_P7)<<4);
p = (p * 10) >> 8;
return (BME280_U32_t)p;
}
// Returns humidity in %RH as unsigned 32 bit integer in Q22.10 format (22 integer and 10 fractional bits).
// Output value of “47445” represents 47445/1024 = 46.333 %RH
static BME280_U32_t bme280_compensate_H(BME280_S32_t adc_H) {
BME280_S32_t v_x1_u32r;
v_x1_u32r = (bme280_t_fine - ((BME280_S32_t)76800));
v_x1_u32r = (((((adc_H << 14) - (((BME280_S32_t)bme280_data.dig_H4) << 20) - (((BME280_S32_t)bme280_data.dig_H5) * v_x1_u32r)) +
((BME280_S32_t)16384)) >> 15) * (((((((v_x1_u32r * ((BME280_S32_t)bme280_data.dig_H6)) >> 10) * (((v_x1_u32r *
((BME280_S32_t)bme280_data.dig_H3)) >> 11) + ((BME280_S32_t)32768))) >> 10) + ((BME280_S32_t)2097152)) *
((BME280_S32_t)bme280_data.dig_H2) + 8192) >> 14));
v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((BME280_S32_t)bme280_data.dig_H1)) >> 4));
v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);
v_x1_u32r = v_x1_u32r>>12;
return (BME280_U32_t)((v_x1_u32r * 1000)>>10);
}
static int bme280_lua_init(lua_State* L) {
uint8_t sda;
uint8_t scl;
uint8_t config;
uint8_t ack;
uint8_t const bit3 = 0b111;
uint8_t const bit2 = 0b11;
if (!lua_isnumber(L, 1) || !lua_isnumber(L, 2)) {
return luaL_error(L, "wrong arg range");
}
sda = luaL_checkinteger(L, 1);
scl = luaL_checkinteger(L, 2);
bme280_mode = (!lua_isnumber(L, 6)?BME280_NORMAL_MODE:(luaL_checkinteger(L, 6)&bit2)) // 6-th parameter: power mode
| ((!lua_isnumber(L, 4)?BME280_OVERSAMP_16X:(luaL_checkinteger(L, 4)&bit3)) << 2) // 4-th parameter: pressure oversampling
| ((!lua_isnumber(L, 3)?BME280_OVERSAMP_16X:(luaL_checkinteger(L, 3)&bit3)) << 5); // 3-rd parameter: temperature oversampling
bme280_ossh = (!lua_isnumber(L, 5))?BME280_OVERSAMP_16X:(luaL_checkinteger(L, 5)&bit3); // 5-th parameter: humidity oversampling
config = ((!lua_isnumber(L, 7)?BME280_STANDBY_TIME_20_MS:(luaL_checkinteger(L, 7)&bit3))<< 4) // 7-th parameter: inactive duration in normal mode
| ((!lua_isnumber(L, 8)?BME280_FILTER_COEFF_16:(luaL_checkinteger(L, 8)&bit3)) << 1); // 8-th parameter: IIR filter
NODE_DBG("mode: %x\nhumidity oss: %x\nconfig: %x\n", bme280_mode, bme280_ossh, config);
platform_i2c_setup(bme280_i2c_id, sda, scl, PLATFORM_I2C_SPEED_SLOW);
bme280_i2c_addr = BME280_I2C_ADDRESS1;
platform_i2c_send_start(bme280_i2c_id);
ack = platform_i2c_send_address(bme280_i2c_id, bme280_i2c_addr, PLATFORM_I2C_DIRECTION_TRANSMITTER);
platform_i2c_send_stop(bme280_i2c_id);
if (!ack) {
NODE_DBG("No ACK on address: %x\n", bme280_i2c_addr);
bme280_i2c_addr = BME280_I2C_ADDRESS2;
platform_i2c_send_start(bme280_i2c_id);
ack = platform_i2c_send_address(bme280_i2c_id, bme280_i2c_addr, PLATFORM_I2C_DIRECTION_TRANSMITTER);
platform_i2c_send_stop(bme280_i2c_id);
if (!ack) {
NODE_DBG("No ACK on address: %x\n", bme280_i2c_addr);
return 0;
}
}
uint8_t chipid = r8u(BME280_REGISTER_CHIPID);
NODE_DBG("chip_id: %x\n", chipid);
bme280_isbme = (chipid == 0x60);
uint8_t reg = BME280_REGISTER_DIG_T;
bme280_data.dig_T1 = r16uLE(reg); reg+=2;
bme280_data.dig_T2 = r16sLE(reg); reg+=2;
bme280_data.dig_T3 = r16sLE(reg);
//NODE_DBG("dig_T: %d\t%d\t%d\n", bme280_data.dig_T1, bme280_data.dig_T2, bme280_data.dig_T3);
reg = BME280_REGISTER_DIG_P;
bme280_data.dig_P1 = r16uLE(reg); reg+=2;
bme280_data.dig_P2 = r16sLE(reg); reg+=2;
bme280_data.dig_P3 = r16sLE(reg); reg+=2;
bme280_data.dig_P4 = r16sLE(reg); reg+=2;
bme280_data.dig_P5 = r16sLE(reg); reg+=2;
bme280_data.dig_P6 = r16sLE(reg); reg+=2;
bme280_data.dig_P7 = r16sLE(reg); reg+=2;
bme280_data.dig_P8 = r16sLE(reg); reg+=2;
bme280_data.dig_P9 = r16sLE(reg);
// NODE_DBG("dig_P: %d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", bme280_data.dig_P1, bme280_data.dig_P2, bme280_data.dig_P3, bme280_data.dig_P4, bme280_data.dig_P5, bme280_data.dig_P6, bme280_data.dig_P7, bme280_data.dig_P8, bme280_data.dig_P9);
w8u(BME280_REGISTER_CONFIG, config);
if (bme280_isbme) {
reg = BME280_REGISTER_DIG_H1;
bme280_data.dig_H1 = r8u(reg);
reg = BME280_REGISTER_DIG_H2;
bme280_data.dig_H2 = r16sLE(reg); reg+=2;
bme280_data.dig_H3 = r8u(reg); reg++;
bme280_data.dig_H4 = ((int16_t)r8u(reg) << 4 | (r8u(reg+1) & 0xF)); reg+=2;
bme280_data.dig_H5 = ((int16_t)r8u(reg+1) << 4 | (r8u(reg) >> 4)); reg+=2;
bme280_data.dig_H6 = (int8_t)r8u(reg);
// NODE_DBG("dig_H: %d\t%d\t%d\t%d\t%d\t%d\n", bme280_data.dig_H1, bme280_data.dig_H2, bme280_data.dig_H3, bme280_data.dig_H4, bme280_data.dig_H5, bme280_data.dig_H6);
w8u(BME280_REGISTER_CONTROL_HUM, bme280_ossh);
lua_pushinteger(L, 2);
} else {
lua_pushinteger(L, 1);
}
w8u(BME280_REGISTER_CONTROL, bme280_mode);
return 1;
}
static void bme280_readoutdone (void *arg)
{
NODE_DBG("timer out\n");
lua_State *L = arg;
lua_rawgeti (L, LUA_REGISTRYINDEX, lua_connected_readout_ref);
lua_call (L, 0, 0);
luaL_unref (L, LUA_REGISTRYINDEX, lua_connected_readout_ref);
os_timer_disarm (&bme280_timer);
}
static int bme280_lua_startreadout(lua_State* L) {
uint32_t delay;
if (lua_isnumber(L, 1)) {
delay = luaL_checkinteger(L, 1);
if (!delay) {delay = BME280_SAMPLING_DELAY;} // if delay is 0 then set the default delay
}
if (!lua_isnoneornil(L, 2)) {
lua_pushvalue(L, 2);
lua_connected_readout_ref = luaL_ref(L, LUA_REGISTRYINDEX);
} else {
lua_connected_readout_ref = LUA_NOREF;
}
w8u(BME280_REGISTER_CONTROL_HUM, bme280_ossh);
w8u(BME280_REGISTER_CONTROL, (bme280_mode & 0xFC) | BME280_FORCED_MODE);
NODE_DBG("control old: %x, control: %x, delay: %d\n", bme280_mode, (bme280_mode & 0xFC) | BME280_FORCED_MODE, delay);
if (lua_connected_readout_ref != LUA_NOREF) {
NODE_DBG("timer armed\n");
os_timer_disarm (&bme280_timer);
os_timer_setfn (&bme280_timer, (os_timer_func_t *)bme280_readoutdone, L);
os_timer_arm (&bme280_timer, delay, 0); // trigger callback when readout is ready
}
return 0;
}
static int bme280_lua_temp(lua_State* L) {
uint32_t adc_T = bme280_adc_T();
if (adc_T == 0x80000 || adc_T == 0xfffff)
return 0;
lua_pushinteger(L, bme280_compensate_T(adc_T));
lua_pushinteger(L, bme280_t_fine);
return 2;
}
static int bme280_lua_baro(lua_State* L) {
uint32_t adc_T = bme280_adc_T();
uint32_t T = bme280_compensate_T(adc_T);
uint32_t adc_P = bme280_adc_P();
if (adc_T == 0x80000 || adc_T == 0xfffff || adc_P ==0x80000 || adc_P == 0xfffff)
return 0;
lua_pushinteger(L, bme280_compensate_P(adc_P));
lua_pushinteger(L, T);
return 2;
}
static int bme280_lua_humi(lua_State* L) {
if (!bme280_isbme) return 0;
uint32_t adc_T = bme280_adc_T();
uint32_t T = bme280_compensate_T(adc_T);
uint32_t adc_H = bme280_adc_H();
if (adc_T == 0x80000 || adc_T == 0xfffff || adc_H == 0x8000 || adc_H == 0xffff)
return 0;
lua_pushinteger(L, bme280_compensate_H(adc_H));
lua_pushinteger(L, T);
return 2;
}
static int bme280_lua_qfe2qnh(lua_State* L) {
if (!lua_isnumber(L, 2)) {
return luaL_error(L, "wrong arg range");
}
int32_t qfe = luaL_checkinteger(L, 1);
int32_t h = luaL_checkinteger(L, 2);
double hc;
if (bme280_h == h) {
hc = bme280_hc;
} else {
hc = pow((double)(1.0 - 2.25577e-5 * h), (double)(-5.25588));
bme280_hc = hc; bme280_h = h;
}
double qnh = (double)qfe * hc;
lua_pushinteger(L, (int32_t)(qnh + 0.5));
return 1;
}
static int bme280_lua_altitude(lua_State* L) {
if (!lua_isnumber(L, 2)) {
return luaL_error(L, "wrong arg range");
}
int32_t P = luaL_checkinteger(L, 1);
int32_t qnh = luaL_checkinteger(L, 2);
double h = (1.0 - pow((double)P/(double)qnh, 1.0/5.25588)) / 2.25577e-5 * 100.0;
lua_pushinteger(L, (int32_t)(h + (((h<0)?-1:(h>0)) * 0.5)));
return 1;
}
static double ln(double x) {
double y = (x-1)/(x+1);
double y2 = y*y;
double r = 0;
for (int8_t i=33; i>0; i-=2) { //we've got the power
r = 1.0/(double)i + y2 * r;
}
return 2*y*r;
}
static int bme280_lua_dewpoint(lua_State* L) {
const double c243 = 243.5;
const double c17 = 17.67;
if (!lua_isnumber(L, 2)) {
return luaL_error(L, "wrong arg range");
}
double H = luaL_checkinteger(L, 1)/100000.0;
double T = luaL_checkinteger(L, 2)/100.0;
double c = ln(H) + ((c17 * T) / (c243 + T));
double d = (c243 * c)/(c17 - c) * 100.0;
lua_pushinteger(L, (int32_t)(d + (((d<0)?-1:(d>0)) * 0.5)));
return 1;
}
static const LUA_REG_TYPE bme280_map[] = {
{ LSTRKEY( "init" ), LFUNCVAL(bme280_lua_init)},
{ LSTRKEY( "temp" ), LFUNCVAL(bme280_lua_temp)},
{ LSTRKEY( "baro" ), LFUNCVAL(bme280_lua_baro)},
{ LSTRKEY( "humi" ), LFUNCVAL(bme280_lua_humi)},
{ LSTRKEY( "startreadout" ), LFUNCVAL(bme280_lua_startreadout)},
{ LSTRKEY( "qfe2qnh" ), LFUNCVAL(bme280_lua_qfe2qnh)},
{ LSTRKEY( "altitude" ), LFUNCVAL(bme280_lua_altitude)},
{ LSTRKEY( "dewpoint" ), LFUNCVAL(bme280_lua_dewpoint)},
{ LNILKEY, LNILVAL}
};
NODEMCU_MODULE(BME280, "bme280", bme280_map, NULL);
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include <stdlib.h>
#include <string.h>
#include "esp_misc.h"
static const uint32_t bmp085_i2c_id = 0;
static const uint8_t bmp085_i2c_addr = 0x77;
static struct {
int16_t AC1;
int16_t AC2;
int16_t AC3;
uint16_t AC4;
uint16_t AC5;
uint16_t AC6;
int16_t B1;
int16_t B2;
int16_t MB;
int16_t MC;
int16_t MD;
} bmp085_data;
static uint8_t ICACHE_FLASH_ATTR r8u(uint32_t id, uint8_t reg) {
uint8_t ret;
platform_i2c_send_start(id);
platform_i2c_send_address(id, bmp085_i2c_addr, PLATFORM_I2C_DIRECTION_TRANSMITTER);
platform_i2c_send_byte(id, reg);
platform_i2c_send_stop(id);
platform_i2c_send_start(id);
platform_i2c_send_address(id, bmp085_i2c_addr, PLATFORM_I2C_DIRECTION_RECEIVER);
ret = platform_i2c_recv_byte(id, 0);
platform_i2c_send_stop(id);
return ret;
}
static uint16_t ICACHE_FLASH_ATTR r16u(uint32_t id, uint8_t reg) {
uint8_t high = r8u(id, reg);
uint8_t low = r8u(id, reg + 1);
return (high << 8) | low;
}
static int16_t ICACHE_FLASH_ATTR r16(uint32_t id, uint8_t reg) {
return (int16_t) r16u(id, reg);
}
static int ICACHE_FLASH_ATTR bmp085_init(lua_State* L) {
uint32_t sda;
uint32_t scl;
if (!lua_isnumber(L, 1) || !lua_isnumber(L, 2)) {
return luaL_error(L, "wrong arg range");
}
sda = luaL_checkinteger(L, 1);
scl = luaL_checkinteger(L, 2);
if (scl == 0 || sda == 0) {
return luaL_error(L, "no i2c for D0");
}
platform_i2c_setup(bmp085_i2c_id, sda, scl, PLATFORM_I2C_SPEED_SLOW);
bmp085_data.AC1 = r16(bmp085_i2c_id, 0xAA);
bmp085_data.AC2 = r16(bmp085_i2c_id, 0xAC);
bmp085_data.AC3 = r16(bmp085_i2c_id, 0xAE);
bmp085_data.AC4 = r16u(bmp085_i2c_id, 0xB0);
bmp085_data.AC5 = r16u(bmp085_i2c_id, 0xB2);
bmp085_data.AC6 = r16u(bmp085_i2c_id, 0xB4);
bmp085_data.B1 = r16(bmp085_i2c_id, 0xB6);
bmp085_data.B2 = r16(bmp085_i2c_id, 0xB8);
bmp085_data.MB = r16(bmp085_i2c_id, 0xBA);
bmp085_data.MC = r16(bmp085_i2c_id, 0xBC);
bmp085_data.MD = r16(bmp085_i2c_id, 0xBE);
return 0;
}
static uint32_t bmp085_temperature_raw_b5(void) {
int16_t t, X1, X2;
platform_i2c_send_start(bmp085_i2c_id);
platform_i2c_send_address(bmp085_i2c_id, bmp085_i2c_addr, PLATFORM_I2C_DIRECTION_TRANSMITTER);
platform_i2c_send_byte(bmp085_i2c_id, 0xF4);
platform_i2c_send_byte(bmp085_i2c_id, 0x2E);
platform_i2c_send_stop(bmp085_i2c_id);
// Wait for device to complete sampling
os_delay_us(4500);
t = r16(bmp085_i2c_id, 0xF6);
X1 = ((t - bmp085_data.AC6) * bmp085_data.AC5) >> 15;
X2 = (bmp085_data.MC << 11)/ (X1 + bmp085_data.MD);
return X1 + X2;
}
static int16_t ICACHE_FLASH_ATTR bmp085_temperature(void) {
return (bmp085_temperature_raw_b5() + 8) >> 4;
}
static int ICACHE_FLASH_ATTR bmp085_lua_temperature(lua_State* L) {
lua_pushinteger(L, bmp085_temperature());
return 1;
}
static int32_t ICACHE_FLASH_ATTR bmp085_pressure_raw(int oss) {
int32_t p;
int32_t p1, p2, p3;
platform_i2c_send_start(bmp085_i2c_id);
platform_i2c_send_address(bmp085_i2c_id, bmp085_i2c_addr, PLATFORM_I2C_DIRECTION_TRANSMITTER);
platform_i2c_send_byte(bmp085_i2c_id, 0xF4);
platform_i2c_send_byte(bmp085_i2c_id, 0x34 + 64 * oss);
platform_i2c_send_stop(bmp085_i2c_id);
// Wait for device to complete sampling
switch (oss) {
case 0: os_delay_us( 4500); break;
case 1: os_delay_us( 7500); break;
case 2: os_delay_us(13500); break;
case 3: os_delay_us(25500); break;
}
p1 = r8u(bmp085_i2c_id, 0xF6);
p2 = r8u(bmp085_i2c_id, 0xF7);
p3 = r8u(bmp085_i2c_id, 0xF8);
p = (p1 << 16) | (p2 << 8) | p3;
p = p >> (8 - oss);
return p;
}
static int ICACHE_FLASH_ATTR bmp085_lua_pressure_raw(lua_State* L) {
uint8_t oss = 0;
int32_t p;
if (lua_isnumber(L, 1)) {
oss = luaL_checkinteger(L, 1);
if (oss > 3) {
oss = 3;
}
}
p = bmp085_pressure_raw(oss);
lua_pushinteger(L, p);
return 1;
}
static int ICACHE_FLASH_ATTR bmp085_lua_pressure(lua_State* L) {
uint8_t oss = 0;
int32_t p;
int32_t X1, X2, X3, B3, B4, B5, B6, B7;
if (lua_isnumber(L, 1)) {
oss = luaL_checkinteger(L, 1);
if (oss > 3) {
oss = 3;
}
}
p = bmp085_pressure_raw(oss);
B5 = bmp085_temperature_raw_b5();
B6 = B5 - 4000;
X1 = ((int32_t)bmp085_data.B2 * ((B6 * B6) >> 12)) >> 11;
X2 = ((int32_t)bmp085_data.AC2 * B6) >> 11;
X3 = X1 + X2;
B3 = ((((int32_t)bmp085_data.AC1 << 2) + X3) * (1 << oss) + 2) >> 2;
X1 = ((int32_t)bmp085_data.AC3 * B6) >> 13;
X2 = ((int32_t)bmp085_data.B1 * ((B6 * B6) >> 12)) >> 16;
X3 = (X1 + X2 + 2) >> 2;
B4 = ((int32_t)bmp085_data.AC4 * (X3 + 32768)) >> 15;
B7 = (p - B3) * (50000 / (1 << oss));
p = (B7 / B4) << 1;
X1 = (p >> 8) * (p >> 8);
X1 = (X1 * 3038) >> 16;
X2 = (-7357 * p) >> 16;
p = p + ((X1 + X2 + 3791) >> 4);
lua_pushinteger(L, p);
return 1;
}
static const LUA_REG_TYPE bmp085_map[] = {
{ LSTRKEY( "temperature" ), LFUNCVAL( bmp085_lua_temperature )},
{ LSTRKEY( "pressure" ), LFUNCVAL( bmp085_lua_pressure )},
{ LSTRKEY( "pressure_raw" ), LFUNCVAL( bmp085_lua_pressure_raw )},
{ LSTRKEY( "init" ), LFUNCVAL( bmp085_init )},
{ LNILKEY, LNILVAL}
};
NODEMCU_MODULE(BMP085, "bmp085", bmp085_map, NULL);
/* Lua CJSON - JSON support for Lua
*
* Copyright (c) 2010-2012 Mark Pulford <mark@kyne.com.au>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* Caveats:
* - JSON "null" values are represented as lightuserdata since Lua
* tables cannot contain "nil". Compare with cjson.null.
* - Invalid UTF-8 characters are not detected and will be passed
* untouched. If required, UTF-8 error checking should be done
* outside this library.
* - Javascript comments are not part of the JSON spec, and are not
* currently supported.
*
* Note: Decoding is slower than encoding. Lua spends significant
* time (30%) managing tables when parsing JSON since it is
* difficult to know object/array sizes ahead of time.
*/
// #include <assert.h>
#include "module.h"
#include <string.h>
#include <math.h>
#include <limits.h>
#include "lauxlib.h"
#include "flash_api.h"
#include "ctype.h"
#include "strbuf.h"
#include "cjson_mem.h"
#define FPCONV_G_FMT_BUFSIZE 32
#define fpconv_strtod c_strtod
#define fpconv_init() ((void)0)
#ifndef CJSON_MODNAME
#define CJSON_MODNAME "cjson"
#endif
#ifndef CJSON_VERSION
#define CJSON_VERSION "2.1devel"
#endif
/* Workaround for Solaris platforms missing isinf() */
#if !defined(isinf) && (defined(USE_INTERNAL_ISINF) || defined(MISSING_ISINF))
#define isinf(x) (!isnan(x) && isnan((x) - (x)))
#endif
#define DEFAULT_SPARSE_CONVERT 0
#define DEFAULT_SPARSE_RATIO 2
#define DEFAULT_SPARSE_SAFE 10
#define DEFAULT_ENCODE_MAX_DEPTH 1000
#define DEFAULT_DECODE_MAX_DEPTH 1000
#define DEFAULT_ENCODE_INVALID_NUMBERS 0
#define DEFAULT_DECODE_INVALID_NUMBERS 1
#define DEFAULT_ENCODE_KEEP_BUFFER 0
#define DEFAULT_ENCODE_NUMBER_PRECISION 14
#ifdef DISABLE_INVALID_NUMBERS
#undef DEFAULT_DECODE_INVALID_NUMBERS
#define DEFAULT_DECODE_INVALID_NUMBERS 0
#endif
typedef enum {
T_OBJ_BEGIN,
T_OBJ_END,
T_ARR_BEGIN,
T_ARR_END,
T_STRING,
T_NUMBER,
T_BOOLEAN,
T_NULL,
T_COLON,
T_COMMA,
T_END,
T_WHITESPACE,
T_ERROR,
T_UNKNOWN
} json_token_type_t;
#if 0
static const char *json_token_type_name[] = {
"T_OBJ_BEGIN",
"T_OBJ_END",
"T_ARR_BEGIN",
"T_ARR_END",
"T_STRING",
"T_NUMBER",
"T_BOOLEAN",
"T_NULL",
"T_COLON",
"T_COMMA",
"T_END",
"T_WHITESPACE",
"T_ERROR",
"T_UNKNOWN",
NULL
};
#endif
static const char json_token_type_name[14][16] ICACHE_STORE_ATTR ICACHE_RODATA_ATTR = {
{'T','_','O','B','J','_','B','E','G','I','N',0},
{'T','_','O','B','J','_','E','N','D',0},
{'T','_','A','R','R','_','B','E','G','I','N',0},
{'T','_','A','R','R','_','E','N','D',0},
{'T','_','S','T','R','I','N','G',0},
{'T','_','N','U','M','B','E','R',0},
{'T','_','B','O','O','L','E','A','N',0},
{'T','_','N','U','L','L',0},
{'T','_','C','O','L','O','N',0},
{'T','_','C','O','M','M','A',0},
{'T','_','E','N','D',0},
{'T','_','W','H','I','T','E','S','P','A','C','E',0},
{'T','_','E','R','R','O','R',0},
{'T','_','U','N','K','N','O','W','N',0}
};
typedef struct {
// json_token_type_t ch2token[256]; // 256*4 = 1024 byte
// char escape2char[256]; /* Decoding */
/* encode_buf is only allocated and used when
* encode_keep_buffer is set */
strbuf_t encode_buf;
int encode_sparse_convert;
int encode_sparse_ratio;
int encode_sparse_safe;
int encode_max_depth;
int encode_invalid_numbers; /* 2 => Encode as "null" */
int encode_number_precision;
int encode_keep_buffer;
int decode_invalid_numbers;
int decode_max_depth;
} json_config_t;
typedef struct {
const char *data;
const char *ptr;
strbuf_t *tmp; /* Temporary storage for strings */
json_config_t *cfg;
int current_depth;
} json_parse_t;
typedef struct {
json_token_type_t type;
int index;
union {
const char *string;
double number;
int boolean;
} value;
int string_len;
} json_token_t;
#if 0
static const char *char2escape[256] = {
"\\u0000", "\\u0001", "\\u0002", "\\u0003",
"\\u0004", "\\u0005", "\\u0006", "\\u0007",
"\\b", "\\t", "\\n", "\\u000b",
"\\f", "\\r", "\\u000e", "\\u000f",
"\\u0010", "\\u0011", "\\u0012", "\\u0013",
"\\u0014", "\\u0015", "\\u0016", "\\u0017",
"\\u0018", "\\u0019", "\\u001a", "\\u001b",
"\\u001c", "\\u001d", "\\u001e", "\\u001f",
NULL, NULL, "\\\"", NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\/",
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, "\\\\", NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\u007f",
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
};
#endif
/* ===== HELPER FUNCTION ===== */
static const char escape_array[36][8] ICACHE_STORE_ATTR ICACHE_RODATA_ATTR = {
{'\\','u','0','0','0','0','\0','\0'},
{'\\','u','0','0','0','1','\0','\0'},
{'\\','u','0','0','0','2','\0','\0'},
{'\\','u','0','0','0','3','\0','\0'},
{'\\','u','0','0','0','4','\0','\0'},
{'\\','u','0','0','0','5','\0','\0'},
{'\\','u','0','0','0','6','\0','\0'},
{'\\','u','0','0','0','7','\0','\0'},
{'\\','b','\0','\0','\0','\0','\0','\0'},
{'\\','t','\0','\0','\0','\0','\0','\0'},
{'\\','n','\0','\0','\0','\0','\0','\0'},
{'\\','u','0','0','0','b','\0','\0'},
{'\\','f','\0','\0','\0','\0','\0','\0'},
{'\\','r','\0','\0','\0','\0','\0','\0'},
{'\\','u','0','0','0','e','\0','\0'},
{'\\','u','0','0','0','f','\0','\0'},
{'\\','u','0','0','1','0','\0','\0'},
{'\\','u','0','0','1','1','\0','\0'},
{'\\','u','0','0','1','2','\0','\0'},
{'\\','u','0','0','1','3','\0','\0'},
{'\\','u','0','0','1','4','\0','\0'},
{'\\','u','0','0','1','5','\0','\0'},
{'\\','u','0','0','1','6','\0','\0'},
{'\\','u','0','0','1','7','\0','\0'},
{'\\','u','0','0','1','8','\0','\0'},
{'\\','u','0','0','1','9','\0','\0'},
{'\\','u','0','0','1','a','\0','\0'},
{'\\','u','0','0','1','b','\0','\0'},
{'\\','u','0','0','1','c','\0','\0'},
{'\\','u','0','0','1','d','\0','\0'},
{'\\','u','0','0','1','e','\0','\0'},
{'\\','u','0','0','1','f','\0','\0'},
{'\\','\"','\0','\0','\0','\0','\0','\0'},
{'\\','/','\0','\0','\0','\0','\0','\0'},
{'\\','\\','\0','\0','\0','\0','\0','\0'},
{'\\','u','0','0','7','f','\0','\0'}
};
static const char *char2escape(unsigned char c){
if(c<32) return escape_array[c];
switch(c){
case 34: return escape_array[32];
case 47: return escape_array[33];
case 92: return escape_array[34];
case 127: return escape_array[35];
default:
return NULL;
}
}
static json_token_type_t ch2token(unsigned char c){
switch(c){
case '{': return T_OBJ_BEGIN;
case '}': return T_OBJ_END;
case '[': return T_ARR_BEGIN;
case ']': return T_ARR_END;
case ',': return T_COMMA;
case ':': return T_COLON;
case '\0': return T_END;
case ' ': return T_WHITESPACE;
case '\t': return T_WHITESPACE;
case '\n': return T_WHITESPACE;
case '\r': return T_WHITESPACE;
/* Update characters that require further processing */
case 'f': case 'i': case 'I': case 'n': case 'N': case 't': case '"': case '+': case '-':
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
return T_UNKNOWN;
default:
return T_ERROR;
}
}
static char escape2char(unsigned char c){
switch(c){
case '"': return '"';
case '\\': return '\\';
case '/': return '/';
case 'b': return '\b';
case 't': return '\t';
case 'n': return '\n';
case 'f': return '\f';
case 'r': return '\r';
case 'u': return 'u';
default:
return 0;
}
}
/* ===== CONFIGURATION ===== */
#if 0
static json_config_t *json_fetch_config(lua_State *l)
{
json_config_t *cfg;
cfg = lua_touserdata(l, lua_upvalueindex(1));
if (!cfg)
luaL_error(l, "BUG: Unable to fetch CJSON configuration");
return cfg;
}
/* Ensure the correct number of arguments have been provided.
* Pad with nil to allow other functions to simply check arg[i]
* to find whether an argument was provided */
static json_config_t *json_arg_init(lua_State *l, int args)
{
luaL_argcheck(l, lua_gettop(l) <= args, args + 1,
"found too many arguments");
while (lua_gettop(l) < args)
lua_pushnil(l);
return json_fetch_config(l);
}
/* Process integer options for configuration functions */
static int json_integer_option(lua_State *l, int optindex, int *setting,
int min, int max)
{
char errmsg[64];
int value;
if (!lua_isnil(l, optindex)) {
value = luaL_checkinteger(l, optindex);
sprintf(errmsg, "expected integer between %d and %d", min, max);
luaL_argcheck(l, min <= value && value <= max, 1, errmsg);
*setting = value;
}
lua_pushinteger(l, *setting);
return 1;
}
/* Process enumerated arguments for a configuration function */
static int json_enum_option(lua_State *l, int optindex, int *setting,
const char **options, int bool_true)
{
static const char *bool_options[] = { "off", "on", NULL };
if (!options) {
options = bool_options;
bool_true = 1;
}
if (!lua_isnil(l, optindex)) {
if (bool_true && lua_isboolean(l, optindex))
*setting = lua_toboolean(l, optindex) * bool_true;
else
*setting = luaL_checkoption(l, optindex, NULL, options);
}
if (bool_true && (*setting == 0 || *setting == bool_true))
lua_pushboolean(l, *setting);
else
lua_pushstring(l, options[*setting]);
return 1;
}
/* Configures handling of extremely sparse arrays:
* convert: Convert extremely sparse arrays into objects? Otherwise error.
* ratio: 0: always allow sparse; 1: never allow sparse; >1: use ratio
* safe: Always use an array when the max index <= safe */
static int json_cfg_encode_sparse_array(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 3);
json_enum_option(l, 1, &cfg->encode_sparse_convert, NULL, 1);
json_integer_option(l, 2, &cfg->encode_sparse_ratio, 0, INT_MAX);
json_integer_option(l, 3, &cfg->encode_sparse_safe, 0, INT_MAX);
return 3;
}
/* Configures the maximum number of nested arrays/objects allowed when
* encoding */
static int json_cfg_encode_max_depth(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
return json_integer_option(l, 1, &cfg->encode_max_depth, 1, INT_MAX);
}
/* Configures the maximum number of nested arrays/objects allowed when
* encoding */
static int json_cfg_decode_max_depth(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
return json_integer_option(l, 1, &cfg->decode_max_depth, 1, INT_MAX);
}
/* Configures number precision when converting doubles to text */
static int json_cfg_encode_number_precision(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
return json_integer_option(l, 1, &cfg->encode_number_precision, 1, 14);
}
/* Configures JSON encoding buffer persistence */
static int json_cfg_encode_keep_buffer(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
int old_value;
old_value = cfg->encode_keep_buffer;
json_enum_option(l, 1, &cfg->encode_keep_buffer, NULL, 1);
/* Init / free the buffer if the setting has changed */
if (old_value ^ cfg->encode_keep_buffer) {
if (cfg->encode_keep_buffer){
if(-1==strbuf_init(&cfg->encode_buf, 0))
return luaL_error(l, "not enough memory");
}
else
strbuf_free(&cfg->encode_buf);
}
return 1;
}
#if defined(DISABLE_INVALID_NUMBERS) && !defined(USE_INTERNAL_FPCONV)
void json_verify_invalid_number_setting(lua_State *l, int *setting)
{
if (*setting == 1) {
*setting = 0;
luaL_error(l, "Infinity, NaN, and/or hexadecimal numbers are not supported.");
}
}
#else
#define json_verify_invalid_number_setting(l, s) do { } while(0)
#endif
static int json_cfg_encode_invalid_numbers(lua_State *l)
{
static const char *options[] = { "off", "on", "null", NULL };
json_config_t *cfg = json_arg_init(l, 1);
json_enum_option(l, 1, &cfg->encode_invalid_numbers, options, 1);
json_verify_invalid_number_setting(l, &cfg->encode_invalid_numbers);
return 1;
}
static int json_cfg_decode_invalid_numbers(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
json_enum_option(l, 1, &cfg->decode_invalid_numbers, NULL, 1);
json_verify_invalid_number_setting(l, &cfg->encode_invalid_numbers);
return 1;
}
static int json_destroy_config(lua_State *l)
{
json_config_t *cfg;
cfg = lua_touserdata(l, 1);
if (cfg)
strbuf_free(&cfg->encode_buf);
cfg = NULL;
return 0;
}
static void json_create_config(lua_State *l)
{
json_config_t *cfg;
int i;
cfg = lua_newuserdata(l, sizeof(*cfg));
/* Create GC method to clean up strbuf */
lua_newtable(l);
lua_pushcfunction(l, json_destroy_config);
lua_setfield(l, -2, "__gc");
lua_setmetatable(l, -2);
cfg->encode_sparse_convert = DEFAULT_SPARSE_CONVERT;
cfg->encode_sparse_ratio = DEFAULT_SPARSE_RATIO;
cfg->encode_sparse_safe = DEFAULT_SPARSE_SAFE;
cfg->encode_max_depth = DEFAULT_ENCODE_MAX_DEPTH;
cfg->decode_max_depth = DEFAULT_DECODE_MAX_DEPTH;
cfg->encode_invalid_numbers = DEFAULT_ENCODE_INVALID_NUMBERS;
cfg->decode_invalid_numbers = DEFAULT_DECODE_INVALID_NUMBERS;
cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER;
cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION;
#if DEFAULT_ENCODE_KEEP_BUFFER > 0
strbuf_init(&cfg->encode_buf, 0);
#endif
/* Decoding init */
/* Tag all characters as an error */
for (i = 0; i < 256; i++)
cfg->ch2token[i] = T_ERROR;
/* Set tokens that require no further processing */
cfg->ch2token['{'] = T_OBJ_BEGIN;
cfg->ch2token['}'] = T_OBJ_END;
cfg->ch2token['['] = T_ARR_BEGIN;
cfg->ch2token[']'] = T_ARR_END;
cfg->ch2token[','] = T_COMMA;
cfg->ch2token[':'] = T_COLON;
cfg->ch2token['\0'] = T_END;
cfg->ch2token[' '] = T_WHITESPACE;
cfg->ch2token['\t'] = T_WHITESPACE;
cfg->ch2token['\n'] = T_WHITESPACE;
cfg->ch2token['\r'] = T_WHITESPACE;
/* Update characters that require further processing */
cfg->ch2token['f'] = T_UNKNOWN; /* false? */
cfg->ch2token['i'] = T_UNKNOWN; /* inf, ininity? */
cfg->ch2token['I'] = T_UNKNOWN;
cfg->ch2token['n'] = T_UNKNOWN; /* null, nan? */
cfg->ch2token['N'] = T_UNKNOWN;
cfg->ch2token['t'] = T_UNKNOWN; /* true? */
cfg->ch2token['"'] = T_UNKNOWN; /* string? */
cfg->ch2token['+'] = T_UNKNOWN; /* number? */
cfg->ch2token['-'] = T_UNKNOWN;
for (i = 0; i < 10; i++)
cfg->ch2token['0' + i] = T_UNKNOWN;
/* Lookup table for parsing escape characters */
for (i = 0; i < 256; i++)
cfg->escape2char[i] = 0; /* String error */
cfg->escape2char['"'] = '"';
cfg->escape2char['\\'] = '\\';
cfg->escape2char['/'] = '/';
cfg->escape2char['b'] = '\b';
cfg->escape2char['t'] = '\t';
cfg->escape2char['n'] = '\n';
cfg->escape2char['f'] = '\f';
cfg->escape2char['r'] = '\r';
cfg->escape2char['u'] = 'u'; /* Unicode parsing required */
}
#endif
json_config_t _cfg;
static json_config_t *json_fetch_config(lua_State *l)
{
return &_cfg;
}
static int cfg_init(json_config_t *cfg){
cfg->encode_sparse_convert = DEFAULT_SPARSE_CONVERT;
cfg->encode_sparse_ratio = DEFAULT_SPARSE_RATIO;
cfg->encode_sparse_safe = DEFAULT_SPARSE_SAFE;
cfg->encode_max_depth = DEFAULT_ENCODE_MAX_DEPTH;
cfg->decode_max_depth = DEFAULT_DECODE_MAX_DEPTH;
cfg->encode_invalid_numbers = DEFAULT_ENCODE_INVALID_NUMBERS;
cfg->decode_invalid_numbers = DEFAULT_DECODE_INVALID_NUMBERS;
cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER;
cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION;
#if DEFAULT_ENCODE_KEEP_BUFFER > 0
if(-1==strbuf_init(&cfg->encode_buf, 0)){
NODE_ERR("not enough memory\n");
return -1;
}
#endif
return 0;
}
/* ===== ENCODING ===== */
static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *json, int lindex,
const char *reason)
{
if (!cfg->encode_keep_buffer)
strbuf_free(json);
luaL_error(l, "Cannot serialise %s: %s",
lua_typename(l, lua_type(l, lindex)), reason);
}
/* json_append_string args:
* - lua_State
* - JSON strbuf
* - String (Lua stack index)
*
* Returns nothing. Doesn't remove string from Lua stack */
static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
{
const char *escstr;
int i;
const char *str;
size_t len;
str = lua_tolstring(l, lindex, &len);
/* Worst case is len * 6 (all unicode escapes).
* This buffer is reused constantly for small strings
* If there are any excess pages, they won't be hit anyway.
* This gains ~5% speedup. */
strbuf_ensure_empty_length(json, len * 6 + 2);
strbuf_append_char_unsafe(json, '\"');
for (i = 0; i < len; i++) {
escstr = char2escape((unsigned char)str[i]);
if (escstr){
int i;
char temp[8]; // for now, 8-bytes is enough.
for (i=0; i < 8; ++i)
{
temp[i] = byte_of_aligned_array(escstr, i);
if(temp[i]==0) break;
}
escstr = temp;
strbuf_append_string(json, escstr);
}
else
strbuf_append_char_unsafe(json, str[i]);
}
strbuf_append_char_unsafe(json, '\"');
}
/* Find the size of the array on the top of the Lua stack
* -1 object (not a pure array)
* >=0 elements in array
*/
static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json)
{
double k;
int max;
int items;
max = 0;
items = 0;
lua_pushnil(l);
/* table, startkey */
while (lua_next(l, -2) != 0) {
/* table, key, value */
if (lua_type(l, -2) == LUA_TNUMBER &&
(k = lua_tonumber(l, -2))) {
/* Integer >= 1 ? */
if (floor(k) == k && k >= 1) {
if (k > max)
max = k;
items++;
lua_pop(l, 1);
continue;
}
}
/* Must not be an array (non integer key) */
lua_pop(l, 2);
return -1;
}
/* Encode excessively sparse arrays as objects (if enabled) */
if (cfg->encode_sparse_ratio > 0 &&
max > items * cfg->encode_sparse_ratio &&
max > cfg->encode_sparse_safe) {
if (!cfg->encode_sparse_convert)
json_encode_exception(l, cfg, json, -1, "excessively sparse array");
return -1;
}
return max;
}
static void json_check_encode_depth(lua_State *l, json_config_t *cfg,
int current_depth, strbuf_t *json)
{
/* Ensure there are enough slots free to traverse a table (key,
* value) and push a string for a potential error message.
*
* Unlike "decode", the key and value are still on the stack when
* lua_checkstack() is called. Hence an extra slot for luaL_error()
* below is required just in case the next check to lua_checkstack()
* fails.
*
* While this won't cause a crash due to the EXTRA_STACK reserve
* slots, it would still be an improper use of the API. */
if (current_depth <= cfg->encode_max_depth && lua_checkstack(l, 3))
return;
if (!cfg->encode_keep_buffer)
strbuf_free(json);
luaL_error(l, "Cannot serialise, excessive nesting (%d)",
current_depth);
}
static void json_append_data(lua_State *l, json_config_t *cfg,
int current_depth, strbuf_t *json);
/* json_append_array args:
* - lua_State
* - JSON strbuf
* - Size of passwd Lua array (top of stack) */
static void json_append_array(lua_State *l, json_config_t *cfg, int current_depth,
strbuf_t *json, int array_length)
{
int comma, i;
strbuf_append_char(json, '[');
comma = 0;
for (i = 1; i <= array_length; i++) {
if (comma)
strbuf_append_char(json, ',');
else
comma = 1;
lua_rawgeti(l, -1, i);
json_append_data(l, cfg, current_depth, json);
lua_pop(l, 1);
}
strbuf_append_char(json, ']');
}
static void json_append_number(lua_State *l, json_config_t *cfg,
strbuf_t *json, int lindex)
{
double num = lua_tonumber(l, lindex);
int len;
if (cfg->encode_invalid_numbers == 0) {
/* Prevent encoding invalid numbers */
if (isinf(num) || isnan(num))
json_encode_exception(l, cfg, json, lindex,
"must not be NaN or Infinity");
} else if (cfg->encode_invalid_numbers == 1) {
/* Encode NaN/Infinity separately to ensure Javascript compatible
* values are used. */
if (isnan(num)) {
strbuf_append_mem(json, "NaN", 3);
return;
}
if (isinf(num)) {
if (num < 0)
strbuf_append_mem(json, "-Infinity", 9);
else
strbuf_append_mem(json, "Infinity", 8);
return;
}
} else {
/* Encode invalid numbers as "null" */
if (isinf(num) || isnan(num)) {
strbuf_append_mem(json, "null", 4);
return;
}
}
strbuf_ensure_empty_length(json, FPCONV_G_FMT_BUFSIZE);
// len = fpconv_g_fmt(strbuf_empty_ptr(json), num, cfg->encode_number_precision);
sprintf(strbuf_empty_ptr(json), LUA_NUMBER_FMT, (LUA_NUMBER)num);
len = strlen(strbuf_empty_ptr(json));
strbuf_extend_length(json, len);
}
static void json_append_object(lua_State *l, json_config_t *cfg,
int current_depth, strbuf_t *json)
{
int comma, keytype;
/* Object */
strbuf_append_char(json, '{');
lua_pushnil(l);
/* table, startkey */
comma = 0;
while (lua_next(l, -2) != 0) {
if (comma)
strbuf_append_char(json, ',');
else
comma = 1;
/* table, key, value */
keytype = lua_type(l, -2);
if (keytype == LUA_TNUMBER) {
strbuf_append_char(json, '"');
json_append_number(l, cfg, json, -2);
strbuf_append_mem(json, "\":", 2);
} else if (keytype == LUA_TSTRING) {
json_append_string(l, json, -2);
strbuf_append_char(json, ':');
} else {
json_encode_exception(l, cfg, json, -2,
"table key must be a number or string");
/* never returns */
}
/* table, key, value */
json_append_data(l, cfg, current_depth, json);
lua_pop(l, 1);
/* table, key */
}
strbuf_append_char(json, '}');
}
/* Serialise Lua data into JSON string. */
static void json_append_data(lua_State *l, json_config_t *cfg,
int current_depth, strbuf_t *json)
{
int len;
switch (lua_type(l, -1)) {
case LUA_TSTRING:
json_append_string(l, json, -1);
break;
case LUA_TNUMBER:
json_append_number(l, cfg, json, -1);
break;
case LUA_TBOOLEAN:
if (lua_toboolean(l, -1))
strbuf_append_mem(json, "true", 4);
else
strbuf_append_mem(json, "false", 5);
break;
case LUA_TTABLE:
current_depth++;
json_check_encode_depth(l, cfg, current_depth, json);
len = lua_array_length(l, cfg, json);
if (len > 0)
json_append_array(l, cfg, current_depth, json, len);
else
json_append_object(l, cfg, current_depth, json);
break;
case LUA_TNIL:
strbuf_append_mem(json, "null", 4);
break;
case LUA_TLIGHTUSERDATA:
if (lua_touserdata(l, -1) == NULL) {
strbuf_append_mem(json, "null", 4);
break;
}
default:
/* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD,
* and LUA_TLIGHTUSERDATA) cannot be serialised */
json_encode_exception(l, cfg, json, -1, "type not supported");
/* never returns */
}
}
static int json_encode(lua_State *l)
{
json_config_t *cfg = json_fetch_config(l);
strbuf_t local_encode_buf;
strbuf_t *encode_buf;
char *json;
int len;
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
if (!cfg->encode_keep_buffer) {
/* Use private buffer */
encode_buf = &local_encode_buf;
if(-1==strbuf_init(encode_buf, 0))
return luaL_error(l, "not enough memory");
} else {
/* Reuse existing buffer */
encode_buf = &cfg->encode_buf;
strbuf_reset(encode_buf);
}
json_append_data(l, cfg, 0, encode_buf);
json = strbuf_string(encode_buf, &len);
lua_pushlstring(l, json, len);
if (!cfg->encode_keep_buffer)
strbuf_free(encode_buf);
return 1;
}
/* ===== DECODING ===== */
static void json_process_value(lua_State *l, json_parse_t *json,
json_token_t *token);
static int hexdigit2int(char hex)
{
if ('0' <= hex && hex <= '9')
return hex - '0';
/* Force lowercase */
hex |= 0x20;
if ('a' <= hex && hex <= 'f')
return 10 + hex - 'a';
return -1;
}
static int decode_hex4(const char *hex)
{
int digit[4];
int i;
/* Convert ASCII hex digit to numeric digit
* Note: this returns an error for invalid hex digits, including
* NULL */
for (i = 0; i < 4; i++) {
digit[i] = hexdigit2int(hex[i]);
if (digit[i] < 0) {
return -1;
}
}
return (digit[0] << 12) +
(digit[1] << 8) +
(digit[2] << 4) +
digit[3];
}
/* Converts a Unicode codepoint to UTF-8.
* Returns UTF-8 string length, and up to 4 bytes in *utf8 */
static int codepoint_to_utf8(char *utf8, int codepoint)
{
/* 0xxxxxxx */
if (codepoint <= 0x7F) {
utf8[0] = codepoint;
return 1;
}
/* 110xxxxx 10xxxxxx */
if (codepoint <= 0x7FF) {
utf8[0] = (codepoint >> 6) | 0xC0;
utf8[1] = (codepoint & 0x3F) | 0x80;
return 2;
}
/* 1110xxxx 10xxxxxx 10xxxxxx */
if (codepoint <= 0xFFFF) {
utf8[0] = (codepoint >> 12) | 0xE0;
utf8[1] = ((codepoint >> 6) & 0x3F) | 0x80;
utf8[2] = (codepoint & 0x3F) | 0x80;
return 3;
}
/* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
if (codepoint <= 0x1FFFFF) {
utf8[0] = (codepoint >> 18) | 0xF0;
utf8[1] = ((codepoint >> 12) & 0x3F) | 0x80;
utf8[2] = ((codepoint >> 6) & 0x3F) | 0x80;
utf8[3] = (codepoint & 0x3F) | 0x80;
return 4;
}
return 0;
}
/* Called when index pointing to beginning of UTF-16 code escape: \uXXXX
* \u is guaranteed to exist, but the remaining hex characters may be
* missing.
* Translate to UTF-8 and append to temporary token string.
* Must advance index to the next character to be processed.
* Returns: 0 success
* -1 error
*/
static int json_append_unicode_escape(json_parse_t *json)
{
char utf8[4]; /* Surrogate pairs require 4 UTF-8 bytes */
int codepoint;
int surrogate_low;
int len;
int escape_len = 6;
/* Fetch UTF-16 code unit */
codepoint = decode_hex4(json->ptr + 2);
if (codepoint < 0)
return -1;
/* UTF-16 surrogate pairs take the following 2 byte form:
* 11011 x yyyyyyyyyy
* When x = 0: y is the high 10 bits of the codepoint
* x = 1: y is the low 10 bits of the codepoint
*
* Check for a surrogate pair (high or low) */
if ((codepoint & 0xF800) == 0xD800) {
/* Error if the 1st surrogate is not high */
if (codepoint & 0x400)
return -1;
/* Ensure the next code is a unicode escape */
if (*(json->ptr + escape_len) != '\\' ||
*(json->ptr + escape_len + 1) != 'u') {
return -1;
}
/* Fetch the next codepoint */
surrogate_low = decode_hex4(json->ptr + 2 + escape_len);
if (surrogate_low < 0)
return -1;
/* Error if the 2nd code is not a low surrogate */
if ((surrogate_low & 0xFC00) != 0xDC00)
return -1;
/* Calculate Unicode codepoint */
codepoint = (codepoint & 0x3FF) << 10;
surrogate_low &= 0x3FF;
codepoint = (codepoint | surrogate_low) + 0x10000;
escape_len = 12;
}
/* Convert codepoint to UTF-8 */
len = codepoint_to_utf8(utf8, codepoint);
if (!len)
return -1;
/* Append bytes and advance parse index */
strbuf_append_mem_unsafe(json->tmp, utf8, len);
json->ptr += escape_len;
return 0;
}
static void json_set_token_error(json_token_t *token, json_parse_t *json,
const char *errtype)
{
token->type = T_ERROR;
token->index = json->ptr - json->data;
token->value.string = errtype;
}
static void json_next_string_token(json_parse_t *json, json_token_t *token)
{
// char *escape2char = json->cfg->escape2char;
char ch;
/* Caller must ensure a string is next */
if(!(*json->ptr == '"')) return;
/* Skip " */
json->ptr++;
/* json->tmp is the temporary strbuf used to accumulate the
* decoded string value.
* json->tmp is sized to handle JSON containing only a string value.
*/
strbuf_reset(json->tmp);
while ((ch = *json->ptr) != '"') {
if (!ch) {
/* Premature end of the string */
json_set_token_error(token, json, "unexpected end of string");
return;
}
/* Handle escapes */
if (ch == '\\') {
/* Fetch escape character */
ch = *(json->ptr + 1);
/* Translate escape code and append to tmp string */
ch = escape2char((unsigned char)ch);
if (ch == 'u') {
if (json_append_unicode_escape(json) == 0)
continue;
json_set_token_error(token, json,
"invalid unicode escape code");
return;
}
if (!ch) {
json_set_token_error(token, json, "invalid escape code");
return;
}
/* Skip '\' */
json->ptr++;
}
/* Append normal character or translated single character
* Unicode escapes are handled above */
strbuf_append_char_unsafe(json->tmp, ch);
json->ptr++;
}
json->ptr++; /* Eat final quote (") */
strbuf_ensure_null(json->tmp);
token->type = T_STRING;
token->value.string = strbuf_string(json->tmp, &token->string_len);
}
/* JSON numbers should take the following form:
* -?(0|[1-9]|[1-9][0-9]+)(.[0-9]+)?([eE][-+]?[0-9]+)?
*
* json_next_number_token() uses strtod() which allows other forms:
* - numbers starting with '+'
* - NaN, -NaN, infinity, -infinity
* - hexadecimal numbers
* - numbers with leading zeros
*
* json_is_invalid_number() detects "numbers" which may pass strtod()'s
* error checking, but should not be allowed with strict JSON.
*
* json_is_invalid_number() may pass numbers which cause strtod()
* to generate an error.
*/
static int json_is_invalid_number(json_parse_t *json)
{
const char *p = json->ptr;
/* Reject numbers starting with + */
if (*p == '+')
return 1;
/* Skip minus sign if it exists */
if (*p == '-')
p++;
/* Reject numbers starting with 0x, or leading zeros */
if (*p == '0') {
int ch2 = *(p + 1);
if ((ch2 | 0x20) == 'x' || /* Hex */
('0' <= ch2 && ch2 <= '9')) /* Leading zero */
return 1;
return 0;
} else if (*p <= '9') {
return 0; /* Ordinary number */
}
char tmp[4]; // conv to lower. because strncasecmp == strcmp
int i;
for (i = 0; i < 3; ++i)
{
if(p[i]!=0)
tmp[i] = tolower(p[i]);
else
tmp[i] = 0;
}
tmp[3] = 0;
/* Reject inf/nan */
if (!strncasecmp(tmp, "inf", 3))
return 1;
if (!strncasecmp(tmp, "nan", 3))
return 1;
/* Pass all other numbers which may still be invalid, but
* strtod() will catch them. */
return 0;
}
static void json_next_number_token(json_parse_t *json, json_token_t *token)
{
char *endptr;
token->type = T_NUMBER;
token->value.number = fpconv_strtod(json->ptr, &endptr);
if (json->ptr == endptr)
json_set_token_error(token, json, "invalid number");
else
json->ptr = endptr; /* Skip the processed number */
return;
}
/* Fills in the token struct.
* T_STRING will return a pointer to the json_parse_t temporary string
* T_ERROR will leave the json->ptr pointer at the error.
*/
static void json_next_token(json_parse_t *json, json_token_t *token)
{
// const json_token_type_t *ch2token = json->cfg->ch2token;
int ch;
/* Eat whitespace. */
while (1) {
ch = (unsigned char)*(json->ptr);
token->type = ch2token(ch);
if (token->type != T_WHITESPACE)
break;
json->ptr++;
}
/* Store location of new token. Required when throwing errors
* for unexpected tokens (syntax errors). */
token->index = json->ptr - json->data;
/* Don't advance the pointer for an error or the end */
if (token->type == T_ERROR) {
json_set_token_error(token, json, "invalid token");
return;
}
if (token->type == T_END) {
return;
}
/* Found a known single character token, advance index and return */
if (token->type != T_UNKNOWN) {
json->ptr++;
return;
}
/* Process characters which triggered T_UNKNOWN
*
* Must use strncmp() to match the front of the JSON string.
* JSON identifier must be lowercase.
* When strict_numbers if disabled, either case is allowed for
* Infinity/NaN (since we are no longer following the spec..) */
if (ch == '"') {
json_next_string_token(json, token);
return;
} else if (ch == '-' || ('0' <= ch && ch <= '9')) {
if (!json->cfg->decode_invalid_numbers && json_is_invalid_number(json)) {
json_set_token_error(token, json, "invalid number");
return;
}
json_next_number_token(json, token);
return;
} else if (!strncmp(json->ptr, "true", 4)) {
token->type = T_BOOLEAN;
token->value.boolean = 1;
json->ptr += 4;
return;
} else if (!strncmp(json->ptr, "false", 5)) {
token->type = T_BOOLEAN;
token->value.boolean = 0;
json->ptr += 5;
return;
} else if (!strncmp(json->ptr, "null", 4)) {
token->type = T_NULL;
json->ptr += 4;
return;
} else if (json->cfg->decode_invalid_numbers &&
json_is_invalid_number(json)) {
/* When decode_invalid_numbers is enabled, only attempt to process
* numbers we know are invalid JSON (Inf, NaN, hex)
* This is required to generate an appropriate token error,
* otherwise all bad tokens will register as "invalid number"
*/
json_next_number_token(json, token);
return;
}
/* Token starts with t/f/n but isn't recognised above. */
json_set_token_error(token, json, "invalid token");
}
/* This function does not return.
* DO NOT CALL WITH DYNAMIC MEMORY ALLOCATED.
* The only supported exception is the temporary parser string
* json->tmp struct.
* json and token should exist on the stack somewhere.
* luaL_error() will long_jmp and release the stack */
static void json_throw_parse_error(lua_State *l, json_parse_t *json,
const char *exp, json_token_t *token)
{
const char *found;
char temp[16]; // for now, 16-bytes is enough.
strbuf_free(json->tmp);
if (token->type == T_ERROR)
found = token->value.string;
else
{
found = json_token_type_name[token->type];
int i;
for (i=0; i < 16; ++i)
{
temp[i] = byte_of_aligned_array(found, i);
if(temp[i]==0) break;
}
found = temp;
}
/* Note: token->index is 0 based, display starting from 1 */
luaL_error(l, "Expected %s but found %s at character %d",
exp, found, token->index + 1);
}
static inline void json_decode_ascend(json_parse_t *json)
{
json->current_depth--;
}
static void json_decode_descend(lua_State *l, json_parse_t *json, int slots)
{
json->current_depth++;
if (json->current_depth <= json->cfg->decode_max_depth &&
lua_checkstack(l, slots)) {
return;
}
strbuf_free(json->tmp);
luaL_error(l, "Found too many nested data structures (%d) at character %d",
json->current_depth, json->ptr - json->data);
}
static void json_parse_object_context(lua_State *l, json_parse_t *json)
{
json_token_t token;
/* 3 slots required:
* .., table, key, value */
json_decode_descend(l, json, 3);
lua_newtable(l);
json_next_token(json, &token);
/* Handle empty objects */
if (token.type == T_OBJ_END) {
json_decode_ascend(json);
return;
}
while (1) {
if (token.type != T_STRING)
json_throw_parse_error(l, json, "object key string", &token);
/* Push key */
lua_pushlstring(l, token.value.string, token.string_len);
json_next_token(json, &token);
if (token.type != T_COLON)
json_throw_parse_error(l, json, "colon", &token);
/* Fetch value */
json_next_token(json, &token);
json_process_value(l, json, &token);
/* Set key = value */
lua_rawset(l, -3);
json_next_token(json, &token);
if (token.type == T_OBJ_END) {
json_decode_ascend(json);
return;
}
if (token.type != T_COMMA)
json_throw_parse_error(l, json, "comma or object end", &token);
json_next_token(json, &token);
}
}
/* Handle the array context */
static void json_parse_array_context(lua_State *l, json_parse_t *json)
{
json_token_t token;
int i;
/* 2 slots required:
* .., table, value */
json_decode_descend(l, json, 2);
lua_newtable(l);
json_next_token(json, &token);
/* Handle empty arrays */
if (token.type == T_ARR_END) {
json_decode_ascend(json);
return;
}
for (i = 1; ; i++) {
json_process_value(l, json, &token);
lua_rawseti(l, -2, i); /* arr[i] = value */
json_next_token(json, &token);
if (token.type == T_ARR_END) {
json_decode_ascend(json);
return;
}
if (token.type != T_COMMA)
json_throw_parse_error(l, json, "comma or array end", &token);
json_next_token(json, &token);
}
}
/* Handle the "value" context */
static void json_process_value(lua_State *l, json_parse_t *json,
json_token_t *token)
{
switch (token->type) {
case T_STRING:
lua_pushlstring(l, token->value.string, token->string_len);
break;;
case T_NUMBER:
lua_pushnumber(l, token->value.number);
break;;
case T_BOOLEAN:
lua_pushboolean(l, token->value.boolean);
break;;
case T_OBJ_BEGIN:
json_parse_object_context(l, json);
break;;
case T_ARR_BEGIN:
json_parse_array_context(l, json);
break;;
case T_NULL:
/* In Lua, setting "t[k] = nil" will delete k from the table.
* Hence a NULL pointer lightuserdata object is used instead */
lua_pushlightuserdata(l, NULL);
break;;
default:
json_throw_parse_error(l, json, "value", token);
}
}
static int json_decode(lua_State *l)
{
json_parse_t json;
json_token_t token;
size_t json_len;
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
json.cfg = json_fetch_config(l);
json.data = luaL_checklstring(l, 1, &json_len);
json.current_depth = 0;
json.ptr = json.data;
/* Detect Unicode other than UTF-8 (see RFC 4627, Sec 3)
*
* CJSON can support any simple data type, hence only the first
* character is guaranteed to be ASCII (at worst: '"'). This is
* still enough to detect whether the wrong encoding is in use. */
if (json_len >= 2 && (!json.data[0] || !json.data[1]))
luaL_error(l, "JSON parser does not support UTF-16 or UTF-32");
/* Ensure the temporary buffer can hold the entire string.
* This means we no longer need to do length checks since the decoded
* string must be smaller than the entire json string */
json.tmp = strbuf_new(json_len);
if(json.tmp == NULL){
return luaL_error(l, "not enough memory");
}
json_next_token(&json, &token);
json_process_value(l, &json, &token);
/* Ensure there is no more input left */
json_next_token(&json, &token);
if (token.type != T_END)
json_throw_parse_error(l, &json, "the end", &token);
strbuf_free(json.tmp);
return 1;
}
/* ===== INITIALISATION ===== */
#if 0
#if !defined(LUA_VERSION_NUM) || LUA_VERSION_NUM < 502
/* Compatibility for Lua 5.1.
*
* luaL_setfuncs() is used to create a module table where the functions have
* json_config_t as their first upvalue. Code borrowed from Lua 5.2 source. */
static void luaL_setfuncs (lua_State *l, const luaL_Reg *reg, int nup)
{
int i;
luaL_checkstack(l, nup, "too many upvalues");
for (; reg->name != NULL; reg++) { /* fill the table with given functions */
for (i = 0; i < nup; i++) /* copy upvalues to the top */
lua_pushvalue(l, -nup);
lua_pushcclosure(l, reg->func, nup); /* closure with those upvalues */
lua_setfield(l, -(nup + 2), reg->name);
}
lua_pop(l, nup); /* remove upvalues */
}
#endif
/* Call target function in protected mode with all supplied args.
* Assumes target function only returns a single non-nil value.
* Convert and return thrown errors as: nil, "error message" */
static int json_protect_conversion(lua_State *l)
{
int err;
/* Deliberately throw an error for invalid arguments */
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
/* pcall() the function stored as upvalue(1) */
lua_pushvalue(l, lua_upvalueindex(1));
lua_insert(l, 1);
err = lua_pcall(l, 1, 1, 0);
if (!err)
return 1;
if (err == LUA_ERRRUN) {
lua_pushnil(l);
lua_insert(l, -2);
return 2;
}
/* Since we are not using a custom error handler, the only remaining
* errors are memory related */
return luaL_error(l, "Memory allocation error in CJSON protected call");
}
/* Return cjson module table */
static int lua_cjson_new(lua_State *l)
{
/* Initialise number conversions */
fpconv_init();
/* cjson module table */
lua_newtable(l);
/* Register functions with config data as upvalue */
json_create_config(l);
luaL_setfuncs(l, reg, 1);
/* Set cjson.null */
lua_pushlightuserdata(l, NULL);
lua_setfield(l, -2, "null");
/* Set module name / version fields */
lua_pushliteral(l, CJSON_MODNAME);
lua_setfield(l, -2, "_NAME");
lua_pushliteral(l, CJSON_VERSION);
lua_setfield(l, -2, "_VERSION");
return 1;
}
/* Return cjson.safe module table */
static int lua_cjson_safe_new(lua_State *l)
{
const char *func[] = { "decode", "encode", NULL };
int i;
lua_cjson_new(l);
/* Fix new() method */
lua_pushcfunction(l, lua_cjson_safe_new);
lua_setfield(l, -2, "new");
for (i = 0; func[i]; i++) {
lua_getfield(l, -1, func[i]);
lua_pushcclosure(l, json_protect_conversion, 1);
lua_setfield(l, -2, func[i]);
}
return 1;
}
int luaopen_cjson(lua_State *l)
{
lua_cjson_new(l);
#ifdef ENABLE_CJSON_GLOBAL
/* Register a global "cjson" table. */
lua_pushvalue(l, -1);
lua_setglobal(l, CJSON_MODNAME);
#endif
/* Return cjson table */
return 1;
}
int luaopen_cjson_safe(lua_State *l)
{
lua_cjson_safe_new(l);
/* Return cjson.safe table */
return 1;
}
#endif
// Module function map
static const LUA_REG_TYPE cjson_map[] = {
{ LSTRKEY( "encode" ), LFUNCVAL( json_encode ) },
{ LSTRKEY( "decode" ), LFUNCVAL( json_decode ) },
//{ LSTRKEY( "encode_sparse_array" ), LFUNCVAL( json_cfg_encode_sparse_array ) },
//{ LSTRKEY( "encode_max_depth" ), LFUNCVAL( json_cfg_encode_max_depth ) },
//{ LSTRKEY( "decode_max_depth" ), LFUNCVAL( json_cfg_decode_max_depth ) },
//{ LSTRKEY( "encode_number_precision" ), LFUNCVAL( json_cfg_encode_number_precision ) },
//{ LSTRKEY( "encode_keep_buffer" ), LFUNCVAL( json_cfg_encode_keep_buffer ) },
//{ LSTRKEY( "encode_invalid_numbers" ), LFUNCVAL( json_cfg_encode_invalid_numbers ) },
//{ LSTRKEY( "decode_invalid_numbers" ), LFUNCVAL( json_cfg_decode_invalid_numbers ) },
//{ LSTRKEY( "new" ), LFUNCVAL( lua_cjson_new ) },
{ LNILKEY, LNILVAL }
};
int luaopen_cjson( lua_State *L )
{
cjson_mem_setlua (L);
/* Initialise number conversions */
// fpconv_init(); // not needed for a specific cpu.
if(-1==cfg_init(&_cfg)){
return luaL_error(L, "BUG: Unable to init config for cjson");;
}
return 0;
}
NODEMCU_MODULE(CJSON, "cjson", cjson_map, luaopen_cjson);
/* vi:ai et sw=4 ts=4:
*/
// Module for coapwork
// No espconn on ESP32
#ifdef __ESP8266__
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include <string.h>
#include <stdlib.h>
#include "c_types.h"
#include "mem.h"
#include "lwip/ip_addr.h"
#include "espconn.h"
#include "driver/uart.h"
#include "esp_misc.h"
#include "coap.h"
#include "uri.h"
#include "node.h"
#include "coap_timer.h"
#include "coap_io.h"
#include "coap_server.h"
coap_queue_t *gQueue = NULL;
typedef struct lcoap_userdata
{
lua_State *L;
struct espconn *pesp_conn;
int self_ref;
}lcoap_userdata;
static void coap_received(void *arg, char *pdata, unsigned short len)
{
NODE_DBG("coap_received is called.\n");
struct espconn *pesp_conn = arg;
lcoap_userdata *cud = (lcoap_userdata *)pesp_conn->reverse;
// static uint8_t buf[MAX_MESSAGE_SIZE+1] = {0}; // +1 for string '\0'
uint8_t buf[MAX_MESSAGE_SIZE+1] = {0}; // +1 for string '\0'
memset(buf, 0, sizeof(buf)); // wipe prev data
if (len > MAX_MESSAGE_SIZE) {
NODE_DBG("Request Entity Too Large.\n"); // NOTE: should response 4.13 to client...
return;
}
// memcpy(buf, pdata, len);
size_t rsplen = coap_server_respond(pdata, len, buf, MAX_MESSAGE_SIZE+1);
// SDK 1.4.0 changed behaviour, for UDP server need to look up remote ip/port
remot_info *pr = 0;
if (espconn_get_connection_info (pesp_conn, &pr, 0) != ESPCONN_OK)
return;
pesp_conn->proto.udp->remote_port = pr->remote_port;
memmove (pesp_conn->proto.udp->remote_ip, pr->remote_ip, 4);
// The remot_info apparently should *not* be free()d, fyi
espconn_sent(pesp_conn, (unsigned char *)buf, rsplen);
// memset(buf, 0, sizeof(buf));
}
static void coap_sent(void *arg)
{
NODE_DBG("coap_sent is called.\n");
}
// Lua: s = coap.create(function(conn))
static int coap_create( lua_State* L, const char* mt )
{
struct espconn *pesp_conn = NULL;
lcoap_userdata *cud;
unsigned type;
int stack = 1;
// create a object
cud = (lcoap_userdata *)lua_newuserdata(L, sizeof(lcoap_userdata));
// pre-initialize it, in case of errors
cud->self_ref = LUA_NOREF;
cud->pesp_conn = NULL;
// set its metatable
luaL_getmetatable(L, mt);
lua_setmetatable(L, -2);
// create the espconn struct
pesp_conn = (struct espconn *)zalloc(sizeof(struct espconn));
if(!pesp_conn)
return luaL_error(L, "not enough memory");
cud->pesp_conn = pesp_conn;
pesp_conn->type = ESPCONN_UDP;
pesp_conn->proto.tcp = NULL;
pesp_conn->proto.udp = NULL;
pesp_conn->proto.udp = (esp_udp *)zalloc(sizeof(esp_udp));
if(!pesp_conn->proto.udp){
free(pesp_conn);
cud->pesp_conn = pesp_conn = NULL;
return luaL_error(L, "not enough memory");
}
pesp_conn->state = ESPCONN_NONE;
NODE_DBG("UDP server/client is set.\n");
cud->L = L;
pesp_conn->reverse = cud;
NODE_DBG("coap_create is called.\n");
return 1;
}
// Lua: server:delete()
static int coap_delete( lua_State* L, const char* mt )
{
struct espconn *pesp_conn = NULL;
lcoap_userdata *cud;
cud = (lcoap_userdata *)luaL_checkudata(L, 1, mt);
luaL_argcheck(L, cud, 1, "Server/Client expected");
if(cud==NULL){
NODE_DBG("userdata is nil.\n");
return 0;
}
// free (unref) callback ref
if(LUA_NOREF!=cud->self_ref){
luaL_unref(L, LUA_REGISTRYINDEX, cud->self_ref);
cud->self_ref = LUA_NOREF;
}
cud->L = NULL;
if(cud->pesp_conn)
{
if(cud->pesp_conn->proto.udp->remote_port || cud->pesp_conn->proto.udp->local_port)
espconn_delete(cud->pesp_conn);
free(cud->pesp_conn->proto.udp);
cud->pesp_conn->proto.udp = NULL;
free(cud->pesp_conn);
cud->pesp_conn = NULL;
}
NODE_DBG("coap_delete is called.\n");
return 0;
}
// Lua: server:listen( port, ip )
static int coap_start( lua_State* L, const char* mt )
{
struct espconn *pesp_conn = NULL;
lcoap_userdata *cud;
unsigned port;
size_t il;
ip_addr_t ipaddr;
cud = (lcoap_userdata *)luaL_checkudata(L, 1, mt);
luaL_argcheck(L, cud, 1, "Server/Client expected");
if(cud==NULL){
NODE_DBG("userdata is nil.\n");
return 0;
}
pesp_conn = cud->pesp_conn;
port = luaL_checkinteger( L, 2 );
pesp_conn->proto.udp->local_port = port;
NODE_DBG("UDP port is set: %d.\n", port);
if( lua_isstring(L,3) ) // deal with the ip string
{
const char *ip = luaL_checklstring( L, 3, &il );
if (ip == NULL)
{
ip = "0.0.0.0";
}
ipaddr.addr = ipaddr_addr(ip);
memcpy(pesp_conn->proto.udp->local_ip, &ipaddr.addr, 4);
NODE_DBG("UDP ip is set: ");
NODE_DBG(IPSTR, IP2STR(&ipaddr.addr));
NODE_DBG("\n");
}
if(LUA_NOREF==cud->self_ref){
lua_pushvalue(L, 1); // copy to the top of stack
cud->self_ref = luaL_ref(L, LUA_REGISTRYINDEX);
}
espconn_regist_recvcb(pesp_conn, coap_received);
espconn_regist_sentcb(pesp_conn, coap_sent);
espconn_create(pesp_conn);
NODE_DBG("Coap Server started on port: %d\n", port);
NODE_DBG("coap_start is called.\n");
return 0;
}
// Lua: server:close()
static int coap_close( lua_State* L, const char* mt )
{
struct espconn *pesp_conn = NULL;
lcoap_userdata *cud;
cud = (lcoap_userdata *)luaL_checkudata(L, 1, mt);
luaL_argcheck(L, cud, 1, "Server/Client expected");
if(cud==NULL){
NODE_DBG("userdata is nil.\n");
return 0;
}
if(cud->pesp_conn)
{
if(cud->pesp_conn->proto.udp->remote_port || cud->pesp_conn->proto.udp->local_port)
espconn_delete(cud->pesp_conn);
}
if(LUA_NOREF!=cud->self_ref){
luaL_unref(L, LUA_REGISTRYINDEX, cud->self_ref);
cud->self_ref = LUA_NOREF;
}
NODE_DBG("coap_close is called.\n");
return 0;
}
// Lua: server/client:on( "method", function(s) )
static int coap_on( lua_State* L, const char* mt )
{
NODE_DBG("coap_on is called.\n");
return 0;
}
static void coap_response_handler(void *arg, char *pdata, unsigned short len)
{
NODE_DBG("coap_response_handler is called.\n");
struct espconn *pesp_conn = arg;
coap_packet_t pkt;
pkt.content.p = NULL;
pkt.content.len = 0;
// static uint8_t buf[MAX_MESSAGE_SIZE+1] = {0}; // +1 for string '\0'
uint8_t buf[MAX_MESSAGE_SIZE+1] = {0}; // +1 for string '\0'
memset(buf, 0, sizeof(buf)); // wipe prev data
int rc;
if( len > MAX_MESSAGE_SIZE )
{
NODE_DBG("Request Entity Too Large.\n"); // NOTE: should response 4.13 to client...
return;
}
memcpy(buf, pdata, len);
if (0 != (rc = coap_parse(&pkt, buf, len))){
NODE_DBG("Bad packet rc=%d\n", rc);
}
else
{
#ifdef COAP_DEBUG
coap_dumpPacket(&pkt);
#endif
/* check if this is a response to our original request */
if (!check_token(&pkt)) {
/* drop if this was just some message, or send RST in case of notification */
if (pkt.hdr.t == COAP_TYPE_CON || pkt.hdr.t == COAP_TYPE_NONCON){
// coap_send_rst(pkt); // send RST response
// or, just ignore it.
}
goto end;
}
if (pkt.hdr.t == COAP_TYPE_RESET) {
NODE_DBG("got RST\n");
goto end;
}
uint32_t ip = 0, port = 0;
coap_tid_t id = COAP_INVALID_TID;
memcpy(&ip, pesp_conn->proto.udp->remote_ip, sizeof(ip));
port = pesp_conn->proto.udp->remote_port;
coap_transaction_id(ip, port, &pkt, &id);
/* transaction done, remove the node from queue */
// stop timer
coap_timer_stop();
// remove the node
coap_remove_node(&gQueue, id);
// calculate time elapsed
coap_timer_update(&gQueue);
coap_timer_start(&gQueue);
if (COAP_RESPONSE_CLASS(pkt.hdr.code) == 2)
{
/* There is no block option set, just read the data and we are done. */
NODE_DBG("%d.%02d\t", (pkt.hdr.code >> 5), pkt.hdr.code & 0x1F);
NODE_DBG((char *)pkt.payload.p);
}
else if (COAP_RESPONSE_CLASS(pkt.hdr.code) >= 4)
{
NODE_DBG("%d.%02d\t", (pkt.hdr.code >> 5), pkt.hdr.code & 0x1F);
NODE_DBG((char *)pkt.payload.p);
}
}
end:
if(!gQueue){ // if there is no node pending in the queue, disconnect from host.
if(pesp_conn->proto.udp->remote_port || pesp_conn->proto.udp->local_port)
espconn_delete(pesp_conn);
}
// memset(buf, 0, sizeof(buf));
}
// Lua: client:request( [CON], uri, [payload] )
static int coap_request( lua_State* L, coap_method_t m )
{
struct espconn *pesp_conn = NULL;
lcoap_userdata *cud;
int stack = 1;
cud = (lcoap_userdata *)luaL_checkudata(L, stack, "coap_client");
luaL_argcheck(L, cud, stack, "Server/Client expected");
if(cud==NULL){
NODE_DBG("userdata is nil.\n");
return 0;
}
stack++;
pesp_conn = cud->pesp_conn;
ip_addr_t ipaddr;
uint8_t host[64];
unsigned t;
if ( lua_isnumber(L, stack) )
{
t = lua_tointeger(L, stack);
stack++;
if ( t != COAP_TYPE_CON && t != COAP_TYPE_NONCON )
return luaL_error( L, "wrong arg type" );
} else {
t = COAP_TYPE_CON; // default to CON
}
size_t l;
const char *url = luaL_checklstring( L, stack, &l );
stack++;
if (url == NULL)
return luaL_error( L, "wrong arg type" );
coap_uri_t *uri = coap_new_uri(url, l); // should call free(uri) somewhere
if (uri == NULL)
return luaL_error( L, "uri wrong format." );
if (uri->host.length + 1 /* for the null */ > sizeof(host)) {
return luaL_error(L, "host too long");
}
pesp_conn->proto.udp->remote_port = uri->port;
NODE_DBG("UDP port is set: %d.\n", uri->port);
pesp_conn->proto.udp->local_port = espconn_port();
if(uri->host.length){
memcpy(host, uri->host.s, uri->host.length);
host[uri->host.length] = '\0';
ipaddr.addr = ipaddr_addr(host);
NODE_DBG("Host len(%d):", uri->host.length);
NODE_DBG(host);
NODE_DBG("\n");
memcpy(pesp_conn->proto.udp->remote_ip, &ipaddr.addr, 4);
NODE_DBG("UDP ip is set: ");
NODE_DBG(IPSTR, IP2STR(&ipaddr.addr));
NODE_DBG("\n");
}
coap_pdu_t *pdu = coap_new_pdu(); // should call coap_delete_pdu() somewhere
if(!pdu){
if(uri)
free(uri);
return luaL_error (L, "alloc fail");
}
const char *payload = NULL;
l = 0;
if( lua_isstring(L, stack) ){
payload = luaL_checklstring( L, stack, &l );
if (payload == NULL)
l = 0;
}
coap_make_request(&(pdu->scratch), pdu->pkt, t, m, uri, payload, l);
#ifdef COAP_DEBUG
coap_dumpPacket(pdu->pkt);
#endif
int rc;
if (0 != (rc = coap_build(pdu->msg.p, &(pdu->msg.len), pdu->pkt))){
NODE_DBG("coap_build failed rc=%d\n", rc);
}
else
{
#ifdef COAP_DEBUG
NODE_DBG("Sending: ");
coap_dump(pdu->msg.p, pdu->msg.len, true);
NODE_DBG("\n");
#endif
espconn_regist_recvcb(pesp_conn, coap_response_handler);
sint8_t con = espconn_create(pesp_conn);
if( ESPCONN_OK != con){
NODE_DBG("Connect to host. code:%d\n", con);
// coap_delete_pdu(pdu);
}
// else
{
coap_tid_t tid = COAP_INVALID_TID;
if (pdu->pkt->hdr.t == COAP_TYPE_CON){
tid = coap_send_confirmed(pesp_conn, pdu);
}
else {
tid = coap_send(pesp_conn, pdu);
}
if (pdu->pkt->hdr.t != COAP_TYPE_CON || tid == COAP_INVALID_TID){
coap_delete_pdu(pdu);
}
}
}
if(uri)
free((void *)uri);
NODE_DBG("coap_request is called.\n");
return 0;
}
extern coap_luser_entry *variable_entry;
extern coap_luser_entry *function_entry;
// Lua: coap:var/func( string )
static int coap_regist( lua_State* L, const char* mt, int isvar )
{
size_t l;
const char *name = luaL_checklstring( L, 2, &l );
int content_type = luaL_optint(L, 3, COAP_CONTENTTYPE_TEXT_PLAIN);
if (name == NULL)
return luaL_error( L, "name must be set." );
coap_luser_entry *h = NULL;
// if(lua_isstring(L, 3))
if(isvar)
h = variable_entry;
else
h = function_entry;
while(NULL!=h->next){ // goto the end of the list
if(h->name!= NULL && strcmp(h->name, name)==0) // key exist, override it
break;
h = h->next;
}
if(h->name==NULL || strcmp(h->name, name)!=0){ // not exists. make a new one.
h->next = (coap_luser_entry *)zalloc(sizeof(coap_luser_entry));
h = h->next;
if(h == NULL)
return luaL_error(L, "not enough memory");
h->next = NULL;
h->name = NULL;
h->L = NULL;
}
h->L = L;
h->name = name;
h->content_type = content_type;
NODE_DBG("coap_regist is called.\n");
return 0;
}
// Lua: s = coap.createServer(function(conn))
static int coap_createServer( lua_State* L )
{
const char *mt = "coap_server";
return coap_create(L, mt);
}
// Lua: server:delete()
static int coap_server_delete( lua_State* L )
{
const char *mt = "coap_server";
return coap_delete(L, mt);
}
// Lua: server:listen( port, ip, function(err) )
static int coap_server_listen( lua_State* L )
{
const char *mt = "coap_server";
return coap_start(L, mt);
}
// Lua: server:close()
static int coap_server_close( lua_State* L )
{
const char *mt = "coap_server";
return coap_close(L, mt);
}
// Lua: server:on( "method", function(server) )
static int coap_server_on( lua_State* L )
{
const char *mt = "coap_server";
return coap_on(L, mt);
}
// Lua: server:var( "name" )
static int coap_server_var( lua_State* L )
{
const char *mt = "coap_server";
return coap_regist(L, mt, 1);
}
// Lua: server:func( "name" )
static int coap_server_func( lua_State* L )
{
const char *mt = "coap_server";
return coap_regist(L, mt, 0);
}
// Lua: s = coap.createClient(function(conn))
static int coap_createClient( lua_State* L )
{
const char *mt = "coap_client";
return coap_create(L, mt);
}
// Lua: client:gcdelete()
static int coap_client_gcdelete( lua_State* L )
{
const char *mt = "coap_client";
return coap_delete(L, mt);
}
// client:get( string, function(sent) )
static int coap_client_get( lua_State* L )
{
return coap_request(L, COAP_METHOD_GET);
}
// client:post( string, function(sent) )
static int coap_client_post( lua_State* L )
{
return coap_request(L, COAP_METHOD_POST);
}
// client:put( string, function(sent) )
static int coap_client_put( lua_State* L )
{
return coap_request(L, COAP_METHOD_PUT);
}
// client:delete( string, function(sent) )
static int coap_client_delete( lua_State* L )
{
return coap_request(L, COAP_METHOD_DELETE);
}
// Module function map
static const LUA_REG_TYPE coap_server_map[] = {
{ LSTRKEY( "listen" ), LFUNCVAL( coap_server_listen ) },
{ LSTRKEY( "close" ), LFUNCVAL( coap_server_close ) },
{ LSTRKEY( "var" ), LFUNCVAL( coap_server_var ) },
{ LSTRKEY( "func" ), LFUNCVAL( coap_server_func ) },
{ LSTRKEY( "__gc" ), LFUNCVAL( coap_server_delete ) },
{ LSTRKEY( "__index" ), LROVAL( coap_server_map ) },
{ LNILKEY, LNILVAL }
};
static const LUA_REG_TYPE coap_client_map[] = {
{ LSTRKEY( "get" ), LFUNCVAL( coap_client_get ) },
{ LSTRKEY( "post" ), LFUNCVAL( coap_client_post ) },
{ LSTRKEY( "put" ), LFUNCVAL( coap_client_put ) },
{ LSTRKEY( "delete" ), LFUNCVAL( coap_client_delete ) },
{ LSTRKEY( "__gc" ), LFUNCVAL( coap_client_gcdelete ) },
{ LSTRKEY( "__index" ), LROVAL( coap_client_map ) },
{ LNILKEY, LNILVAL }
};
static const LUA_REG_TYPE coap_map[] =
{
{ LSTRKEY( "Server" ), LFUNCVAL( coap_createServer ) },
{ LSTRKEY( "Client" ), LFUNCVAL( coap_createClient ) },
{ LSTRKEY( "CON" ), LNUMVAL( COAP_TYPE_CON ) },
{ LSTRKEY( "NON" ), LNUMVAL( COAP_TYPE_NONCON ) },
{ LSTRKEY( "TEXT_PLAIN"), LNUMVAL( COAP_CONTENTTYPE_TEXT_PLAIN ) },
{ LSTRKEY( "LINKFORMAT"), LNUMVAL( COAP_CONTENTTYPE_APPLICATION_LINKFORMAT ) },
{ LSTRKEY( "XML"), LNUMVAL( COAP_CONTENTTYPE_APPLICATION_XML ) },
{ LSTRKEY( "OCTET_STREAM"), LNUMVAL( COAP_CONTENTTYPE_APPLICATION_OCTET_STREAM ) },
{ LSTRKEY( "EXI"), LNUMVAL( COAP_CONTENTTYPE_APPLICATION_EXI ) },
{ LSTRKEY( "JSON"), LNUMVAL( COAP_CONTENTTYPE_APPLICATION_JSON) },
{ LSTRKEY( "__metatable" ), LROVAL( coap_map ) },
{ LNILKEY, LNILVAL }
};
int luaopen_coap( lua_State *L )
{
endpoint_setup();
luaL_rometatable(L, "coap_server", (void *)coap_server_map); // create metatable for coap_server
luaL_rometatable(L, "coap_client", (void *)coap_client_map); // create metatable for coap_client
return 0;
}
NODEMCU_MODULE(COAP, "coap", coap_map, luaopen_coap);
#endif
// Module for cryptography
#include <errno.h>
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include "c_types.h"
#include "esp_libc.h"
#include <stdlib.h>
#include "flash_fs.h"
#include "../crypto/digests.h"
#include "../crypto/mech.h"
#include "user_interface.h"
#include "rom.h"
/**
* hash = crypto.sha1(input)
*
* Calculates raw SHA1 hash of input string.
* Input is arbitrary string, output is raw 20-byte hash as string.
*/
static int crypto_sha1( lua_State* L )
{
SHA1_CTX ctx;
uint8_t digest[20];
// Read the string from lua (with length)
int len;
const char* msg = luaL_checklstring(L, 1, &len);
// Use the SHA* functions in the rom
SHA1Init(&ctx);
SHA1Update(&ctx, msg, len);
SHA1Final(digest, &ctx);
// Push the result as a lua string
lua_pushlstring(L, digest, 20);
return 1;
}
#ifdef LUA_USE_MODULES_ENCODER
static int call_encoder( lua_State* L, const char *function ) {
if (lua_gettop(L) != 1) {
luaL_error(L, "%s must have one argument", function);
}
lua_getfield(L, LUA_GLOBALSINDEX, "encoder");
if (!lua_istable(L, -1) && !lua_isrotable(L, -1)) { // also need table just in case encoder has been overloaded
luaL_error(L, "Cannot find encoder.%s", function);
}
lua_getfield(L, -1, function);
lua_insert(L, 1); //move function below the argument
lua_pop(L, 1); //and dump the encoder rotable from stack.
lua_call(L,1,1); // call encoder.xxx(string)
return 1;
}
static int crypto_base64_encode (lua_State* L) {
return call_encoder(L, "toBase64");
}
static int crypto_hex_encode (lua_State* L) {
return call_encoder(L, "toHex");
}
#else
static const char* bytes64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/**
* encoded = crypto.toBase64(raw)
*
* Encodes raw binary string as base64 string.
*/
static int crypto_base64_encode( lua_State* L )
{
int len;
const char* msg = luaL_checklstring(L, 1, &len);
int blen = (len + 2) / 3 * 4;
char* out = (char*)malloc(blen);
int j = 0, i;
for (i = 0; i < len; i += 3) {
int a = msg[i];
int b = (i + 1 < len) ? msg[i + 1] : 0;
int c = (i + 2 < len) ? msg[i + 2] : 0;
out[j++] = bytes64[a >> 2];
out[j++] = bytes64[((a & 3) << 4) | (b >> 4)];
out[j++] = (i + 1 < len) ? bytes64[((b & 15) << 2) | (c >> 6)] : 61;
out[j++] = (i + 2 < len) ? bytes64[(c & 63)] : 61;
}
lua_pushlstring(L, out, j);
free(out);
return 1;
}
/**
* encoded = crypto.toHex(raw)
*
* Encodes raw binary string as hex string.
*/
static int crypto_hex_encode( lua_State* L)
{
int len;
const char* msg = luaL_checklstring(L, 1, &len);
char* out = (char*)malloc(len * 2);
int i, j = 0;
for (i = 0; i < len; i++) {
out[j++] = crypto_hexbytes[msg[i] >> 4];
out[j++] = crypto_hexbytes[msg[i] & 0xf];
}
lua_pushlstring(L, out, len*2);
free(out);
return 1;
}
#endif
/**
* masked = crypto.mask(message, mask)
*
* Apply a mask (repeated if shorter than message) as XOR to each byte.
*/
static int crypto_mask( lua_State* L )
{
int len, mask_len;
const char* msg = luaL_checklstring(L, 1, &len);
const char* mask = luaL_checklstring(L, 2, &mask_len);
int i;
char* copy = (char*)malloc(len);
for (i = 0; i < len; i++) {
copy[i] = msg[i] ^ mask[i % 4];
}
lua_pushlstring(L, copy, len);
free(copy);
return 1;
}
static inline int bad_mech (lua_State *L) { return luaL_error (L, "unknown hash mech"); }
static inline int bad_mem (lua_State *L) { return luaL_error (L, "insufficient memory"); }
static inline int bad_file (lua_State *L) { return luaL_error (L, "file does not exist"); }
/* rawdigest = crypto.hash("MD5", str)
* strdigest = crypto.toHex(rawdigest)
*/
static int crypto_lhash (lua_State *L)
{
const digest_mech_info_t *mi = crypto_digest_mech (luaL_checkstring (L, 1));
if (!mi)
return bad_mech (L);
size_t len = 0;
const char *data = luaL_checklstring (L, 2, &len);
uint8_t digest[mi->digest_size];
if (crypto_hash (mi, data, len, digest) != 0)
return bad_mem (L);
lua_pushlstring (L, digest, sizeof (digest));
return 1;
}
typedef struct digest_user_datum_t {
const digest_mech_info_t *mech_info;
void *ctx;
} digest_user_datum_t;
/* General Usage for extensible hash functions:
* sha = crypto.new_hash("MD5")
* sha.update("Data")
* sha.update("Data2")
* strdigest = crypto.toHex(sha.finalize())
*/
/* crypto.new_hash("MECHTYPE") */
static int crypto_new_hash (lua_State *L)
{
const digest_mech_info_t *mi = crypto_digest_mech (luaL_checkstring (L, 1));
if (!mi)
return bad_mech (L);
void *ctx = malloc (mi->ctx_size);
if (ctx==NULL)
return bad_mem (L);
mi->create (ctx);
// create a userdataum with specific metatable
digest_user_datum_t *dudat = (digest_user_datum_t *)lua_newuserdata(L, sizeof(digest_user_datum_t));
luaL_getmetatable(L, "crypto.hash");
lua_setmetatable(L, -2);
// Set pointers to the mechanics and CTX
dudat->mech_info = mi;
dudat->ctx = ctx;
return 1; // Pass userdata object back
}
/* Called as object, params:
1 - userdata "this"
2 - new string to add to the hash state */
static int crypto_hash_update (lua_State *L)
{
NODE_DBG("enter crypto_hash_update.\n");
digest_user_datum_t *dudat;
size_t sl;
dudat = (digest_user_datum_t *)luaL_checkudata(L, 1, "crypto.hash");
luaL_argcheck(L, dudat, 1, "crypto.hash expected");
const digest_mech_info_t *mi = dudat->mech_info;
size_t len = 0;
const char *data = luaL_checklstring (L, 2, &len);
mi->update (dudat->ctx, data, len);
return 0; // No return value
}
/* Called as object, no params. Returns digest of default size. */
static int crypto_hash_finalize (lua_State *L)
{
NODE_DBG("enter crypto_hash_update.\n");
digest_user_datum_t *dudat;
size_t sl;
dudat = (digest_user_datum_t *)luaL_checkudata(L, 1, "crypto.hash");
luaL_argcheck(L, dudat, 1, "crypto.hash expected");
const digest_mech_info_t *mi = dudat->mech_info;
uint8_t digest[mi->digest_size]; // Allocate as local
mi->finalize (digest, dudat->ctx);
lua_pushlstring (L, digest, sizeof (digest));
return 1;
}
/* Frees memory for the user datum and CTX hash state */
static int crypto_hash_gcdelete (lua_State *L)
{
NODE_DBG("enter crypto_hash_delete.\n");
digest_user_datum_t *dudat;
dudat = (digest_user_datum_t *)luaL_checkudata(L, 1, "crypto.hash");
luaL_argcheck(L, dudat, 1, "crypto.hash expected");
free(dudat->ctx);
return 0;
}
/* rawdigest = crypto.hash("MD5", filename)
* strdigest = crypto.toHex(rawdigest)
*/
static int crypto_flhash (lua_State *L)
{
const digest_mech_info_t *mi = crypto_digest_mech (luaL_checkstring (L, 1));
if (!mi)
return bad_mech (L);
const char *filename = luaL_checkstring (L, 2);
// Open the file
int file_fd = fs_open (filename, FS_RDONLY);
if(file_fd < FS_OPEN_OK) {
return bad_file(L);
}
// Compute hash
uint8_t digest[mi->digest_size];
int returncode = crypto_fhash (mi, &fs_read, file_fd, digest);
// Finish up
fs_close(file_fd);
if (returncode == ENOMEM)
return bad_mem (L);
else if (returncode == EINVAL)
return bad_mech(L);
else
lua_pushlstring (L, digest, sizeof (digest));
return 1;
}
/* rawsignature = crypto.hmac("SHA1", str, key)
* strsignature = crypto.toHex(rawsignature)
*/
static int crypto_lhmac (lua_State *L)
{
const digest_mech_info_t *mi = crypto_digest_mech (luaL_checkstring (L, 1));
if (!mi)
return bad_mech (L);
size_t len = 0;
const char *data = luaL_checklstring (L, 2, &len);
size_t klen = 0;
const char *key = luaL_checklstring (L, 3, &klen);
uint8_t digest[mi->digest_size];
if (crypto_hmac (mi, data, len, key, klen, digest) != 0)
return bad_mem (L);
lua_pushlstring (L, digest, sizeof (digest));
return 1;
}
static const crypto_mech_t *get_mech (lua_State *L, int idx)
{
const char *name = luaL_checkstring (L, idx);
const crypto_mech_t *mech = crypto_encryption_mech (name);
if (mech)
return mech;
luaL_error (L, "unknown cipher: %s", name);
__builtin_unreachable ();
}
static int crypto_encdec (lua_State *L, bool enc)
{
const crypto_mech_t *mech = get_mech (L, 1);
size_t klen;
const char *key = luaL_checklstring (L, 2, &klen);
size_t dlen;
const char *data = luaL_checklstring (L, 3, &dlen);
size_t ivlen;
const char *iv = luaL_optlstring (L, 4, "", &ivlen);
size_t bs = mech->block_size;
size_t outlen = ((dlen + bs -1) / bs) * bs;
char *buf = (char *)zalloc (outlen);
if (!buf)
return luaL_error (L, "crypto init failed");
crypto_op_t op =
{
key, klen,
iv, ivlen,
data, dlen,
buf, outlen,
enc ? OP_ENCRYPT : OP_DECRYPT
};
if (!mech->run (&op))
{
free (buf);
return luaL_error (L, "crypto op failed");
}
else
{
lua_pushlstring (L, buf, outlen);
// note: if lua_pushlstring runs out of memory, we leak buf :(
free (buf);
return 1;
}
}
static int lcrypto_encrypt (lua_State *L)
{
return crypto_encdec (L, true);
}
static int lcrypto_decrypt (lua_State *L)
{
return crypto_encdec (L, false);
}
// Hash function map
static const LUA_REG_TYPE crypto_hash_map[] = {
{ LSTRKEY( "update" ), LFUNCVAL( crypto_hash_update ) },
{ LSTRKEY( "finalize" ), LFUNCVAL( crypto_hash_finalize ) },
{ LSTRKEY( "__gc" ), LFUNCVAL( crypto_hash_gcdelete ) },
{ LSTRKEY( "__index" ), LROVAL( crypto_hash_map ) },
{ LNILKEY, LNILVAL }
};
// Module function map
static const LUA_REG_TYPE crypto_map[] = {
{ LSTRKEY( "sha1" ), LFUNCVAL( crypto_sha1 ) },
{ LSTRKEY( "toBase64" ), LFUNCVAL( crypto_base64_encode ) },
{ LSTRKEY( "toHex" ), LFUNCVAL( crypto_hex_encode ) },
{ LSTRKEY( "mask" ), LFUNCVAL( crypto_mask ) },
{ LSTRKEY( "hash" ), LFUNCVAL( crypto_lhash ) },
{ LSTRKEY( "fhash" ), LFUNCVAL( crypto_flhash ) },
{ LSTRKEY( "new_hash" ), LFUNCVAL( crypto_new_hash ) },
{ LSTRKEY( "hmac" ), LFUNCVAL( crypto_lhmac ) },
{ LSTRKEY( "encrypt" ), LFUNCVAL( lcrypto_encrypt ) },
{ LSTRKEY( "decrypt" ), LFUNCVAL( lcrypto_decrypt ) },
{ LNILKEY, LNILVAL }
};
int luaopen_crypto ( lua_State *L )
{
luaL_rometatable(L, "crypto.hash", (void *)crypto_hash_map); // create metatable for crypto.hash
return 0;
}
NODEMCU_MODULE(CRYPTO, "crypto", crypto_map, luaopen_crypto);
// Module for interfacing with the DHTxx sensors (xx = 11-21-22-33-44).
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include "dht.h"
#define NUM_DHT GPIO_PIN_NUM
// ****************************************************************************
// DHT functions
int platform_dht_exists( unsigned id )
{
return ((id < NUM_DHT) && (id > 0));
}
// Lua: status, temp, humi, tempdec, humidec = dht.read( id )
static int dht_lapi_read( lua_State *L )
{
unsigned id = luaL_checkinteger( L, 1 );
MOD_CHECK_ID( dht, id );
lua_pushinteger( L, dht_read_universal(id) );
double temp = dht_getTemperature();
double humi = dht_getHumidity();
int tempdec = (int)((temp - (int)temp) * 1000);
int humidec = (int)((humi - (int)humi) * 1000);
lua_pushnumber( L, temp );
lua_pushnumber( L, humi );
lua_pushnumber( L, tempdec );
lua_pushnumber( L, humidec );
return 5;
}
// Lua: status, temp, humi, tempdec, humidec = dht.read11( id ))
static int dht_lapi_read11( lua_State *L )
{
unsigned id = luaL_checkinteger( L, 1 );
MOD_CHECK_ID( dht, id );
lua_pushinteger( L, dht_read11(id) );
double temp = dht_getTemperature();
double humi = dht_getHumidity();
int tempdec = (int)((temp - (int)temp) * 1000);
int humidec = (int)((humi - (int)humi) * 1000);
lua_pushnumber( L, temp );
lua_pushnumber( L, humi );
lua_pushnumber( L, tempdec );
lua_pushnumber( L, humidec );
return 5;
}
// Lua: status, temp, humi, tempdec, humidec = dht.readxx( id ))
static int dht_lapi_readxx( lua_State *L )
{
unsigned id = luaL_checkinteger( L, 1 );
MOD_CHECK_ID( dht, id );
lua_pushinteger( L, dht_read(id) );
double temp = dht_getTemperature();
double humi = dht_getHumidity();
int tempdec = (int)((temp - (int)temp) * 1000);
int humidec = (int)((humi - (int)humi) * 1000);
lua_pushnumber( L, temp );
lua_pushnumber( L, humi );
lua_pushnumber( L, tempdec );
lua_pushnumber( L, humidec );
return 5;
}
// // Lua: result = dht.humidity()
// static int dht_lapi_humidity( lua_State *L )
// {
// lua_pushnumber( L, dht_getHumidity() );
// return 1;
// }
// // Lua: result = dht.humiditydecimal()
// static int dht_lapi_humiditydecimal( lua_State *L )
// {
// double value = dht_getHumidity();
// int result = (int)((value - (int)value) * 1000);
// lua_pushnumber( L, result );
// return 1;
// }
// // Lua: result = dht.temperature()
// static int dht_lapi_temperature( lua_State *L )
// {
// lua_pushnumber( L, dht_getTemperature() );
// return 1;
// }
// // Lua: result = dht.temperaturedecimal()
// static int dht_lapi_temperaturedecimal( lua_State *L )
// {
// double value = dht_getTemperature();
// int result = (int)((value - (int)value) * 1000);
// lua_pushnumber( L, result );
// return 1;
// }
// Module function map
static const LUA_REG_TYPE dht_map[] = {
{ LSTRKEY( "read" ), LFUNCVAL( dht_lapi_read ) },
{ LSTRKEY( "read11" ), LFUNCVAL( dht_lapi_read11 ) },
{ LSTRKEY( "readxx" ), LFUNCVAL( dht_lapi_readxx ) },
{ LSTRKEY( "OK" ), LNUMVAL( DHTLIB_OK ) },
{ LSTRKEY( "ERROR_CHECKSUM" ), LNUMVAL( DHTLIB_ERROR_CHECKSUM ) },
{ LSTRKEY( "ERROR_TIMEOUT" ), LNUMVAL( DHTLIB_ERROR_TIMEOUT ) },
{ LNILKEY, LNILVAL }
};
NODEMCU_MODULE(DHT, "dht", dht_map, NULL);
// Module encoders
#include "module.h"
#include "lauxlib.h"
#include "lmem.h"
#include <string.h>
#include <stdint.h>
#define BASE64_INVALID '\xff'
#define BASE64_PADDING '='
#define ISBASE64(c) (unbytes64[c] != BASE64_INVALID)
static const uint8 b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static uint8 *toBase64 ( lua_State* L, const uint8 *msg, size_t *len){
size_t i, n = *len;
if (!n) // handle empty string case
return NULL;
uint8 * q, *out = (uint8 *)luaM_malloc(L, (n + 2) / 3 * 4);
uint8 bytes64[sizeof(b64)];
memcpy(bytes64, b64, sizeof(b64)); //Avoid lots of flash unaligned fetches
for (i = 0, q = out; i < n; i += 3) {
int a = msg[i];
int b = (i + 1 < n) ? msg[i + 1] : 0;
int c = (i + 2 < n) ? msg[i + 2] : 0;
*q++ = bytes64[a >> 2];
*q++ = bytes64[((a & 3) << 4) | (b >> 4)];
*q++ = (i + 1 < n) ? bytes64[((b & 15) << 2) | (c >> 6)] : BASE64_PADDING;
*q++ = (i + 2 < n) ? bytes64[(c & 63)] : BASE64_PADDING;
}
*len = q - out;
return out;
}
static uint8 *fromBase64 ( lua_State* L, const uint8 *enc_msg, size_t *len){
int i, n = *len, blocks = (n>>2), pad = 0;
const uint8 *p;
uint8 unbytes64[UCHAR_MAX+1], *msg, *q;
if (!n) // handle empty string case
return NULL;
if (n & 3)
luaL_error (L, "Invalid base64 string");
memset(unbytes64, BASE64_INVALID, sizeof(unbytes64));
for (i = 0; i < sizeof(b64)-1; i++) unbytes64[b64[i]] = i; // sequential so no exceptions
if (enc_msg[n-1] == BASE64_PADDING) {
pad = (enc_msg[n-2] != BASE64_PADDING) ? 1 : 2;
blocks--; //exclude padding block
}
for (i = 0; i < n - pad; i++) if (!ISBASE64(enc_msg[i])) luaL_error (L, "Invalid base64 string");
unbytes64[BASE64_PADDING] = 0;
msg = q = (uint8 *) luaM_malloc(L, 1+ (3 * n / 4));
for (i = 0, p = enc_msg; i<blocks; i++) {
uint8 a = unbytes64[*p++];
uint8 b = unbytes64[*p++];
uint8 c = unbytes64[*p++];
uint8 d = unbytes64[*p++];
*q++ = (a << 2) | (b >> 4);
*q++ = (b << 4) | (c >> 2);
*q++ = (c << 6) | d;
}
if (pad) { //now process padding block bytes
uint8 a = unbytes64[*p++];
uint8 b = unbytes64[*p++];
*q++ = (a << 2) | (b >> 4);
if (pad == 1) *q++ = (b << 4) | (unbytes64[*p] >> 2);
}
*len = q - msg;
return msg;
}
static inline uint8 to_hex_nibble(uint8 b) {
return b + ( b < 10 ? '0' : 'a' - 10 );
}
static uint8 *toHex ( lua_State* L, const uint8 *msg, size_t *len){
int i, n = *len;
uint8 *q, *out = (uint8 *)luaM_malloc(L, n * 2);
for (i = 0, q = out; i < n; i++) {
*q++ = to_hex_nibble(msg[i] >> 4);
*q++ = to_hex_nibble(msg[i] & 0xf);
}
*len = 2*n;
return out;
}
static uint8 *fromHex ( lua_State* L, const uint8 *msg, size_t *len){
int i, n = *len;
const uint8 *p;
uint8 b, *q, *out = (uint8 *)luaM_malloc(L, n * 2);
uint8 c;
if (n &1)
luaL_error (L, "Invalid hex string");
for (i = 0, p = msg, q = out; i < n; i++) {
if (*p >= '0' && *p <= '9') {
b = *p++ - '0';
} else if (*p >= 'a' && *p <= 'f') {
b = *p++ - ('a' - 10);
} else if (*p >= 'A' && *p <= 'F') {
b = *p++ - ('A' - 10);
} else {
luaL_error (L, "Invalid hex string");
}
if ((i&1) == 0) {
c = b<<4;
} else {
*q++ = c+ b;
}
}
*len = n>>1;
return out;
}
// All encoder functions are of the form:
// Lua: output_string = encoder.function(input_string)
// Where input string maybe empty, but not nil
// Hence these all call the do_func wrapper
static int do_func (lua_State *L, uint8 * (*conv_func)(lua_State *, const uint8 *, size_t *)) {
size_t l;
const uint8 *input = luaL_checklstring(L, 1, &l);
// luaL_argcheck(L, l>0, 1, "input string empty");
uint8 *output = conv_func(L, input, &l);
if (output) {
lua_pushlstring(L, output, l);
luaM_free(L, output);
} else {
lua_pushstring(L, "");
}
return 1;
}
#define DECLARE_FUNCTION(f) static int encoder_ ## f (lua_State *L) \
{ return do_func(L, f); }
DECLARE_FUNCTION(fromBase64);
DECLARE_FUNCTION(toBase64);
DECLARE_FUNCTION(fromHex);
DECLARE_FUNCTION(toHex);
// Module function map
static const LUA_REG_TYPE encoder_map[] = {
{ LSTRKEY("fromBase64"), LFUNCVAL(encoder_fromBase64) },
{ LSTRKEY("toBase64"), LFUNCVAL(encoder_toBase64) },
{ LSTRKEY("fromHex"), LFUNCVAL(encoder_fromHex) },
{ LSTRKEY("toHex"), LFUNCVAL(encoder_toHex) },
{ LNILKEY, LNILVAL }
};
NODEMCU_MODULE(ENCODER, "encoder", encoder_map, NULL);
/*
* Copyright 2015 Robert Foss. 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 Robert Foss <dev@robertfoss.se>
*
* Additions & fixes: Johny Mattsson <jmattsson@dius.com.au>
*/
// No espconn on ESP32
#ifdef __ESP8266__
#include "module.h"
#include "lauxlib.h"
#include "lwip/tcp.h"
#include "lwip/pbuf.h"
#include "lmem.h"
#include "platform.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "ctype.h"
#include "espconn.h"
#include "flash_fs.h"
#include "task/task.h"
#include "esp_wifi.h"
#include "esp_sta.h"
#include "esp_softap.h"
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#define LITLEN(strliteral) (sizeof (strliteral) -1)
#define ENDUSER_SETUP_ERR_FATAL (1 << 0)
#define ENDUSER_SETUP_ERR_NONFATAL (1 << 1)
#define ENDUSER_SETUP_ERR_NO_RETURN (1 << 2)
#define ENDUSER_SETUP_ERR_OUT_OF_MEMORY 1
#define ENDUSER_SETUP_ERR_CONNECTION_NOT_FOUND 2
#define ENDUSER_SETUP_ERR_UNKOWN_ERROR 3
#define ENDUSER_SETUP_ERR_SOCKET_ALREADY_OPEN 4
/**
* DNS Response Packet:
*
* |DNS ID - 16 bits|
* |dns_header|
* |QNAME|
* |dns_body|
* |ip - 32 bits|
*
* DNS Header Part | FLAGS | | Q COUNT | | A CNT | |AUTH CNT| | ADD CNT| */
static const char dns_header[] = { 0x80, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 };
/* DNS Query Part | Q TYPE | | Q CLASS| */
static const char dns_body[] = { 0x00, 0x01, 0x00, 0x01,
/* DNS Answer Part |LBL OFFS| | TYPE | | CLASS | | TTL | | RD LEN | */
0xC0, 0x0C, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x04 };
static const char http_html_filename[] = "enduser_setup.html";
static const char http_header_200[] = "HTTP/1.1 200 OK\r\nCache-control:no-cache\r\nContent-Type: text/html\r\n"; /* Note single \r\n here! */
static const char http_header_204[] = "HTTP/1.1 204 No Content\r\n\r\n";
static const char http_header_302[] = "HTTP/1.1 302 Moved\r\nLocation: /\r\n\r\n";
static const char http_header_401[] = "HTTP/1.1 401 Bad request\r\n\r\n";
static const char http_header_404[] = "HTTP/1.1 404 Not found\r\n\r\n";
static const char http_header_500[] = "HTTP/1.1 500 Internal Error\r\n\r\n";
/* The below is the un-minified version of the http_html_backup[] string.
* Minified using https://kangax.github.io/html-minifier/
* Note: using method="get" due to iOS not always sending body in same
* packet as the HTTP header, and us thus missing it in that case
*/
#if 0
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<meta name="viewport" content="width=380">
<title>WiFi Login</title>
<style media="screen" type="text/css">
*{margin:0;padding:0}
html{height:100%;background:linear-gradient(rgba(196,102,0,.2),rgba(155,89,182,.2)),url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAAA8AgMAAACm+SSwAAAADFBMVEVBR1FFS1VHTlg8Q0zU/YXIAAADVElEQVQ4yy1TTYvTUBQ9GTKiYNoodsCF4MK6U4TZChOhiguFWHyBFzqlLl4hoeNvEBeCrlrhBVKq1EUKLTP+hvi1GyguXqBdiZCBzGqg20K8L3hDQnK55+OeJNguHx6UujYl3dL5ALn4JOIUluAqeAWciyGaSdvngOWzNT+G0UyGUOxVOAdqkjXDCbBiUyjZ5QzYEbGadYAi6kHxth+kthXNVNCDofwhGv1D4QGGiM9iAjbCHgr2iUUpDJbs+VPQ4xAr2fX7KXbkOJMdok965Ksb+6lrjdkem8AshIuHm9Nyu19uTunYlOXDTQqi8VgeH0kBXH2xq/ouiMZPzuMukymutrBmulUTovC6HqNFW2ZOiqlpSXZOTvSUeUPxChjxol8BLbRy4gJuhV7OR4LRVBs3WQ9VVAU7SXgK2HeUrOj7bC8YsUgr3lEV/TXB7hK90EBnxaeg1Ov15bY80M736ekCGesGAaGvG0Ct4WRkVQVHIgIM9xJgvSFfPay8Q6GNv7VpR7xUnkvhnMQCJDYkYOtNLihV70tCU1Sk+BQrpoP+HLHUrJkuta40C6LP5GvBv+Hqo10ATxxFrTPvNdPr7XwgQud6RvQN/sXjBGzqbU27wcj9cgsyvSTrpyXV8gKpXeNJU3aFl7MOdldzV4+HfO19jBa5f2IjWwx1OLHIvFHkqbBj20ro1g7nDfY1DpScvDRUNARgjMMVO0zoMjKxJ6uWCPP+YRAWbGoaN8kXYHmLjB9FXLGOazfFVCvOgqzfnicNPrHtPKlex2ye824gMza0cTZ2sS2Xm7Qst/UfFw8O6vVtmUKxZy9xFgzMys5cJ5fxZw4y37Ufk1Dsfb8MqOjYxE3ZMWxiDcO0PYUaD2ys+8OW1pbB7/e3sfZeGVCL0Q2aMjjPdm2sxADuejZxHJAd8dO9DSUdA0V8/NggRRanDkBrANn8yHlEQOn/MmwoQfQF7xgmKDnv520bS/pgylP67vf3y2V5sCwfoCEMkZClgOfJAFX9eXefR2RpnmRs4CDVPceaRfoFzCkJVJX27vWZnoqyvmtXU3+dW1EIXIu8Qg5Qta4Zlv7drUCoWe8/8MXzaEwux7ESE9h6qnHj3mIO0/D9RvzfxPmjWiQ1vbeSk4rrHwhAre35EEVaAAAAAElFTkSuQmCC)}
body{font-family:arial,verdana}
div{position:absolute;margin:auto;top:-150px;right:0;bottom:0;left:0;width:320px;height:304px}
form{width:320px;text-align:center;position:relative}
form fieldset{background:#fff;border:0 none;border-radius:5px;box-shadow:0 0 15px 1px rgba(0,0,0,.4);padding:20px 30px;box-sizing:border-box}
form input{padding:15px;border:1px solid #ccc;border-radius:3px;margin-bottom:10px;width:100%;box-sizing:border-box;font-family:montserrat;color:#2C3E50;font-size:13px}
form .action-button{border:0 none;border-radius:3px;cursor:pointer;}
#msform .submit:focus,form .action-button:hover{box-shadow:0 0 0 2px #fff,0 0 0 3px #27AE60;}
#formFrame{display: none;}
#aplist{display: block;}
select{width:100%;margin-bottom: 20px;padding: 10px 5px; border:1px solid #ccc;display:none;}
.fs-title{font-size:15px;text-transform:uppercase;color:#2C3E50;margin-bottom:10px}
.fs-subtitle{font-weight:400;font-size:13px;color:#666;margin-bottom:20px}
.fs-status{font-weight:400;font-size:13px;color:#666;margin-bottom:10px;padding-top:20px; border-top:1px solid #ccc}
.submit{width:100px;background: #27AE60; color: #fff;font-weight:700;margin:10px 5px; padding: 10px 5px; }
</style>
</head>
<body>
<div>
<form id="credentialsForm" method="get" action="/update" target="formFrame">
<fieldset>
<iframe id="formFrame" src="" name="formFrame"></iframe> <!-- Used to submit data, needed to prevent re-direction after submission -->
<h2 class="fs-title">WiFi Login</h2>
<h3 class="fs-subtitle">Connect gadget to your WiFi network</h3>
<input id="wifi_ssid" autocorrect="off" autocapitalize="none" name="wifi_ssid" placeholder="WiFi Name">
<select id="aplist" name="aplist" size="1" disabled>
<option>Scanning for networks...</option>
</select>
<input name="wifi_password" placeholder="Password" type="password">
<input type=submit name=save class="action-button submit" value="Save">
<h3 class="fs-status">Status: <span id="status">Updating...</span></h3>
</fieldset>
<h3 id="dbg"></h3>
</form>
</div>
<script>
function fetch(url, method, callback)
{
var xhr = new XMLHttpRequest();
xhr.onreadystatechange=check_ready;
function check_ready()
{
if (xhr.readyState === 4)
{
callback(xhr.status === 200 ? xhr.responseText : null);
}
}
xhr.open(method, url, true);
xhr.send();
}
function new_status(stat)
{
if (stat)
{
var e = document.getElementById("status");
e.innerHTML = stat;
}
}
function new_status_repeat(stat)
{
new_status(stat);
setTimeout(refresh_status, 750);
}
function new_ap_list(json)
{
if (json)
{
var list = JSON.parse(json);
list.sort(function(a, b){ return b.rssi - a.rssi; });
var ssids = list.map(function(a) { return a.ssid; }).filter(function(item, pos, self) { return self.indexOf(item)==pos; });
var sel = document.getElementById("aplist");
sel.innerHTML = "";
sel.setAttribute("size", Math.max(Math.min(3, list.length), 1));
sel.removeAttribute("disabled");
for (var i = 0; i < ssids.length; ++i)
{
var o = document.createElement("option");
o.innerHTML = ssids[i];
sel.options.add(o);
}
sel.style.display = 'block';
}
}
function new_ap_list_repeat(json)
{
new_ap_list(json);
setTimeout(refresh_ap_list, 3000);
}
function refresh_status()
{
fetch('/status','GET', new_status_repeat);
}
function refresh_ap_list()
{
fetch('/aplist','GET', new_ap_list_repeat);
}
function set_ssid_field() {
var sel = document.getElementById("aplist");
document.getElementById("wifi_ssid").value = sel.value;
}
window.onload = function()
{
refresh_status();
refresh_ap_list();
document.getElementById("aplist").onclick = set_ssid_field;
document.getElementById("aplist").onchange = set_ssid_field;
document.getElementById("credentialsForm").addEventListener("submit", function(){
fetch('/status','GET', new_status);
});
}
</script>
</body>
</html>
#endif
static const char http_html_backup[] =
"<!DOCTYPE html><meta http-equiv=content-type content='text/html; charset=UTF-8'><meta charset=utf-8><meta name=viewport content='width=380'><title>WiFi Login</title><style media=screen type=text/css>*{margin:0;padding:0}html{height:100%;background:linear-gradient(rgba(196,102,0,.2),rgba(155,89,182,.2)),url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEYAAAA8AgMAAACm+SSwAAAADFBMVEVBR1FFS1VHTlg8Q0zU/YXIAAADVElEQVQ4yy1TTYvTUBQ9GTKiYNoodsCF4MK6U4TZChOhiguFWHyBFzqlLl4hoeNvEBeCrlrhBVKq1EUKLTP+hvi1GyguXqBdiZCBzGqg20K8L3hDQnK55+OeJNguHx6UujYl3dL5ALn4JOIUluAqeAWciyGaSdvngOWzNT+G0UyGUOxVOAdqkjXDCbBiUyjZ5QzYEbGadYAi6kHxth+kthXNVNCDofwhGv1D4QGGiM9iAjbCHgr2iUUpDJbs+VPQ4xAr2fX7KXbkOJMdok965Ksb+6lrjdkem8AshIuHm9Nyu19uTunYlOXDTQqi8VgeH0kBXH2xq/ouiMZPzuMukymutrBmulUTovC6HqNFW2ZOiqlpSXZOTvSUeUPxChjxol8BLbRy4gJuhV7OR4LRVBs3WQ9VVAU7SXgK2HeUrOj7bC8YsUgr3lEV/TXB7hK90EBnxaeg1Ov15bY80M736ekCGesGAaGvG0Ct4WRkVQVHIgIM9xJgvSFfPay8Q6GNv7VpR7xUnkvhnMQCJDYkYOtNLihV70tCU1Sk+BQrpoP+HLHUrJkuta40C6LP5GvBv+Hqo10ATxxFrTPvNdPr7XwgQud6RvQN/sXjBGzqbU27wcj9cgsyvSTrpyXV8gKpXeNJU3aFl7MOdldzV4+HfO19jBa5f2IjWwx1OLHIvFHkqbBj20ro1g7nDfY1DpScvDRUNARgjMMVO0zoMjKxJ6uWCPP+YRAWbGoaN8kXYHmLjB9FXLGOazfFVCvOgqzfnicNPrHtPKlex2ye824gMza0cTZ2sS2Xm7Qst/UfFw8O6vVtmUKxZy9xFgzMys5cJ5fxZw4y37Ufk1Dsfb8MqOjYxE3ZMWxiDcO0PYUaD2ys+8OW1pbB7/e3sfZeGVCL0Q2aMjjPdm2sxADuejZxHJAd8dO9DSUdA0V8/NggRRanDkBrANn8yHlEQOn/MmwoQfQF7xgmKDnv520bS/pgylP67vf3y2V5sCwfoCEMkZClgOfJAFX9eXefR2RpnmRs4CDVPceaRfoFzCkJVJX27vWZnoqyvmtXU3+dW1EIXIu8Qg5Qta4Zlv7drUCoWe8/8MXzaEwux7ESE9h6qnHj3mIO0/D9RvzfxPmjWiQ1vbeSk4rrHwhAre35EEVaAAAAAElFTkSuQmCC)}body{font-family:arial,verdana}div{position:absolute;margin:auto;top:-150px;right:0;bottom:0;left:0;width:320px;height:304px}form{width:320px;text-align:center;position:relative}form fieldset{background:#fff;border:0 none;border-radius:5px;box-shadow:0 0 15px 1px rgba(0,0,0,.4);padding:20px 30px;box-sizing:border-box}form input{padding:15px;border:1px solid #ccc;border-radius:3px;margin-bottom:10px;width:100%;box-sizing:border-box;font-family:montserrat;color:#2C3E50;font-size:13px}form .action-button{border:0 none;border-radius:3px;cursor:pointer}#msform .submit:focus,form .action-button:hover{box-shadow:0 0 0 2px #fff,0 0 0 3px #27AE60}#formFrame{display:none}#aplist{display:block}select{width:100%;margin-bottom:20px;padding:10px 5px;border:1px solid #ccc;display:none}.fs-title{font-size:15px;text-transform:uppercase;color:#2C3E50;margin-bottom:10px}.fs-subtitle{font-weight:400;font-size:13px;color:#666;margin-bottom:20px}.fs-status{font-weight:400;font-size:13px;color:#666;margin-bottom:10px;padding-top:20px;border-top:1px solid #ccc}.submit{width:100px;background:#27AE60;color:#fff;font-weight:700;margin:10px 5px;padding:10px 5px}</style><div><form id=credentialsForm action=/update target=formFrame><fieldset><iframe id=formFrame src=''name=formFrame></iframe><h2 class=fs-title>WiFi Login</h2><h3 class=fs-subtitle>Connect gadget to your WiFi network</h3><input id=wifi_ssid autocorrect=off autocapitalize=none name=wifi_ssid placeholder='WiFi Name'><select id=aplist name=aplist size=1 disabled><option>Scanning for networks...</select><input name=wifi_password placeholder=Password type=password> <input type=submit name=save class='action-button submit'value=Save><h3 class=fs-status>Status: <span id=status>Updating...</span></h3></fieldset><h3 id=dbg></h3></form></div><script>function fetch(t,e,n){function s(){4===i.readyState&&n(200===i.status?i.responseText:null)}var i=new XMLHttpRequest;i.onreadystatechange=s,i.open(e,t,!0),i.send()}function new_status(t){if(t){var e=document.getElementById('status');e.innerHTML=t}}function new_status_repeat(t){new_status(t),setTimeout(refresh_status,750)}function new_ap_list(t){if(t){var e=JSON.parse(t);e.sort(function(t,e){return e.rssi-t.rssi});var n=e.map(function(t){return t.ssid}).filter(function(t,e,n){return n.indexOf(t)==e}),s=document.getElementById('aplist');s.innerHTML='',s.setAttribute('size',Math.max(Math.min(3,e.length),1)),s.removeAttribute('disabled');for(var i=0;i<n.length;++i){var a=document.createElement('option');a.innerHTML=n[i],s.options.add(a)}s.style.display='block'}}function new_ap_list_repeat(t){new_ap_list(t),setTimeout(refresh_ap_list,3e3)}function refresh_status(){fetch('/status','GET',new_status_repeat)}function refresh_ap_list(){fetch('/aplist','GET',new_ap_list_repeat)}function set_ssid_field(){var t=document.getElementById('aplist');document.getElementById('wifi_ssid').value=t.value}window.onload=function(){refresh_status(),refresh_ap_list(),document.getElementById('aplist').onclick=set_ssid_field,document.getElementById('aplist').onchange=set_ssid_field,document.getElementById('credentialsForm').addEventListener('submit',function(){fetch('/status','GET',new_status)})}</script>";
typedef struct scan_listener
{
struct tcp_pcb *conn;
struct scan_listener *next;
} scan_listener_t;
typedef struct
{
struct espconn *espconn_dns_udp;
struct tcp_pcb *http_pcb;
char *http_payload_data;
uint32_t http_payload_len;
os_timer_t check_station_timer;
os_timer_t shutdown_timer;
int lua_connected_cb_ref;
int lua_err_cb_ref;
int lua_dbg_cb_ref;
scan_listener_t *scan_listeners;
} enduser_setup_state_t;
static enduser_setup_state_t *state;
static bool manual = false;
static task_handle_t do_station_cfg_handle;
static int enduser_setup_manual(lua_State* L);
static int enduser_setup_start(lua_State* L);
static int enduser_setup_stop(lua_State* L);
static void enduser_setup_stop_callback(void *ptr);
static void enduser_setup_station_start(void);
static void enduser_setup_ap_start(void);
static void enduser_setup_ap_stop(void);
static void enduser_setup_check_station(void *p);
static void enduser_setup_debug(int line, const char *str);
#define ENDUSER_SETUP_DEBUG_ENABLE 0
#if ENDUSER_SETUP_DEBUG_ENABLE
#define ENDUSER_SETUP_DEBUG(str) enduser_setup_debug(__LINE__, str)
#else
#define ENDUSER_SETUP_DEBUG(str) do {} while(0)
#endif
#define ENDUSER_SETUP_ERROR(str, err, err_severity) \
do { \
ENDUSER_SETUP_DEBUG(str); \
if (err_severity & ENDUSER_SETUP_ERR_FATAL) enduser_setup_stop(lua_getstate());\
enduser_setup_error(__LINE__, str, err);\
if (!(err_severity & ENDUSER_SETUP_ERR_NO_RETURN)) \
return err; \
} while (0)
#define ENDUSER_SETUP_ERROR_VOID(str, err, err_severity) \
do { \
ENDUSER_SETUP_DEBUG(str); \
if (err_severity & ENDUSER_SETUP_ERR_FATAL) enduser_setup_stop(lua_getstate());\
enduser_setup_error(__LINE__, str, err);\
if (!(err_severity & ENDUSER_SETUP_ERR_NO_RETURN)) \
return; \
} while (0)
static void enduser_setup_debug(int line, const char *str)
{
lua_State *L = lua_getstate();
if(state != NULL && state->lua_dbg_cb_ref != LUA_NOREF)
{
lua_rawgeti(L, LUA_REGISTRYINDEX, state->lua_dbg_cb_ref);
lua_pushfstring(L, "%d: \t%s", line, str);
lua_call(L, 1, 0);
}
}
static void enduser_setup_error(int line, const char *str, int err)
{
ENDUSER_SETUP_DEBUG("enduser_setup_error");
lua_State *L = lua_getstate();
if (state != NULL && state->lua_err_cb_ref != LUA_NOREF)
{
lua_rawgeti (L, LUA_REGISTRYINDEX, state->lua_err_cb_ref);
lua_pushnumber(L, err);
lua_pushfstring(L, "%d: \t%s", line, str);
lua_call (L, 2, 0);
}
}
static void enduser_setup_connected_callback()
{
ENDUSER_SETUP_DEBUG("enduser_setup_connected_callback");
lua_State *L = lua_getstate();
if (state != NULL && state->lua_connected_cb_ref != LUA_NOREF)
{
lua_rawgeti(L, LUA_REGISTRYINDEX, state->lua_connected_cb_ref);
lua_call(L, 0, 0);
}
}
static void enduser_setup_check_station_start(void)
{
ENDUSER_SETUP_DEBUG("enduser_setup_check_station_start");
os_timer_setfn(&(state->check_station_timer), enduser_setup_check_station, NULL);
os_timer_arm(&(state->check_station_timer), 1*1000, TRUE);
}
static void enduser_setup_check_station_stop(void)
{
ENDUSER_SETUP_DEBUG("enduser_setup_check_station_stop");
if (state != NULL)
{
os_timer_disarm(&(state->check_station_timer));
}
}
/**
* Check Station
*
* Check that we've successfully entered station mode.
*/
static void enduser_setup_check_station(void *p)
{
ENDUSER_SETUP_DEBUG("enduser_setup_check_station");
(void)p;
struct ip_info ip;
memset(&ip, 0, sizeof(struct ip_info));
wifi_get_ip_info(STATION_IF, &ip);
int i;
char has_ip = 0;
for (i = 0; i < sizeof(struct ip_info); ++i)
{
has_ip |= ((char *) &ip)[i];
}
if (has_ip == 0)
{
return;
}
enduser_setup_check_station_stop();
enduser_setup_connected_callback();
/* Trigger shutdown, but allow time for HTTP client to fetch last status. */
if (!manual)
{
os_timer_setfn(&(state->shutdown_timer), enduser_setup_stop_callback, NULL);
os_timer_arm(&(state->shutdown_timer), 10*1000, FALSE);
}
}
/* --- Connection closing handling ----------------------------------------- */
/* It is far more memory efficient to let the other end close the connection
* first and respond to that, than us initiating the closing. The latter
* seems to leave the pcb in a fin_wait state for a long time, which can
* starve us of memory over time.
*
* By instead using the poll function to schedule a hard abort a few seconds
* from now we achieve a deadline close. The downside is a (very) slight
* risk of dropping the connection early, but in this application that's
* hidden by the retries on the JavaScript side anyway.
*/
/* Callback on timeout to hard-close a connection */
static err_t force_abort (void *arg, struct tcp_pcb *pcb)
{
(void)arg;
tcp_poll (pcb, 0, 0);
tcp_abort (pcb);
return ERR_ABRT;
}
/* Callback to detect a remote-close of a connection */
static err_t handle_remote_close (void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
{
(void)arg; (void)err;
if (p) /* server sent us data, just ACK and move on */
{
tcp_recved (pcb, p->tot_len);
pbuf_free (p);
}
else /* hey, remote end closed, we can do a soft close safely, yay! */
{
tcp_recv (pcb, 0);
tcp_poll (pcb, 0, 0);
tcp_close (pcb);
}
return ERR_OK;
}
/* Set up a deferred close of a connection, as discussed above. */
static inline void deferred_close (struct tcp_pcb *pcb)
{
tcp_poll (pcb, force_abort, 15); /* ~3sec from now */
tcp_recv (pcb, handle_remote_close);
tcp_sent (pcb, 0);
}
/* Convenience function to queue up a close-after-send. */
static err_t close_once_sent (void *arg, struct tcp_pcb *pcb, u16_t len)
{
(void)arg; (void)len;
deferred_close (pcb);
return ERR_OK;
}
/* ------------------------------------------------------------------------- */
/**
* Search String
*
* Search string for first occurance of any char in srch_str.
*
* @return -1 iff no occurance of char was found.
*/
static int enduser_setup_srch_str(const char *str, const char *srch_str)
{
char *found = strpbrk (str, srch_str);
if (!found)
{
return -1;
}
else
{
return found - str;
}
}
/**
* Load HTTP Payload
*
* @return - 0 iff payload loaded successfully
* 1 iff backup html was loaded
* 2 iff out of memory
*/
static int enduser_setup_http_load_payload(void)
{
ENDUSER_SETUP_DEBUG("enduser_setup_http_load_payload");
int f = fs_open(http_html_filename, fs_mode2flag("r"));
int err = fs_seek(f, 0, FS_SEEK_END);
int file_len = (int) fs_tell(f);
int err2 = fs_seek(f, 0, FS_SEEK_SET);
const char cl_hdr[] = "Content-length:%5d\r\n\r\n";
const size_t cl_len = LITLEN(cl_hdr) + 3; /* room to expand %4d */
if (f == 0 || err == -1 || err2 == -1)
{
ENDUSER_SETUP_DEBUG("enduser_setup_http_load_payload unable to load file enduser_setup.html, loading backup HTML.");
int payload_len = LITLEN(http_header_200) + cl_len + LITLEN(http_html_backup);
state->http_payload_len = payload_len;
state->http_payload_data = (char *) malloc(payload_len);
if (state->http_payload_data == NULL)
{
return 2;
}
int offset = 0;
memcpy(&(state->http_payload_data[offset]), &(http_header_200), LITLEN(http_header_200));
offset += LITLEN(http_header_200);
offset += sprintf(state->http_payload_data + offset, cl_hdr, LITLEN(http_html_backup));
memcpy(&(state->http_payload_data[offset]), &(http_html_backup), LITLEN(http_html_backup));
return 1;
}
int payload_len = LITLEN(http_header_200) + cl_len + file_len;
state->http_payload_len = payload_len;
state->http_payload_data = (char *) malloc(payload_len);
if (state->http_payload_data == NULL)
{
return 2;
}
int offset = 0;
memcpy(&(state->http_payload_data[offset]), &(http_header_200), LITLEN(http_header_200));
offset += LITLEN(http_header_200);
offset += sprintf(state->http_payload_data + offset, cl_hdr, file_len);
fs_read(f, &(state->http_payload_data[offset]), file_len);
return 0;
}
/**
* De-escape URL data
*
* Parse escaped and form encoded data of request.
*
* @return - return 0 iff the HTTP parameter is decoded into a valid string.
*/
static int enduser_setup_http_urldecode(char *dst, const char *src, int src_len, int dst_len)
{
ENDUSER_SETUP_DEBUG("enduser_setup_http_urldecode");
char *dst_start = dst;
char *dst_last = dst + dst_len - 1; /* -1 to reserve space for last \0 */
char a, b;
int i;
for (i = 0; i < src_len && *src && dst < dst_last; ++i)
{
if ((*src == '%') && ((a = src[1]) && (b = src[2])) && (isxdigit(a) && isxdigit(b)))
{
if (a >= 'a')
{
a -= 'a'-'A';
}
if (a >= 'A')
{
a -= ('A' - 10);
}
else
{
a -= '0';
}
if (b >= 'a')
{
b -= 'a'-'A';
}
if (b >= 'A')
{
b -= ('A' - 10);
}
else
{
b -= '0';
}
*dst++ = 16 * a + b;
src += 3;
i += 2;
} else {
char c = *src++;
if (c == '+')
{
c = ' ';
}
*dst++ = c;
}
}
*dst++ = '\0';
return (i < src_len); /* did we fail to process all the input? */
}
/**
* Task to do the actual station configuration.
* This config *cannot* be done in the network receive callback or serious
* issues like memory corruption occur.
*/
static void do_station_cfg (task_param_t param, task_prio_t prio)
{
struct station_config *cnf = (struct station_config *)param;
(void)prio;
/* Best-effort disconnect-reconfig-reconnect. If the device is currently
* connected, the disconnect will work but the connect will report failure
* (though it will actually start connecting). If the devices is not
* connected, the disconnect may fail but the connect will succeed. A
* solid head-in-the-sand approach seems to be the best tradeoff on
* functionality-vs-code-size.
* TODO: maybe use an error callback to at least report if the set config
* call fails.
*/
wifi_station_disconnect ();
wifi_station_set_config (cnf);
wifi_station_connect ();
luaM_free(lua_getstate(), cnf);
}
/**
* Handle HTTP Credentials
*
* @return - return 0 iff credentials are found and handled successfully
* return 1 iff credentials aren't found
* return 2 iff an error occured
*/
static int enduser_setup_http_handle_credentials(char *data, unsigned short data_len)
{
ENDUSER_SETUP_DEBUG("enduser_setup_http_handle_credentials");
char *name_str = (char *) ((uint32_t)strstr(&(data[6]), "wifi_ssid="));
char *pwd_str = (char *) ((uint32_t)strstr(&(data[6]), "wifi_password="));
if (name_str == NULL || pwd_str == NULL)
{
ENDUSER_SETUP_DEBUG("Password or SSID string not found");
return 1;
}
int name_field_len = LITLEN("wifi_ssid=");
int pwd_field_len = LITLEN("wifi_password=");
char *name_str_start = name_str + name_field_len;
char *pwd_str_start = pwd_str + pwd_field_len;
int name_str_len = enduser_setup_srch_str(name_str_start, "& ");
int pwd_str_len = enduser_setup_srch_str(pwd_str_start, "& ");
if (name_str_len == -1 || pwd_str_len == -1)
{
ENDUSER_SETUP_DEBUG("Password or SSID HTTP paramter divider not found");
return 1;
}
struct station_config *cnf = luaM_malloc(lua_getstate(), sizeof(struct station_config));
memset(cnf, 0, sizeof(struct station_config));
int err;
err = enduser_setup_http_urldecode(cnf->ssid, name_str_start, name_str_len, sizeof(cnf->ssid));
err |= enduser_setup_http_urldecode(cnf->password, pwd_str_start, pwd_str_len, sizeof(cnf->password));
if (err != 0)
{
ENDUSER_SETUP_DEBUG("Unable to decode HTTP parameter to valid password or SSID");
return 1;
}
ENDUSER_SETUP_DEBUG("");
ENDUSER_SETUP_DEBUG("WiFi Credentials Stored");
ENDUSER_SETUP_DEBUG("-----------------------");
ENDUSER_SETUP_DEBUG("name: ");
ENDUSER_SETUP_DEBUG(cnf->ssid);
ENDUSER_SETUP_DEBUG("pass: ");
ENDUSER_SETUP_DEBUG(cnf->password);
ENDUSER_SETUP_DEBUG("-----------------------");
ENDUSER_SETUP_DEBUG("");
task_post_medium(do_station_cfg_handle, (task_param_t) cnf);
return 0;
}
/**
* Serve HTML
*
* @return - return 0 iff html was served successfully
*/
static int enduser_setup_http_serve_header(struct tcp_pcb *http_client, const char *header, uint32_t header_len)
{
ENDUSER_SETUP_DEBUG("enduser_setup_http_serve_header");
err_t err = tcp_write (http_client, header, header_len, TCP_WRITE_FLAG_COPY);
if (err != ERR_OK)
{
deferred_close (http_client);
ENDUSER_SETUP_ERROR("http_serve_header failed on tcp_write", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_NONFATAL);
}
return 0;
}
static err_t streamout_sent (void *arg, struct tcp_pcb *pcb, u16_t len)
{
(void)len;
unsigned offs = (unsigned)arg;
if (!state || !state->http_payload_data)
{
tcp_abort (pcb);
return ERR_ABRT;
}
unsigned wanted_len = state->http_payload_len - offs;
unsigned buf_free = tcp_sndbuf (pcb);
if (buf_free < wanted_len)
wanted_len = buf_free;
/* no-copy write */
err_t err = tcp_write (pcb, state->http_payload_data + offs, wanted_len, 0);
if (err != ERR_OK)
{
ENDUSER_SETUP_DEBUG("streaming out html failed");
tcp_abort (pcb);
return ERR_ABRT;
}
offs += wanted_len;
if (offs >= state->http_payload_len)
{
tcp_sent (pcb, 0);
deferred_close (pcb);
}
else
tcp_arg (pcb, (void *)offs);
return ERR_OK;
}
/**
* Serve HTML
*
* @return - return 0 iff html was served successfully
*/
static int enduser_setup_http_serve_html(struct tcp_pcb *http_client)
{
ENDUSER_SETUP_DEBUG("enduser_setup_http_serve_html");
if (state->http_payload_data == NULL)
{
enduser_setup_http_load_payload();
}
unsigned chunklen = tcp_sndbuf (http_client);
tcp_arg (http_client, (void *)chunklen);
tcp_recv (http_client, 0); /* avoid confusion about the tcp_arg */
tcp_sent (http_client, streamout_sent);
/* Begin the no-copy stream-out here */
err_t err = tcp_write (http_client, state->http_payload_data, chunklen, 0);
if (err != 0)
{
ENDUSER_SETUP_ERROR("http_serve_html failed. tcp_write failed", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_NONFATAL);
}
return 0;
}
static void serve_status(struct tcp_pcb *conn)
{
ENDUSER_SETUP_DEBUG("enduser_setup_serve_status");
const char fmt[] =
"HTTP/1.1 200 OK\r\n"
"Cache-control:no-cache\r\n"
"Connection:close\r\n"
"Content-type:text/plain\r\n"
"Content-length: %d\r\n"
"\r\n"
"%s%s";
const char *state[] =
{
"Idle.",
"Connecting to \"%s\".",
"Failed to connect to \"%s\" - Wrong password.",
"Failed to connect to \"%s\" - Network not found.",
"Failed to connect.",
"Connected to \"%s\"."
};
const size_t num_states = sizeof(state)/sizeof(state[0]);
uint8_t curr_state = wifi_station_get_connect_status ();
if (curr_state < num_states)
{
switch (curr_state)
{
case STATION_CONNECTING:
case STATION_WRONG_PASSWORD:
case STATION_NO_AP_FOUND:
case STATION_GOT_IP:
{
const char *s = state[curr_state];
struct station_config config;
wifi_station_get_config(&config);
config.ssid[31] = '\0';
int state_len = strlen(s);
int ssid_len = strlen(config.ssid);
int status_len = state_len + ssid_len + 1;
char status_buf[status_len];
memset(status_buf, 0, status_len);
status_len = sprintf(status_buf, s, config.ssid);
int buf_len = sizeof(fmt) + status_len + 10; //10 = (9+1), 1 byte is '\0' and 9 are reserved for length field
char buf[buf_len];
memset(buf, 0, buf_len);
int output_len = sprintf(buf, fmt, status_len, status_buf);
enduser_setup_http_serve_header(conn, buf, output_len);
}
break;
/* Handle non-formatted strings */
default:
{
const char *s = state[curr_state];
int status_len = strlen(s);
int buf_len = sizeof(fmt) + status_len + 10; //10 = (9+1), 1 byte is '\0' and 9 are reserved for length field
char buf[buf_len];
memset(buf, 0, buf_len);
int output_len = sprintf(buf, fmt, status_len, s);
enduser_setup_http_serve_header(conn, buf, output_len);
}
break;
}
}
else
{
enduser_setup_http_serve_header(conn, http_header_500, LITLEN(http_header_500));
}
}
/* --- WiFi AP scanning support -------------------------------------------- */
static void free_scan_listeners (void)
{
if (!state || !state->scan_listeners)
{
return;
}
scan_listener_t *l = state->scan_listeners , *next = 0;
while (l)
{
next = l->next;
free (l);
l = next;
}
state->scan_listeners = 0;
}
static void remove_scan_listener (scan_listener_t *l)
{
if (state)
{
scan_listener_t **sl = &state->scan_listeners;
while (*sl)
{
/* Remove any and all references to the closed conn from the scan list */
if (*sl == l)
{
*sl = l->next;
free (l);
/* No early exit to guard against multi-entry on list */
}
else
sl = &(*sl)->next;
}
}
}
static char *escape_ssid (char *dst, const char *src)
{
for (int i = 0; i < 32 && src[i]; ++i)
{
if (src[i] == '\\' || src[i] == '"')
{
*dst++ = '\\';
}
*dst++ = src[i];
}
return dst;
}
static void notify_scan_listeners (const char *payload, size_t sz)
{
if (!state)
{
return;
}
for (scan_listener_t *l = state->scan_listeners; l; l = l->next)
{
if (tcp_write (l->conn, payload, sz, TCP_WRITE_FLAG_COPY) != ERR_OK)
{
ENDUSER_SETUP_DEBUG("failed to send wifi list");
tcp_abort (l->conn);
}
else
tcp_sent (l->conn, close_once_sent); /* TODO: time-out sends? */
l->conn = 0;
}
free_scan_listeners ();
}
static void on_scan_done (void *arg, STATUS status)
{
if (!state || !state->scan_listeners)
{
return;
}
if (status == OK)
{
unsigned num_nets = 0;
for (struct bss_info *wn = arg; wn; wn = wn->next.stqe_next)
{
++num_nets;
}
const char header_fmt[] =
"HTTP/1.1 200 OK\r\n"
"Connection:close\r\n"
"Cache-control:no-cache\r\n"
"Content-type:application/json\r\n"
"Content-length:%4d\r\n"
"\r\n";
const size_t hdr_sz = sizeof (header_fmt) +1 -1; /* +expand %4d, -\0 */
/* To be able to safely escape a pathological SSID, we need 2*32 bytes */
const size_t max_entry_sz = sizeof("{\"ssid\":\"\",\"rssi\":},") + 2*32 + 6;
const size_t alloc_sz = hdr_sz + num_nets * max_entry_sz + 3;
char *http = zalloc (alloc_sz);
if (!http)
{
goto serve_500;
}
char *p = http + hdr_sz; /* start body where we know it will be */
/* p[0] will be clobbered when we print the header, so fill it in last */
++p;
for (struct bss_info *wn = arg; wn; wn = wn->next.stqe_next)
{
if (wn != arg)
{
*p++ = ',';
}
const char entry_start[] = "{\"ssid\":\"";
strcpy (p, entry_start);
p += sizeof (entry_start) -1;
p = escape_ssid (p, wn->ssid);
const char entry_mid[] = "\",\"rssi\":";
strcpy (p, entry_mid);
p += sizeof (entry_mid) -1;
p += sprintf (p, "%d", wn->rssi);
*p++ = '}';
}
*p++ = ']';
size_t body_sz = (p - http) - hdr_sz;
sprintf (http, header_fmt, body_sz);
http[hdr_sz] = '['; /* Rewrite the \0 with the correct start of body */
notify_scan_listeners (http, hdr_sz + body_sz);
free (http);
return;
}
serve_500:
notify_scan_listeners (http_header_500, LITLEN(http_header_500));
}
/* ---- end WiFi AP scan support ------------------------------------------- */
static err_t enduser_setup_http_recvcb(void *arg, struct tcp_pcb *http_client, struct pbuf *p, err_t err)
{
ENDUSER_SETUP_DEBUG("enduser_setup_http_recvcb");
if (!state || err != ERR_OK)
{
if (!state)
ENDUSER_SETUP_DEBUG("ignoring received data while stopped");
tcp_abort (http_client);
return ERR_ABRT;
}
if (!p) /* remote side closed, close our end too */
{
ENDUSER_SETUP_DEBUG("connection closed");
scan_listener_t *l = arg; /* if it's waiting for scan, we have a ptr here */
if (l)
remove_scan_listener (l);
deferred_close (http_client);
return ERR_OK;
}
char *data = zalloc (p->tot_len + 1);
if (!data)
return ERR_MEM;
unsigned data_len = pbuf_copy_partial (p, data, p->tot_len, 0);
tcp_recved (http_client, p->tot_len);
pbuf_free (p);
err_t ret = ERR_OK;
if (strncmp(data, "GET ", 4) == 0)
{
if (strncmp(data + 4, "/ ", 2) == 0)
{
if (enduser_setup_http_serve_html(http_client) != 0)
{
ENDUSER_SETUP_ERROR("http_recvcb failed. Unable to send HTML.", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_NONFATAL);
}
else
{
goto free_out; /* streaming now in progress */
}
}
else if (strncmp(data + 4, "/aplist ", 8) == 0)
{
scan_listener_t *l = malloc (sizeof (scan_listener_t));
if (!l)
{
ENDUSER_SETUP_ERROR("out of memory", ENDUSER_SETUP_ERR_OUT_OF_MEMORY, ENDUSER_SETUP_ERR_NONFATAL);
}
bool already = (state->scan_listeners != NULL);
tcp_arg (http_client, l);
/* TODO: check if also need a tcp_err() cb, or if recv() is enough */
l->conn = http_client;
l->next = state->scan_listeners;
state->scan_listeners = l;
if (!already)
{
if (!wifi_station_scan(NULL, on_scan_done))
{
enduser_setup_http_serve_header(http_client, http_header_500, LITLEN(http_header_500));
deferred_close (l->conn);
l->conn = 0;
free_scan_listeners();
}
}
goto free_out; /* request queued */
}
else if (strncmp(data + 4, "/status ", 8) == 0)
{
serve_status(http_client);
}
else if (strncmp(data + 4, "/update?", 8) == 0)
{
switch (enduser_setup_http_handle_credentials(data, data_len))
{
case 0:
enduser_setup_http_serve_header(http_client, http_header_302, LITLEN(http_header_302));
break;
case 1:
enduser_setup_http_serve_header(http_client, http_header_401, LITLEN(http_header_401));
break;
default:
ENDUSER_SETUP_ERROR("http_recvcb failed. Failed to handle wifi credentials.", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_NONFATAL);
break;
}
}
else if (strncmp(data + 4, "/generate_204 ", 14) == 0)
{
/* Convince Android devices that they have internet access to avoid pesky dialogues. */
enduser_setup_http_serve_header(http_client, http_header_204, LITLEN(http_header_204));
}
else
{
ENDUSER_SETUP_DEBUG("serving 404");
ENDUSER_SETUP_DEBUG(data + 4);
enduser_setup_http_serve_header(http_client, http_header_404, LITLEN(http_header_404));
}
}
else /* not GET */
{
enduser_setup_http_serve_header(http_client, http_header_401, LITLEN(http_header_401));
}
deferred_close (http_client);
free_out:
free (data);
return ret;
}
static err_t enduser_setup_http_connectcb(void *arg, struct tcp_pcb *pcb, err_t err)
{
ENDUSER_SETUP_DEBUG("enduser_setup_http_connectcb");
if (!state)
{
ENDUSER_SETUP_DEBUG("connect callback but no state?!");
tcp_abort (pcb);
return ERR_ABRT;
}
tcp_accepted (state->http_pcb);
tcp_recv (pcb, enduser_setup_http_recvcb);
tcp_nagle_disable (pcb);
return ERR_OK;
}
static int enduser_setup_http_start(void)
{
ENDUSER_SETUP_DEBUG("enduser_setup_http_start");
struct tcp_pcb *pcb = tcp_new ();
if (pcb == NULL)
{
ENDUSER_SETUP_ERROR("http_start failed. Memory allocation failed (http_pcb == NULL).", ENDUSER_SETUP_ERR_OUT_OF_MEMORY, ENDUSER_SETUP_ERR_FATAL);
}
if (tcp_bind (pcb, IP_ADDR_ANY, 80) != ERR_OK)
{
ENDUSER_SETUP_ERROR("http_start bind failed", ENDUSER_SETUP_ERR_SOCKET_ALREADY_OPEN, ENDUSER_SETUP_ERR_FATAL);
}
state->http_pcb = tcp_listen (pcb);
if (!state->http_pcb)
{
tcp_abort(pcb); /* original wasn't freed for us */
ENDUSER_SETUP_ERROR("http_start listen failed", ENDUSER_SETUP_ERR_SOCKET_ALREADY_OPEN, ENDUSER_SETUP_ERR_FATAL);
}
tcp_accept (state->http_pcb, enduser_setup_http_connectcb);
/* TODO: check lwip tcp timeouts */
#if 0
err = espconn_regist_time(state->espconn_http_tcp, 2, 0);
if (err == ESPCONN_ARG)
{
ENDUSER_SETUP_ERROR("http_start failed. Unable to set TCP timeout.", ENDUSER_SETUP_ERR_CONNECTION_NOT_FOUND, ENDUSER_SETUP_ERR_NONFATAL | ENDUSER_SETUP_ERR_NO_RETURN);
}
#endif
int err = enduser_setup_http_load_payload();
if (err == 1)
{
ENDUSER_SETUP_DEBUG("enduser_setup_http_start info. Loaded backup HTML.");
}
else if (err == 2)
{
ENDUSER_SETUP_ERROR("http_start failed. Unable to allocate memory for HTTP payload.", ENDUSER_SETUP_ERR_OUT_OF_MEMORY, ENDUSER_SETUP_ERR_FATAL);
}
return 0;
}
static void enduser_setup_http_stop(void)
{
ENDUSER_SETUP_DEBUG("enduser_setup_http_stop");
if (state && state->http_pcb)
{
tcp_close (state->http_pcb); /* cannot fail for listening sockets */
state->http_pcb = 0;
}
}
static void enduser_setup_ap_stop(void)
{
ENDUSER_SETUP_DEBUG("enduser_setup_station_stop");
wifi_set_opmode(~SOFTAP_MODE & wifi_get_opmode());
}
static void enduser_setup_ap_start(void)
{
ENDUSER_SETUP_DEBUG("enduser_setup_ap_start");
struct softap_config cnf;
memset(&(cnf), 0, sizeof(struct softap_config));
#ifndef ENDUSER_SETUP_AP_SSID
#define ENDUSER_SETUP_AP_SSID "SetupGadget"
#endif
char ssid[] = ENDUSER_SETUP_AP_SSID;
int ssid_name_len = strlen(ssid);
memcpy(&(cnf.ssid), ssid, ssid_name_len);
uint8_t mac[6];
wifi_get_macaddr(SOFTAP_IF, mac);
cnf.ssid[ssid_name_len] = '_';
sprintf(cnf.ssid + ssid_name_len + 1, "%02X%02X%02X", mac[3], mac[4], mac[5]);
cnf.ssid_len = ssid_name_len + 7;
cnf.channel = 1;
cnf.authmode = AUTH_OPEN;
cnf.ssid_hidden = 0;
cnf.max_connection = 5;
cnf.beacon_interval = 100;
wifi_set_opmode(STATIONAP_MODE);
wifi_softap_set_config(&cnf);
}
static void enduser_setup_dns_recv_callback(void *arg, char *recv_data, unsigned short recv_len)
{
ENDUSER_SETUP_DEBUG("enduser_setup_dns_recv_callback.");
struct espconn *callback_espconn = arg;
struct ip_info ip_info;
uint32_t qname_len = strlen(&(recv_data[12])) + 1; // \0=1byte
uint32_t dns_reply_static_len = (uint32_t) sizeof(dns_header) + (uint32_t) sizeof(dns_body) + 2 + 4; // dns_id=2bytes, ip=4bytes
uint32_t dns_reply_len = dns_reply_static_len + qname_len;
uint8_t if_mode = wifi_get_opmode();
if ((if_mode & SOFTAP_MODE) == 0)
{
ENDUSER_SETUP_ERROR_VOID("dns_recv_callback failed. Interface mode not supported.", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_FATAL);
}
uint8_t if_index = (if_mode == STATION_MODE? STATION_IF : SOFTAP_IF);
if (wifi_get_ip_info(if_index , &ip_info) == false)
{
ENDUSER_SETUP_ERROR_VOID("dns_recv_callback failed. Unable to get interface IP.", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_FATAL);
}
char *dns_reply = (char *) malloc(dns_reply_len);
if (dns_reply == NULL)
{
ENDUSER_SETUP_ERROR_VOID("dns_recv_callback failed. Failed to allocate memory.", ENDUSER_SETUP_ERR_OUT_OF_MEMORY, ENDUSER_SETUP_ERR_NONFATAL);
}
uint32_t insert_byte = 0;
memcpy(&(dns_reply[insert_byte]), recv_data, 2);
insert_byte += 2;
memcpy(&(dns_reply[insert_byte]), dns_header, sizeof(dns_header));
insert_byte += (uint32_t) sizeof(dns_header);
memcpy(&(dns_reply[insert_byte]), &(recv_data[12]), qname_len);
insert_byte += qname_len;
memcpy(&(dns_reply[insert_byte]), dns_body, sizeof(dns_body));
insert_byte += (uint32_t) sizeof(dns_body);
memcpy(&(dns_reply[insert_byte]), &(ip_info.ip), 4);
// SDK 1.4.0 changed behaviour, for UDP server need to look up remote ip/port
remot_info *pr = 0;
if (espconn_get_connection_info(callback_espconn, &pr, 0) != ESPCONN_OK)
{
ENDUSER_SETUP_ERROR_VOID("dns_recv_callback failed. Unable to get IP of UDP sender.", ENDUSER_SETUP_ERR_CONNECTION_NOT_FOUND, ENDUSER_SETUP_ERR_FATAL);
}
callback_espconn->proto.udp->remote_port = pr->remote_port;
memmove(callback_espconn->proto.udp->remote_ip, pr->remote_ip, 4);
int8_t err;
err = espconn_send(callback_espconn, dns_reply, dns_reply_len);
free(dns_reply);
if (err == ESPCONN_MEM)
{
ENDUSER_SETUP_ERROR_VOID("dns_recv_callback failed. Failed to allocate memory for send.", ENDUSER_SETUP_ERR_OUT_OF_MEMORY, ENDUSER_SETUP_ERR_FATAL);
}
else if (err == ESPCONN_ARG)
{
ENDUSER_SETUP_ERROR_VOID("dns_recv_callback failed. Can't execute transmission.", ENDUSER_SETUP_ERR_CONNECTION_NOT_FOUND, ENDUSER_SETUP_ERR_FATAL);
}
else if (err != 0)
{
ENDUSER_SETUP_ERROR_VOID("dns_recv_callback failed. espconn_send failed", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_FATAL);
}
}
static void enduser_setup_free(void)
{
ENDUSER_SETUP_DEBUG("enduser_setup_free");
if (state == NULL)
{
return;
}
/* Make sure no running timers are left. */
os_timer_disarm(&(state->check_station_timer));
os_timer_disarm(&(state->shutdown_timer));
if (state->espconn_dns_udp != NULL)
{
if (state->espconn_dns_udp->proto.udp != NULL)
{
free(state->espconn_dns_udp->proto.udp);
}
free(state->espconn_dns_udp);
}
free(state->http_payload_data);
free_scan_listeners ();
free(state);
state = NULL;
}
static int enduser_setup_dns_start(void)
{
ENDUSER_SETUP_DEBUG("enduser_setup_dns_start");
if (state->espconn_dns_udp != NULL)
{
ENDUSER_SETUP_ERROR("dns_start failed. Appears to already be started (espconn_dns_udp != NULL).", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_FATAL);
}
state->espconn_dns_udp = (struct espconn *) malloc(sizeof(struct espconn));
if (state->espconn_dns_udp == NULL)
{
ENDUSER_SETUP_ERROR("dns_start failed. Memory allocation failed (espconn_dns_udp == NULL).", ENDUSER_SETUP_ERR_OUT_OF_MEMORY, ENDUSER_SETUP_ERR_FATAL);
}
esp_udp *esp_udp_data = (esp_udp *) malloc(sizeof(esp_udp));
if (esp_udp_data == NULL)
{
ENDUSER_SETUP_ERROR("dns_start failed. Memory allocation failed (esp_udp == NULL).", ENDUSER_SETUP_ERR_OUT_OF_MEMORY, ENDUSER_SETUP_ERR_FATAL);
}
memset(state->espconn_dns_udp, 0, sizeof(struct espconn));
memset(esp_udp_data, 0, sizeof(esp_udp));
state->espconn_dns_udp->proto.udp = esp_udp_data;
state->espconn_dns_udp->type = ESPCONN_UDP;
state->espconn_dns_udp->state = ESPCONN_NONE;
esp_udp_data->local_port = 53;
int8_t err;
err = espconn_regist_recvcb(state->espconn_dns_udp, enduser_setup_dns_recv_callback);
if (err != 0)
{
ENDUSER_SETUP_ERROR("dns_start failed. Couldn't add receive callback, unknown error.", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_FATAL);
}
err = espconn_create(state->espconn_dns_udp);
if (err == ESPCONN_ISCONN)
{
ENDUSER_SETUP_ERROR("dns_start failed. Couldn't create connection, already listening for that connection.", ENDUSER_SETUP_ERR_SOCKET_ALREADY_OPEN, ENDUSER_SETUP_ERR_FATAL);
}
else if (err == ESPCONN_MEM)
{
ENDUSER_SETUP_ERROR("dns_start failed. Couldn't create connection, out of memory.", ENDUSER_SETUP_ERR_OUT_OF_MEMORY, ENDUSER_SETUP_ERR_FATAL);
}
else if (err != 0)
{
ENDUSER_SETUP_ERROR("dns_start failed. Couldn't create connection, unknown error.", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_FATAL);
}
return 0;
}
static void enduser_setup_dns_stop(void)
{
ENDUSER_SETUP_DEBUG("enduser_setup_dns_stop");
if (state != NULL && state->espconn_dns_udp != NULL)
{
espconn_delete(state->espconn_dns_udp);
}
}
static int enduser_setup_init(lua_State *L)
{
ENDUSER_SETUP_DEBUG("enduser_setup_init");
if (state != NULL)
{
ENDUSER_SETUP_ERROR("init failed. Appears to already be started.", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_FATAL);
return ENDUSER_SETUP_ERR_UNKOWN_ERROR;
}
state = (enduser_setup_state_t *) zalloc(sizeof(enduser_setup_state_t));
if (state == NULL)
{
ENDUSER_SETUP_ERROR("init failed. Unable to allocate memory.", ENDUSER_SETUP_ERR_OUT_OF_MEMORY, ENDUSER_SETUP_ERR_FATAL);
return ENDUSER_SETUP_ERR_OUT_OF_MEMORY;
}
memset(state, 0, sizeof(enduser_setup_state_t));
state->lua_connected_cb_ref = LUA_NOREF;
state->lua_err_cb_ref = LUA_NOREF;
state->lua_dbg_cb_ref = LUA_NOREF;
if (!lua_isnoneornil(L, 1))
{
lua_pushvalue(L, 1);
state->lua_connected_cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
}
else
{
state->lua_connected_cb_ref = LUA_NOREF;
}
if (!lua_isnoneornil(L, 2))
{
lua_pushvalue (L, 2);
state->lua_err_cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
}
else
{
state->lua_err_cb_ref = LUA_NOREF;
}
if (!lua_isnoneornil(L, 3))
{
lua_pushvalue (L, 3);
state->lua_dbg_cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
}
else
{
state->lua_dbg_cb_ref = LUA_NOREF;
}
return 0;
}
static int enduser_setup_manual(lua_State *L)
{
if (!lua_isnoneornil (L, 1))
{
manual = lua_toboolean (L, 1);
}
lua_pushboolean (L, manual);
return 1;
}
static int enduser_setup_start(lua_State *L)
{
ENDUSER_SETUP_DEBUG("enduser_setup_start");
if (!do_station_cfg_handle)
{
do_station_cfg_handle = task_get_id(do_station_cfg);
}
if(enduser_setup_init(L))
{
goto failed;
}
enduser_setup_check_station_start();
if (!manual)
{
enduser_setup_ap_start();
}
if(enduser_setup_dns_start())
{
goto failed;
}
if(enduser_setup_http_start())
{
goto failed;
}
goto out;
failed:
enduser_setup_stop(lua_getstate());
out:
return 0;
}
/**
* A wrapper needed for type-reasons strictness reasons.
*/
static void enduser_setup_stop_callback(void *ptr)
{
enduser_setup_stop(lua_getstate());
}
static int enduser_setup_stop(lua_State* L)
{
ENDUSER_SETUP_DEBUG("enduser_setup_stop");
if (!manual)
{
enduser_setup_ap_stop();
}
enduser_setup_dns_stop();
enduser_setup_http_stop();
enduser_setup_free();
return 0;
}
static const LUA_REG_TYPE enduser_setup_map[] = {
{ LSTRKEY( "manual" ), LFUNCVAL( enduser_setup_manual )},
{ LSTRKEY( "start" ), LFUNCVAL( enduser_setup_start )},
{ LSTRKEY( "stop" ), LFUNCVAL( enduser_setup_stop )},
{ LNILKEY, LNILVAL}
};
NODEMCU_MODULE(ENDUSER_SETUP, "enduser_setup", enduser_setup_map, NULL);
#endif
// Module for interfacing with file system
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include "c_types.h"
#include "flash_fs.h"
#include <string.h>
static volatile int file_fd = FS_OPEN_OK - 1;
// Lua: open(filename, mode)
static int file_open( lua_State* L )
{
size_t len;
if((FS_OPEN_OK - 1)!=file_fd){
fs_close(file_fd);
file_fd = FS_OPEN_OK - 1;
}
const char *fname = luaL_checklstring( L, 1, &len );
luaL_argcheck(L, len < FS_NAME_MAX_LENGTH && strlen(fname) == len, 1, "filename invalid");
const char *mode = luaL_optstring(L, 2, "r");
file_fd = fs_open(fname, fs_mode2flag(mode));
if(file_fd < FS_OPEN_OK){
file_fd = FS_OPEN_OK - 1;
lua_pushnil(L);
} else {
lua_pushboolean(L, 1);
}
return 1;
}
// Lua: close()
static int file_close( lua_State* L )
{
if((FS_OPEN_OK - 1)!=file_fd){
fs_close(file_fd);
file_fd = FS_OPEN_OK - 1;
}
return 0;
}
// Lua: format()
static int file_format( lua_State* L )
{
size_t len;
file_close(L);
if( !fs_format() )
{
NODE_ERR( "\ni*** ERROR ***: unable to format. FS might be compromised.\n" );
NODE_ERR( "It is advised to re-flash the NodeMCU image.\n" );
}
else{
NODE_ERR( "format done.\n" );
}
return 0;
}
#if defined(BUILD_SPIFFS)
extern spiffs fs;
// Lua: list()
static int file_list( lua_State* L )
{
spiffs_DIR d;
struct spiffs_dirent e;
struct spiffs_dirent *pe = &e;
lua_newtable( L );
SPIFFS_opendir(&fs, "/", &d);
while ((pe = SPIFFS_readdir(&d, pe))) {
// NODE_ERR(" %s size:%i\n", pe->name, pe->size);
lua_pushinteger(L, pe->size);
lua_setfield( L, -2, pe->name );
}
SPIFFS_closedir(&d);
return 1;
}
static int file_seek (lua_State *L)
{
static const int mode[] = {FS_SEEK_SET, FS_SEEK_CUR, FS_SEEK_END};
static const char *const modenames[] = {"set", "cur", "end", NULL};
if((FS_OPEN_OK - 1)==file_fd)
return luaL_error(L, "open a file first");
int op = luaL_checkoption(L, 1, "cur", modenames);
long offset = luaL_optlong(L, 2, 0);
op = fs_seek(file_fd, offset, mode[op]);
if (op < 0)
lua_pushnil(L); /* error */
else
lua_pushinteger(L, fs_tell(file_fd));
return 1;
}
// Lua: exists(filename)
static int file_exists( lua_State* L )
{
size_t len;
const char *fname = luaL_checklstring( L, 1, &len );
luaL_argcheck(L, len <= FS_NAME_MAX_LENGTH, 1, "filename too long");
spiffs_stat stat;
int rc = SPIFFS_stat(&fs, (char *)fname, &stat);
lua_pushboolean(L, (rc == SPIFFS_OK ? 1 : 0));
return 1;
}
// Lua: remove(filename)
static int file_remove( lua_State* L )
{
size_t len;
const char *fname = luaL_checklstring( L, 1, &len );
luaL_argcheck(L, len <= FS_NAME_MAX_LENGTH, 1, "filename too long");
file_close(L);
SPIFFS_remove(&fs, (char *)fname);
return 0;
}
// Lua: flush()
static int file_flush( lua_State* L )
{
if((FS_OPEN_OK - 1)==file_fd)
return luaL_error(L, "open a file first");
if(fs_flush(file_fd) == 0)
lua_pushboolean(L, 1);
else
lua_pushnil(L);
return 1;
}
#if 0
// Lua: check()
static int file_check( lua_State* L )
{
file_close(L);
lua_pushinteger(L, fs_check());
return 1;
}
#endif
// Lua: rename("oldname", "newname")
static int file_rename( lua_State* L )
{
size_t len;
if((FS_OPEN_OK - 1)!=file_fd){
fs_close(file_fd);
file_fd = FS_OPEN_OK - 1;
}
const char *oldname = luaL_checklstring( L, 1, &len );
luaL_argcheck(L, len <= FS_NAME_MAX_LENGTH, 1, "filename too long");
const char *newname = luaL_checklstring( L, 2, &len );
luaL_argcheck(L, len <= FS_NAME_MAX_LENGTH, 2, "filename too long");
if(SPIFFS_OK==myspiffs_rename( oldname, newname )){
lua_pushboolean(L, 1);
} else {
lua_pushboolean(L, 0);
}
return 1;
}
// Lua: fsinfo()
static int file_fsinfo( lua_State* L )
{
u32_t total, used;
SPIFFS_info(&fs, &total, &used);
NODE_DBG("total: %d, used:%d\n", total, used);
if(total>0x7FFFFFFF || used>0x7FFFFFFF || used > total)
{
return luaL_error(L, "file system error");;
}
lua_pushinteger(L, total-used);
lua_pushinteger(L, used);
lua_pushinteger(L, total);
return 3;
}
#endif
// g_read()
static int file_g_read( lua_State* L, int n, int16_t end_char )
{
if(n <= 0 || n > LUAL_BUFFERSIZE)
n = LUAL_BUFFERSIZE;
if(end_char < 0 || end_char >255)
end_char = EOF;
luaL_Buffer b;
if((FS_OPEN_OK - 1)==file_fd)
return luaL_error(L, "open a file first");
luaL_buffinit(L, &b);
char *p = luaL_prepbuffer(&b);
int i;
n = fs_read(file_fd, p, n);
for (i = 0; i < n; ++i)
if (p[i] == end_char)
{
++i;
break;
}
if(i==0){
luaL_pushresult(&b); /* close buffer */
return (lua_objlen(L, -1) > 0); /* check whether read something */
}
fs_seek(file_fd, -(n - i), SEEK_CUR);
luaL_addsize(&b, i);
luaL_pushresult(&b); /* close buffer */
return 1; /* read at least an `eol' */
}
// Lua: read()
// file.read() will read all byte in file
// file.read(10) will read 10 byte from file, or EOF is reached.
// file.read('q') will read until 'q' or EOF is reached.
static int file_read( lua_State* L )
{
unsigned need_len = LUAL_BUFFERSIZE;
int16_t end_char = EOF;
size_t el;
if( lua_type( L, 1 ) == LUA_TNUMBER )
{
need_len = ( unsigned )luaL_checkinteger( L, 1 );
if( need_len > LUAL_BUFFERSIZE ){
need_len = LUAL_BUFFERSIZE;
}
}
else if(lua_isstring(L, 1))
{
const char *end = luaL_checklstring( L, 1, &el );
if(el!=1){
return luaL_error( L, "wrong arg range" );
}
end_char = (int16_t)end[0];
}
return file_g_read(L, need_len, end_char);
}
// Lua: readline()
static int file_readline( lua_State* L )
{
return file_g_read(L, LUAL_BUFFERSIZE, '\n');
}
// Lua: write("string")
static int file_write( lua_State* L )
{
if((FS_OPEN_OK - 1)==file_fd)
return luaL_error(L, "open a file first");
size_t l, rl;
const char *s = luaL_checklstring(L, 1, &l);
rl = fs_write(file_fd, s, l);
if(rl==l)
lua_pushboolean(L, 1);
else
lua_pushnil(L);
return 1;
}
// Lua: writeline("string")
static int file_writeline( lua_State* L )
{
if((FS_OPEN_OK - 1)==file_fd)
return luaL_error(L, "open a file first");
size_t l, rl;
const char *s = luaL_checklstring(L, 1, &l);
rl = fs_write(file_fd, s, l);
if(rl==l){
rl = fs_write(file_fd, "\n", 1);
if(rl==1)
lua_pushboolean(L, 1);
else
lua_pushnil(L);
}
else{
lua_pushnil(L);
}
return 1;
}
static int file_fscfg (lua_State *L)
{
lua_pushinteger (L, fs.cfg.phys_addr);
lua_pushinteger (L, fs.cfg.phys_size);
return 2;
}
// Module function map
static const LUA_REG_TYPE file_map[] = {
{ LSTRKEY( "list" ), LFUNCVAL( file_list ) },
{ LSTRKEY( "open" ), LFUNCVAL( file_open ) },
{ LSTRKEY( "close" ), LFUNCVAL( file_close ) },
{ LSTRKEY( "write" ), LFUNCVAL( file_write ) },
{ LSTRKEY( "writeline" ), LFUNCVAL( file_writeline ) },
{ LSTRKEY( "read" ), LFUNCVAL( file_read ) },
{ LSTRKEY( "readline" ), LFUNCVAL( file_readline ) },
{ LSTRKEY( "format" ), LFUNCVAL( file_format ) },
#if defined(BUILD_SPIFFS)
{ LSTRKEY( "remove" ), LFUNCVAL( file_remove ) },
{ LSTRKEY( "seek" ), LFUNCVAL( file_seek ) },
{ LSTRKEY( "flush" ), LFUNCVAL( file_flush ) },
{ LSTRKEY( "rename" ), LFUNCVAL( file_rename ) },
{ LSTRKEY( "fsinfo" ), LFUNCVAL( file_fsinfo ) },
{ LSTRKEY( "fscfg" ), LFUNCVAL( file_fscfg ) },
{ LSTRKEY( "exists" ), LFUNCVAL( file_exists ) },
#endif
{ LNILKEY, LNILVAL }
};
NODEMCU_MODULE(FILE, "file", file_map, NULL);
// Module for interfacing with GPIO
#include "module.h"
#include "lauxlib.h"
#include "lmem.h"
#include "platform.h"
#include "user_interface.h"
#include "c_types.h"
#include <string.h>
#include "gpio.h"
#undef INTERRUPT
#define PULLUP PLATFORM_GPIO_PULLUP
#define FLOAT PLATFORM_GPIO_FLOAT
#define OUTPUT PLATFORM_GPIO_OUTPUT
#define OPENDRAIN PLATFORM_GPIO_OPENDRAIN
#define INPUT PLATFORM_GPIO_INPUT
#define INTERRUPT PLATFORM_GPIO_INT
#define HIGH PLATFORM_GPIO_HIGH
#define LOW PLATFORM_GPIO_LOW
#ifdef GPIO_INTERRUPT_ENABLE
#if defined(__ESP8266__)
// We also know that the non-level interrupt types are < LOLEVEL, and that
// HILEVEL is > LOLEVEL. Since this is burned into the hardware it is not
// going to change.
# define INTERRUPT_TYPE_IS_LEVEL(x) ((x) >= GPIO_PIN_INTR_LOLEVEL)
#elif defined(__ESP32__)
// There appears to be no level interrupt support on the ESP32?
# define INTERRUPT_TYPE_IS_LEVEL(x) 0
#endif
static int gpio_cb_ref[GPIO_PIN_NUM];
// This task is scheduled by the ISR and is used
// to initiate the Lua-land gpio.trig() callback function
// It also re-enables the pin interrupt, so that we get another callback queued
static void gpio_intr_callback_task (task_param_t param, task_prio_t priority)
{
unsigned pin = param >> 1;
unsigned level = param & 1;
UNUSED(priority);
NODE_DBG("pin:%d, level:%d \n", pin, level);
if(gpio_cb_ref[pin] != LUA_NOREF) {
// GPIO callbacks are run in L0 and inlcude the level as a parameter
lua_State *L = lua_getstate();
NODE_DBG("Calling: %08x\n", gpio_cb_ref[pin]);
//
if (!INTERRUPT_TYPE_IS_LEVEL(pin_int_type[pin])) {
// Edge triggered -- re-enable the interrupt
platform_gpio_intr_init(pin, pin_int_type[pin]);
}
// Do the actual callback
lua_rawgeti(L, LUA_REGISTRYINDEX, gpio_cb_ref[pin]);
lua_pushinteger(L, level);
lua_call(L, 1, 0);
if (INTERRUPT_TYPE_IS_LEVEL(pin_int_type[pin])) {
// Level triggered -- re-enable the callback
platform_gpio_intr_init(pin, pin_int_type[pin]);
}
}
}
// Lua: trig( pin, type, function )
static int lgpio_trig( lua_State* L )
{
unsigned pin = luaL_checkinteger( L, 1 );
static const char * const opts[] = {"none", "up", "down", "both",
#ifdef __ESP8266__
"low", "high",
#endif
NULL};
static const int opts_type[] = {
GPIO_PIN_INTR_DISABLE, GPIO_PIN_INTR_POSEDGE, GPIO_PIN_INTR_NEGEDGE,
GPIO_PIN_INTR_ANYEDGE
#ifdef __ESP8266__
,GPIO_PIN_INTR_LOLEVEL, GPIO_PIN_INTR_HILEVEL
#endif
};
luaL_argcheck(L, platform_gpio_exists(pin) && pin>0, 1, "Invalid interrupt pin");
int old_pin_ref = gpio_cb_ref[pin];
int type = opts_type[luaL_checkoption(L, 2, "none", opts)];
if (type == GPIO_PIN_INTR_DISABLE) {
// "none" clears the callback
gpio_cb_ref[pin] = LUA_NOREF;
} else if (lua_gettop(L)==2 && old_pin_ref != LUA_NOREF) {
// keep the old one if no callback
old_pin_ref = LUA_NOREF;
} else if (lua_type(L, 3) == LUA_TFUNCTION || lua_type(L, 3) == LUA_TLIGHTFUNCTION) {
// set up the new callback if present
lua_pushvalue(L, 3);
gpio_cb_ref[pin] = luaL_ref(L, LUA_REGISTRYINDEX);
} else {
// invalid combination, so clear down any old callback and throw an error
if(old_pin_ref != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, old_pin_ref);
luaL_argcheck(L, 0, 3, "invalid callback type");
}
// unreference any overwritten callback
if(old_pin_ref != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, old_pin_ref);
NODE_DBG("Pin data: %d %d %08x, %d %d %d, %08x\n",
pin, type, pin_mux[pin], pin_num[pin], pin_func[pin], pin_int_type[pin], gpio_cb_ref[pin]);
platform_gpio_intr_init(pin, type);
return 0;
}
#endif
// Lua: mode( pin, mode, pullup )
static int lgpio_mode( lua_State* L )
{
unsigned pin = luaL_checkinteger( L, 1 );
unsigned mode = luaL_checkinteger( L, 2 );
unsigned pullup = luaL_optinteger( L, 3, FLOAT );
luaL_argcheck(L, platform_gpio_exists(pin) && (mode!=INTERRUPT || pin>0), 1, "Invalid pin");
luaL_argcheck(L, mode==OUTPUT || mode==OPENDRAIN || mode==INPUT
#ifdef GPIO_INTERRUPT_ENABLE
|| mode==INTERRUPT
#endif
, 2, "wrong arg type" );
if(pullup!=FLOAT) pullup = PULLUP;
NODE_DBG("pin,mode,pullup= %d %d %d\n",pin,mode,pullup);
NODE_DBG("Pin data at mode: %d %08x, %d %d %d, %08x\n",
pin, pin_mux[pin], pin_num[pin], pin_func[pin], pin_int_type[pin], gpio_cb_ref[pin]);
#ifdef GPIO_INTERRUPT_ENABLE
if (mode != INTERRUPT){ // disable interrupt
if(gpio_cb_ref[pin] != LUA_NOREF){
luaL_unref(L, LUA_REGISTRYINDEX, gpio_cb_ref[pin]);
gpio_cb_ref[pin] = LUA_NOREF;
}
}
#endif
if( platform_gpio_mode( pin, mode, pullup ) < 0 )
return luaL_error( L, "wrong pin num." );
return 0;
}
// Lua: read( pin )
static int lgpio_read( lua_State* L )
{
unsigned pin = luaL_checkinteger( L, 1 );
MOD_CHECK_ID( gpio, pin );
lua_pushinteger( L, platform_gpio_read( pin ) );
return 1;
}
// Lua: write( pin, level )
static int lgpio_write( lua_State* L )
{
unsigned pin = luaL_checkinteger( L, 1 );
unsigned level = luaL_checkinteger( L, 2 );
MOD_CHECK_ID( gpio, pin );
luaL_argcheck(L, level==HIGH || level==LOW, 2, "wrong level type" );
platform_gpio_write(pin, level);
return 0;
}
#define DELAY_TABLE_MAX_LEN 256
#define delayMicroseconds os_delay_us
// Lua: serout( pin, firstLevel, delay_table, [repeatNum] )
// -- serout( pin, firstLevel, delay_table, [repeatNum] )
// gpio.mode(1,gpio.OUTPUT,gpio.PULLUP)
// gpio.serout(1,1,{30,30,60,60,30,30}) -- serial one byte, b10110010
// gpio.serout(1,1,{30,70},8) -- serial 30% pwm 10k, lasts 8 cycles
// gpio.serout(1,1,{3,7},8) -- serial 30% pwm 100k, lasts 8 cycles
// gpio.serout(1,1,{0,0},8) -- serial 50% pwm as fast as possible, lasts 8 cycles
// gpio.mode(1,gpio.OUTPUT,gpio.PULLUP)
// gpio.serout(1,0,{20,10,10,20,10,10,10,100}) -- sim uart one byte 0x5A at about 100kbps
// gpio.serout(1,1,{8,18},8) -- serial 30% pwm 38k, lasts 8 cycles
static int lgpio_serout( lua_State* L )
{
unsigned clocks_per_us = system_get_cpu_freq();
unsigned pin = luaL_checkinteger( L, 1 );
unsigned level = luaL_checkinteger( L, 2 );
unsigned repeats = luaL_optint( L, 4, 1 );
unsigned table_len, i, j;
luaL_argcheck(L, platform_gpio_exists(pin), 1, "Invalid pin");
luaL_argcheck(L, level==HIGH || level==LOW, 2, "Wrong arg type" );
luaL_argcheck(L, lua_istable( L, 3 ) &&
((table_len = lua_objlen( L, 3 )<DELAY_TABLE_MAX_LEN)), 3, "Invalid table" );
luaL_argcheck(L, repeats<256, 4, "repeats >= 256" );
uint32 *delay_table = luaM_newvector(L, table_len*repeats, uint32);
for( i = 1; i <= table_len; i++ ) {
lua_rawgeti( L, 3, i + 1 );
unsigned delay = (unsigned) luaL_checkinteger( L, -1 );
if (delay > 1000000) return luaL_error( L, "delay %u must be < 1,000,000 us", i );
delay_table[i-1] = delay;
lua_pop( L, 1 );
}
for( i = 0; i <= repeats; i++ ) {
if (!i) // skip the first loop (presumably this is some form of icache priming??).
continue;
for( j = 0;j < table_len; j++ ){
/* Direct Write is a ROM function which already disables interrupts for the atomic bit */
GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[pin]), level);
delayMicroseconds(delay_table[j]);
level = level==LOW ? HIGH : LOW;
}
}
luaM_freearray(L, delay_table, table_len, uint32);
return 0;
}
#undef DELAY_TABLE_MAX_LEN
// Module function map
static const LUA_REG_TYPE gpio_map[] = {
{ LSTRKEY( "mode" ), LFUNCVAL( lgpio_mode ) },
{ LSTRKEY( "read" ), LFUNCVAL( lgpio_read ) },
{ LSTRKEY( "write" ), LFUNCVAL( lgpio_write ) },
{ LSTRKEY( "serout" ), LFUNCVAL( lgpio_serout ) },
#ifdef GPIO_INTERRUPT_ENABLE
{ LSTRKEY( "trig" ), LFUNCVAL( lgpio_trig ) },
{ LSTRKEY( "INT" ), LNUMVAL( INTERRUPT ) },
#endif
{ LSTRKEY( "OUTPUT" ), LNUMVAL( OUTPUT ) },
{ LSTRKEY( "OPENDRAIN" ), LNUMVAL( OPENDRAIN ) },
{ LSTRKEY( "INPUT" ), LNUMVAL( INPUT ) },
{ LSTRKEY( "HIGH" ), LNUMVAL( HIGH ) },
{ LSTRKEY( "LOW" ), LNUMVAL( LOW ) },
{ LSTRKEY( "FLOAT" ), LNUMVAL( FLOAT ) },
{ LSTRKEY( "PULLUP" ), LNUMVAL( PULLUP ) },
{ LNILKEY, LNILVAL }
};
int luaopen_gpio( lua_State *L ) {
#ifdef GPIO_INTERRUPT_ENABLE
int i;
for(i=0;i<GPIO_PIN_NUM;i++){
gpio_cb_ref[i] = LUA_NOREF;
}
platform_gpio_init(task_get_id(gpio_intr_callback_task));
#endif
return 0;
}
NODEMCU_MODULE(GPIO, "gpio", gpio_map, luaopen_gpio);
/******************************************************************************
* HTTP module for NodeMCU
* vowstar@gmail.com
* 2015-12-29
*******************************************************************************/
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include "httpclient.h"
static int http_callback_registry = LUA_NOREF;
static void http_callback( char * response, int http_status, char * full_response )
{
#if defined(HTTPCLIENT_DEBUG_ON)
printf( "http_status=%d\n", http_status );
if ( http_status != HTTP_STATUS_GENERIC_ERROR )
{
if (full_response != NULL) {
printf( "strlen(full_response)=%d\n", strlen( full_response ) );
}
printf( "response=%s<EOF>\n", response );
}
#endif
if (http_callback_registry != LUA_NOREF)
{
lua_State *L = lua_getstate();
lua_rawgeti(L, LUA_REGISTRYINDEX, http_callback_registry);
lua_pushnumber(L, http_status);
if ( http_status != HTTP_STATUS_GENERIC_ERROR && response)
{
lua_pushstring(L, response);
}
else
{
lua_pushnil(L);
}
lua_call(L, 2, 0); // With 2 arguments and 0 result
luaL_unref(L, LUA_REGISTRYINDEX, http_callback_registry);
http_callback_registry = LUA_NOREF;
}
}
// Lua: http.request( url, method, header, body, function(status, reponse) end )
static int http_lapi_request( lua_State *L )
{
int length;
const char * url = luaL_checklstring(L, 1, &length);
const char * method = luaL_checklstring(L, 2, &length);
const char * headers = NULL;
const char * body = NULL;
// Check parameter
if ((url == NULL) || (method == NULL))
{
return luaL_error( L, "wrong arg type" );
}
if (lua_isstring(L, 3))
{
headers = luaL_checklstring(L, 3, &length);
}
if (lua_isstring(L, 4))
{
body = luaL_checklstring(L, 4, &length);
}
if (lua_type(L, 5) == LUA_TFUNCTION || lua_type(L, 5) == LUA_TLIGHTFUNCTION) {
lua_pushvalue(L, 5); // copy argument (func) to the top of stack
if (http_callback_registry != LUA_NOREF)
luaL_unref(L, LUA_REGISTRYINDEX, http_callback_registry);
http_callback_registry = luaL_ref(L, LUA_REGISTRYINDEX);
}
http_request(url, method, headers, body, http_callback);
return 0;
}
// Lua: http.post( url, header, body, function(status, reponse) end )
static int http_lapi_post( lua_State *L )
{
int length;
const char * url = luaL_checklstring(L, 1, &length);
const char * headers = NULL;
const char * body = NULL;
// Check parameter
if ((url == NULL))
{
return luaL_error( L, "wrong arg type" );
}
if (lua_isstring(L, 2))
{
headers = luaL_checklstring(L, 2, &length);
}
if (lua_isstring(L, 3))
{
body = luaL_checklstring(L, 3, &length);
}
if (lua_type(L, 4) == LUA_TFUNCTION || lua_type(L, 4) == LUA_TLIGHTFUNCTION) {
lua_pushvalue(L, 4); // copy argument (func) to the top of stack
if (http_callback_registry != LUA_NOREF)
luaL_unref(L, LUA_REGISTRYINDEX, http_callback_registry);
http_callback_registry = luaL_ref(L, LUA_REGISTRYINDEX);
}
http_post(url, headers, body, http_callback);
return 0;
}
// Lua: http.put( url, header, body, function(status, reponse) end )
static int http_lapi_put( lua_State *L )
{
int length;
const char * url = luaL_checklstring(L, 1, &length);
const char * headers = NULL;
const char * body = NULL;
// Check parameter
if ((url == NULL))
{
return luaL_error( L, "wrong arg type" );
}
if (lua_isstring(L, 2))
{
headers = luaL_checklstring(L, 2, &length);
}
if (lua_isstring(L, 3))
{
body = luaL_checklstring(L, 3, &length);
}
if (lua_type(L, 4) == LUA_TFUNCTION || lua_type(L, 4) == LUA_TLIGHTFUNCTION) {
lua_pushvalue(L, 4); // copy argument (func) to the top of stack
if (http_callback_registry != LUA_NOREF)
luaL_unref(L, LUA_REGISTRYINDEX, http_callback_registry);
http_callback_registry = luaL_ref(L, LUA_REGISTRYINDEX);
}
http_put(url, headers, body, http_callback);
return 0;
}
// Lua: http.delete( url, header, body, function(status, reponse) end )
static int http_lapi_delete( lua_State *L )
{
int length;
const char * url = luaL_checklstring(L, 1, &length);
const char * headers = NULL;
const char * body = NULL;
// Check parameter
if ((url == NULL))
{
return luaL_error( L, "wrong arg type" );
}
if (lua_isstring(L, 2))
{
headers = luaL_checklstring(L, 2, &length);
}
if (lua_isstring(L, 3))
{
body = luaL_checklstring(L, 3, &length);
}
if (lua_type(L, 4) == LUA_TFUNCTION || lua_type(L, 4) == LUA_TLIGHTFUNCTION) {
lua_pushvalue(L, 4); // copy argument (func) to the top of stack
if (http_callback_registry != LUA_NOREF)
luaL_unref(L, LUA_REGISTRYINDEX, http_callback_registry);
http_callback_registry = luaL_ref(L, LUA_REGISTRYINDEX);
}
http_delete(url, headers, body, http_callback);
return 0;
}
// Lua: http.get( url, header, function(status, reponse) end )
static int http_lapi_get( lua_State *L )
{
int length;
const char * url = luaL_checklstring(L, 1, &length);
const char * headers = NULL;
// Check parameter
if ((url == NULL))
{
return luaL_error( L, "wrong arg type" );
}
if (lua_isstring(L, 2))
{
headers = luaL_checklstring(L, 2, &length);
}
if (lua_type(L, 3) == LUA_TFUNCTION || lua_type(L, 3) == LUA_TLIGHTFUNCTION) {
lua_pushvalue(L, 3); // copy argument (func) to the top of stack
if (http_callback_registry != LUA_NOREF)
luaL_unref(L, LUA_REGISTRYINDEX, http_callback_registry);
http_callback_registry = luaL_ref(L, LUA_REGISTRYINDEX);
}
http_get(url, headers, http_callback);
return 0;
}
// Module function map
static const LUA_REG_TYPE http_map[] = {
{ LSTRKEY( "request" ), LFUNCVAL( http_lapi_request ) },
{ LSTRKEY( "post" ), LFUNCVAL( http_lapi_post ) },
{ LSTRKEY( "put" ), LFUNCVAL( http_lapi_put ) },
{ LSTRKEY( "delete" ), LFUNCVAL( http_lapi_delete ) },
{ LSTRKEY( "get" ), LFUNCVAL( http_lapi_get ) },
{ LSTRKEY( "OK" ), LNUMVAL( 0 ) },
{ LSTRKEY( "ERROR" ), LNUMVAL( HTTP_STATUS_GENERIC_ERROR ) },
{ LNILKEY, LNILVAL }
};
NODEMCU_MODULE(HTTP, "http", http_map, NULL);
// Module for HX711 load cell amplifier
// https://learn.sparkfun.com/tutorials/load-cell-amplifier-hx711-breakout-hookup-guide
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
#include <stdlib.h>
#include <string.h>
#include "user_interface.h"
#include "esp_system.h"
static uint8_t data_pin;
static uint8_t clk_pin;
/*Lua: hx711.init(clk_pin,data_pin)*/
static int hx711_init(lua_State* L) {
clk_pin = luaL_checkinteger(L,1);
data_pin = luaL_checkinteger(L,2);
MOD_CHECK_ID( gpio, clk_pin );
MOD_CHECK_ID( gpio, data_pin );
platform_gpio_mode(clk_pin, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_FLOAT);
platform_gpio_mode(data_pin, PLATFORM_GPIO_INPUT, PLATFORM_GPIO_FLOAT);
platform_gpio_write(clk_pin,1);//put chip to sleep.
return 0;
}
#define HX711_MAX_WAIT 1000000
/*will only read chA@128gain*/
/*Lua: result = hx711.read()*/
static int ICACHE_FLASH_ATTR hx711_read(lua_State* L) {
uint32_t i;
int32_t data = 0;
//TODO: double check init has happened first.
//wakeup hx711
platform_gpio_write(clk_pin,0);
//wait for data ready. or time out.
//TODO: set pin inturrupt and come back to it. This may take up to 1/10 sec
// or maybe just make an async version too and have both available.
system_soft_wdt_feed(); //clear WDT... this may take a while.
for (i = 0; i<HX711_MAX_WAIT && platform_gpio_read(data_pin)==1;i++){
asm ("nop");
}
//Handle timeout error
if (i>=HX711_MAX_WAIT) {
return luaL_error( L, "ADC timeout!", ( unsigned )0 );
}
for (i = 0; i<24 ; i++){ //clock in the 24 bits
platform_gpio_write(clk_pin,1);
platform_gpio_write(clk_pin,0);
data = data<<1;
if (platform_gpio_read(data_pin)==1) {
data = i==0 ? -1 : data|1; //signextend the first bit
}
}
//add 25th clock pulse to prevent protocol error (probably not needed
// since we'll go to sleep immediately after and reset on wakeup.)
platform_gpio_write(clk_pin,1);
platform_gpio_write(clk_pin,0);
//sleep
platform_gpio_write(clk_pin,1);
lua_pushinteger( L, data );
return 1;
}
// Module function map
static const LUA_REG_TYPE hx711_map[] = {
{ LSTRKEY( "init" ), LFUNCVAL( hx711_init )},
{ LSTRKEY( "read" ), LFUNCVAL( hx711_read )},
{ LNILKEY, LNILVAL}
};
int luaopen_hx711(lua_State *L) {
// TODO: Make sure that the GPIO system is initialized
return 0;
}
NODEMCU_MODULE(HX711, "hx711", hx711_map, luaopen_hx711);
// Module for interfacing with the I2C interface
#include "module.h"
#include "lauxlib.h"
#include "platform.h"
// Lua: speed = i2c.setup( id, sda, scl, speed )
static int i2c_setup( lua_State *L )
{
unsigned id = luaL_checkinteger( L, 1 );
unsigned sda = luaL_checkinteger( L, 2 );
unsigned scl = luaL_checkinteger( L, 3 );
MOD_CHECK_ID( i2c, id );
MOD_CHECK_ID( gpio, sda );
MOD_CHECK_ID( gpio, scl );
if(scl==0 || sda==0)
return luaL_error( L, "no i2c for D0" );
s32 speed = ( s32 )luaL_checkinteger( L, 4 );
if (speed <= 0)
return luaL_error( L, "wrong arg range" );
lua_pushinteger( L, platform_i2c_setup( id, sda, scl, (u32)speed ) );
return 1;
}
// Lua: i2c.start( id )
static int i2c_start( lua_State *L )
{
unsigned id = luaL_checkinteger( L, 1 );
MOD_CHECK_ID( i2c, id );
platform_i2c_send_start( id );
return 0;
}
// Lua: i2c.stop( id )
static int i2c_stop( lua_State *L )
{
unsigned id = luaL_checkinteger( L, 1 );
MOD_CHECK_ID( i2c, id );
platform_i2c_send_stop( id );
return 0;
}
// Lua: status = i2c.address( id, address, direction )
static int i2c_address( lua_State *L )
{
unsigned id = luaL_checkinteger( L, 1 );
int address = luaL_checkinteger( L, 2 );
int direction = luaL_checkinteger( L, 3 );
MOD_CHECK_ID( i2c, id );
if ( address < 0 || address > 127 )
return luaL_error( L, "wrong arg range" );
lua_pushboolean( L, platform_i2c_send_address( id, (u16)address, direction ) );
return 1;
}
// Lua: wrote = i2c.write( id, data1, [data2], ..., [datan] )
// data can be either a string, a table or an 8-bit number
static int i2c_write( lua_State *L )
{
unsigned id = luaL_checkinteger( L, 1 );
const char *pdata;
size_t datalen, i;
int numdata;
u32 wrote = 0;
unsigned argn;
MOD_CHECK_ID( i2c, id );
if( lua_gettop( L ) < 2 )
return luaL_error( L, "wrong arg type" );
for( argn = 2; argn <= lua_gettop( L ); argn ++ )
{
// lua_isnumber() would silently convert a string of digits to an integer
// whereas here strings are handled separately.
if( lua_type( L, argn ) == LUA_TNUMBER )
{
numdata = ( int )luaL_checkinteger( L, argn );
if( numdata < 0 || numdata > 255 )
return luaL_error( L, "wrong arg range" );
if( platform_i2c_send_byte( id, numdata ) != 1 )
break;
wrote ++;
}
else if( lua_istable( L, argn ) )
{
datalen = lua_objlen( L, argn );
for( i = 0; i < datalen; i ++ )
{
lua_rawgeti( L, argn, i + 1 );
numdata = ( int )luaL_checkinteger( L, -1 );
lua_pop( L, 1 );
if( numdata < 0 || numdata > 255 )
return luaL_error( L, "wrong arg range" );
if( platform_i2c_send_byte( id, numdata ) == 0 )
break;
}
wrote += i;
if( i < datalen )
break;
}
else
{
pdata = luaL_checklstring( L, argn, &datalen );
for( i = 0; i < datalen; i ++ )
if( platform_i2c_send_byte( id, pdata[ i ] ) == 0 )
break;
wrote += i;
if( i < datalen )
break;
}
}
lua_pushinteger( L, wrote );
return 1;
}
// Lua: read = i2c.read( id, size )
static int i2c_read( lua_State *L )
{
unsigned id = luaL_checkinteger( L, 1 );
u32 size = ( u32 )luaL_checkinteger( L, 2 ), i;
luaL_Buffer b;
int data;
MOD_CHECK_ID( i2c, id );
if( size == 0 )
return 0;
luaL_buffinit( L, &b );
for( i = 0; i < size; i ++ )
if( ( data = platform_i2c_recv_byte( id, i < size - 1 ) ) == -1 )
break;
else
luaL_addchar( &b, ( char )data );
luaL_pushresult( &b );
return 1;
}
// Module function map
static const LUA_REG_TYPE i2c_map[] = {
{ LSTRKEY( "setup" ), LFUNCVAL( i2c_setup ) },
{ LSTRKEY( "start" ), LFUNCVAL( i2c_start ) },
{ LSTRKEY( "stop" ), LFUNCVAL( i2c_stop ) },
{ LSTRKEY( "address" ), LFUNCVAL( i2c_address ) },
{ LSTRKEY( "write" ), LFUNCVAL( i2c_write ) },
{ LSTRKEY( "read" ), LFUNCVAL( i2c_read ) },
//{ LSTRKEY( "FAST" ), LNUMVAL( PLATFORM_I2C_SPEED_FAST ) },
{ LSTRKEY( "SLOW" ), LNUMVAL( PLATFORM_I2C_SPEED_SLOW ) },
{ LSTRKEY( "TRANSMITTER" ), LNUMVAL( PLATFORM_I2C_DIRECTION_TRANSMITTER ) },
{ LSTRKEY( "RECEIVER" ), LNUMVAL( PLATFORM_I2C_DIRECTION_RECEIVER ) },
{ LNILKEY, LNILVAL }
};
NODEMCU_MODULE(I2C, "i2c", i2c_map, NULL);
/*
** $Id: linit.c,v 1.14.1.1 2007/12/27 13:02:25 roberto Exp $
** Initialization of libraries for lua.c
** See Copyright Notice in lua.h
*/
#define linit_c
#define LUA_LIB
#define LUAC_CROSS_FILE
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include "luaconf.h"
#include "module.h"
BUILTIN_LIB_INIT( BASE, "", luaopen_base);
BUILTIN_LIB_INIT( LOADLIB, LUA_LOADLIBNAME, luaopen_package);
#if defined(LUA_USE_BUILTIN_IO)
BUILTIN_LIB_INIT( IO, LUA_IOLIBNAME, luaopen_io);
#endif
#if defined (LUA_USE_BUILTIN_STRING)
extern const luaR_entry strlib[];
BUILTIN_LIB_INIT( STRING, LUA_STRLIBNAME, luaopen_string);
BUILTIN_LIB( STRING, LUA_STRLIBNAME, strlib);
#endif
#if defined(LUA_USE_BUILTIN_TABLE)
extern const luaR_entry tab_funcs[];
BUILTIN_LIB_INIT( TABLE, LUA_TABLIBNAME, luaopen_table);
BUILTIN_LIB( TABLE, LUA_TABLIBNAME, tab_funcs);
#endif
#if defined(LUA_USE_BUILTIN_DEBUG) || defined(LUA_USE_BUILTIN_DEBUG_MINIMAL)
extern const luaR_entry dblib[];
BUILTIN_LIB_INIT( DBG, LUA_DBLIBNAME, luaopen_debug);
BUILTIN_LIB( DBG, LUA_DBLIBNAME, dblib);
#endif
#if defined(LUA_USE_BUILTIN_OS)
extern const luaR_entry syslib[];
BUILTIN_LIB( OS, LUA_OSLIBNAME, syslib);
#endif
#if defined(LUA_USE_BUILTIN_COROUTINE)
extern const luaR_entry co_funcs[];
BUILTIN_LIB( CO, LUA_COLIBNAME, co_funcs);
#endif
#if defined(LUA_USE_BUILTIN_MATH)
extern const luaR_entry math_map[];
BUILTIN_LIB( MATH, LUA_MATHLIBNAME, math_map);
#endif
#ifdef LUA_CROSS_COMPILER
const luaL_Reg lua_libs[] = {{NULL, NULL}};
const luaR_table lua_rotable[] = {{NULL, NULL}};
#else
extern const luaL_Reg lua_libs[];
#endif
void luaL_openlibs (lua_State *L) {
const luaL_Reg *lib = lua_libs;
for (; lib->name; lib++) {
if (lib->func)
{
lua_pushcfunction(L, lib->func);
lua_pushstring(L, lib->name);
lua_call(L, 1, 0);
}
}
}
// Module for access to the nodemcu_mdns functions
#include "module.h"
#include "lauxlib.h"
#include <string.h>
#include <stdlib.h>
#include "c_types.h"
#include "mem.h"
#include "lwip/ip_addr.h"
#include "nodemcu_mdns.h"
#include "user_interface.h"
//
// mdns.close()
//
static int mdns_close(lua_State *L)
{
nodemcu_mdns_close();
return 0;
}
//
// mdns.register(hostname [, { attributes} ])
//
static int mdns_register(lua_State *L)
{
struct nodemcu_mdns_info info;
memset(&info, 0, sizeof(info));
info.host_name = luaL_checkstring(L, 1);
info.service_name = "http";
info.service_port = 80;
info.host_desc = info.host_name;
if (lua_gettop(L) >= 2) {
luaL_checktype(L, 2, LUA_TTABLE);
lua_pushnil(L); // first key
int slot = 0;
while (lua_next(L, 2) != 0 && slot < sizeof(info.txt_data) / sizeof(info.txt_data[0])) {
luaL_checktype(L, -2, LUA_TSTRING);
const char *key = luaL_checkstring(L, -2);
if (strcmp(key, "port") == 0) {
info.service_port = luaL_checknumber(L, -1);
} else if (strcmp(key, "service") == 0) {
info.service_name = luaL_checkstring(L, -1);
} else if (strcmp(key, "description") == 0) {
info.host_desc = luaL_checkstring(L, -1);
} else {
int len = strlen(key) + 1;
const char *value = luaL_checkstring(L, -1);
char *p = alloca(len + strlen(value) + 1);
strcpy(p, key);
strcat(p, "=");
strcat(p, value);
info.txt_data[slot++] = p;
}
lua_pop(L, 1);
}
}
struct ip_info ipconfig;
uint8_t mode = wifi_get_opmode();
if (!wifi_get_ip_info((mode == 2) ? SOFTAP_IF : STATION_IF, &ipconfig) || !ipconfig.ip.addr) {
return luaL_error(L, "No network connection");
}
// Close up the old session (if any). This cannot fail
// so no chance of losing the memory in 'result'
mdns_close(L);
// Save the result as it appears that nodemcu_mdns_init needs
// to have the data valid while it is running.
if (!nodemcu_mdns_init(&info)) {
mdns_close(L);
return luaL_error(L, "Unable to start mDns daemon");
}
return 0;
}
// Module function map
static const LUA_REG_TYPE mdns_map[] = {
{ LSTRKEY("register"), LFUNCVAL(mdns_register) },
{ LSTRKEY("close"), LFUNCVAL(mdns_close) },
{ LNILKEY, LNILVAL }
};
NODEMCU_MODULE(MDNS, "mdns", mdns_map, NULL);
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment