Adding qrcodegen module for generating QR Codes (#2543)
* Adding qrcodegen module for generating QR Codes * Added LUA_MODULE_QRCODEGEN KConfig * Changed qrcodegen.encodeText() to use an options table Created common.h with new option table helper fns. * Reworked http.c to use new common.h options table APIs
This commit is contained in:
parent
1cb1aff4cd
commit
72d28fa86e
|
@ -11,3 +11,6 @@
|
|||
[submodule "components/ucg/ucg"]
|
||||
path = components/ucg/ucg
|
||||
url = https://github.com/olikraus/Ucglib_Arduino.git
|
||||
[submodule "components/qrcodegen/qrcodegen"]
|
||||
path = components/qrcodegen/qrcodegen
|
||||
url = https://github.com/nayuki/QR-Code-generator.git
|
||||
|
|
|
@ -153,6 +153,13 @@ config LUA_MODULE_OW
|
|||
help
|
||||
Includes the 1-Wire (ow) module (recommended).
|
||||
|
||||
config LUA_MODULE_QRCODEGEN
|
||||
bool "QR Code Generator module"
|
||||
default "n"
|
||||
help
|
||||
Includes the QR Code Generator from
|
||||
https://www.nayuki.io/page/qr-code-generator-library
|
||||
|
||||
config LUA_MODULE_SDMMC
|
||||
bool "SD-MMC module"
|
||||
default "n"
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
#include "lauxlib.h"
|
||||
#include "common.h"
|
||||
|
||||
// Like luaL_argerror but for options table
|
||||
int opt_error(lua_State *L, const char* name, const char *extramsg) {
|
||||
lua_Debug ar;
|
||||
if (!lua_getstack(L, 0, &ar)) /* no stack frame? */
|
||||
return luaL_error(L, "bad option %s (%s)", name, extramsg);
|
||||
lua_getinfo(L, "n", &ar);
|
||||
if (ar.name == NULL)
|
||||
ar.name = "?";
|
||||
return luaL_error(L, "bad option %s to " LUA_QS " (%s)",
|
||||
name, ar.name, extramsg);
|
||||
}
|
||||
|
||||
// Similar to luaL_argcheck() but using the options table rather than a raw index
|
||||
bool opt_get(lua_State *L, const char *name, int required_type)
|
||||
{
|
||||
if (!lua_istable(L, -1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lua_getfield(L, -1, name);
|
||||
int type = lua_type(L, -1);
|
||||
if (type == LUA_TNIL) {
|
||||
// Option not present
|
||||
lua_pop(L, 1);
|
||||
return false;
|
||||
}
|
||||
if (type != required_type) {
|
||||
const char* msg = lua_pushfstring(L, "%s expected, got %s",
|
||||
lua_typename(L, required_type),
|
||||
lua_typename(L, type));
|
||||
opt_error(L, name, msg);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int opt_checkint(lua_State *L, const char *name, int default_val)
|
||||
{
|
||||
if (opt_get(L, name, LUA_TNUMBER)) {
|
||||
int result = lua_tointeger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
return result;
|
||||
} else {
|
||||
return default_val;
|
||||
}
|
||||
}
|
||||
|
||||
int opt_checkint_range(lua_State *L, const char *name, int default_val, int min_val, int max_val)
|
||||
{
|
||||
if (opt_get(L, name, LUA_TNUMBER)) {
|
||||
int result = lua_tointeger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
if (!(result >= min_val && result <= max_val)) {
|
||||
const char* msg = lua_pushfstring(L, "must be in range %d-%d", min_val, max_val);
|
||||
opt_error(L, name, msg);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return default_val;
|
||||
}
|
||||
}
|
||||
|
||||
bool opt_checkbool(lua_State *L, const char *name, bool default_val)
|
||||
{
|
||||
if (opt_get(L, name, LUA_TBOOLEAN)) {
|
||||
int result = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
return !!result;
|
||||
} else {
|
||||
return default_val;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
#ifndef modules_common_h
|
||||
#define modules_common_h
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
/* Some common code shared between modules */
|
||||
|
||||
/* Fetch an optional int value from a table on the top of the stack. If the
|
||||
option name is not present or the table is nil, returns default_val. Errors
|
||||
if the key name is present in the table but is not an integer.
|
||||
*/
|
||||
int opt_checkint(lua_State *L, const char *name, int default_val);
|
||||
|
||||
/* Like the above, but also specifies a range which the value must be within */
|
||||
int opt_checkint_range(lua_State *L, const char *name, int default_val, int min_val, int max_val);
|
||||
|
||||
/* Fetch an optional bool value from a table on the top of the stack. If the
|
||||
option name is not present or the table is nil, returns default_val. Errors
|
||||
if the key name is present in the table but is not a boolean.
|
||||
*/
|
||||
bool opt_checkbool(lua_State *L, const char *name, bool default_val);
|
||||
|
||||
/* Like luaL_argerror() but producing a more suitable error message */
|
||||
int opt_error(lua_State *L, const char* name, const char *extramsg);
|
||||
|
||||
/* Returns true and pushes the value onto the stack, if name exists in the
|
||||
table on the top of the stack and is of type required_type. Errors if name
|
||||
is present but the wrong type. Returns false and pushes nothing if name is
|
||||
not present in the table, or the table is nil.
|
||||
*/
|
||||
bool opt_get(lua_State *L, const char *name, int required_type);
|
||||
|
||||
/* Macro like luaL_argcheck() for defining custom additional checks */
|
||||
#define opt_check(L, cond, name, extramsg) \
|
||||
((void)((cond) || opt_error(L, (name), (extramsg))))
|
||||
|
||||
#endif
|
|
@ -1,4 +1,5 @@
|
|||
#include "module.h"
|
||||
#include "common.h"
|
||||
#include "lauxlib.h"
|
||||
#include "lmem.h"
|
||||
#include <string.h>
|
||||
|
@ -406,55 +407,12 @@ static void set_headers(lua_State *L, int headers_idx, esp_http_client_handle_t
|
|||
}
|
||||
}
|
||||
|
||||
// Similar to luaL_argcheck() but using the options table rather than a raw index
|
||||
static bool get_option(lua_State *L, const char *name, int required_type)
|
||||
{
|
||||
if (!lua_istable(L, -1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
lua_getfield(L, -1, name);
|
||||
int type = lua_type(L, -1);
|
||||
if (type == LUA_TNIL) {
|
||||
// Option not present
|
||||
lua_pop(L, 1);
|
||||
return false;
|
||||
}
|
||||
if (type != required_type) {
|
||||
luaL_error(L, "Bad option '%s' to createConnection (%s expected, got %s)",
|
||||
name, lua_typename(L, required_type), lua_typename(L, type));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int check_optint(lua_State *L, const char *name, int default_val)
|
||||
{
|
||||
if (get_option(L, name, LUA_TNUMBER)) {
|
||||
int result = lua_tointeger(L, -1);
|
||||
lua_pop(L, 1);
|
||||
return result;
|
||||
} else {
|
||||
return default_val;
|
||||
}
|
||||
}
|
||||
|
||||
static bool check_optbool(lua_State *L, const char *name, bool default_val)
|
||||
{
|
||||
if (get_option(L, name, LUA_TBOOLEAN)) {
|
||||
int result = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
return !!result;
|
||||
} else {
|
||||
return default_val;
|
||||
}
|
||||
}
|
||||
|
||||
// Options assumed to be on top of stack
|
||||
static void parse_options(lua_State *L, lhttp_context_t *context, esp_http_client_config_t *config)
|
||||
{
|
||||
config->timeout_ms = check_optint(L, "timeout", 10*1000); // Same default as old http module
|
||||
config->buffer_size = check_optint(L, "bufsz", DEFAULT_HTTP_BUF_SIZE);
|
||||
int redirects = check_optint(L, "max_redirects", -1); // -1 means "not specified" here
|
||||
config->timeout_ms = opt_checkint(L, "timeout", 10*1000); // Same default as old http module
|
||||
config->buffer_size = opt_checkint(L, "bufsz", DEFAULT_HTTP_BUF_SIZE);
|
||||
int redirects = opt_checkint(L, "max_redirects", -1); // -1 means "not specified" here
|
||||
if (redirects == 0) {
|
||||
config->disable_auto_redirect = true;
|
||||
} else if (redirects > 0) {
|
||||
|
@ -463,9 +421,9 @@ static void parse_options(lua_State *L, lhttp_context_t *context, esp_http_clien
|
|||
// Note, config->is_async is always set to false regardless of what we set
|
||||
// the Async flag to, because of how we configure the tasks we always want
|
||||
// esp_http_client_perform to run in its 'is_async=false' mode.
|
||||
context_setflagbool(context, Async, check_optbool(L, "async", false));
|
||||
context_setflagbool(context, Async, opt_checkbool(L, "async", false));
|
||||
|
||||
if (get_option(L, "cert", LUA_TSTRING)) {
|
||||
if (opt_get(L, "cert", LUA_TSTRING)) {
|
||||
const char *cert = lua_tostring(L, -1);
|
||||
context_setref(L, context, CertRef);
|
||||
config->cert_pem = cert;
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
#include "module.h"
|
||||
#include "lauxlib.h"
|
||||
#include "common.h"
|
||||
#include "lmem.h"
|
||||
|
||||
#include "qrcodegen.h"
|
||||
|
||||
// qrcodegen.encodeText(text, [{minver=int, maxver=int, ecl=int, mask=int, boostecl=bool}])
|
||||
// all params except text are optional
|
||||
static int encodeText(lua_State *L)
|
||||
{
|
||||
lua_settop(L, 2);
|
||||
const char *text = luaL_checkstring(L, 1);
|
||||
|
||||
int min_ver = opt_checkint_range(L, "minver", qrcodegen_VERSION_MIN,
|
||||
qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX);
|
||||
|
||||
int max_ver = opt_checkint_range(L, "maxver", qrcodegen_VERSION_MAX,
|
||||
min_ver, qrcodegen_VERSION_MAX);
|
||||
|
||||
int ecl = opt_checkint_range(L, "ecl", qrcodegen_Ecc_LOW,
|
||||
qrcodegen_Ecc_LOW, qrcodegen_Ecc_HIGH);
|
||||
|
||||
int mask = opt_checkint_range(L, "mask", qrcodegen_Mask_AUTO,
|
||||
qrcodegen_Mask_AUTO, qrcodegen_Mask_7);
|
||||
|
||||
bool boost = opt_checkbool(L, "boostecl", false);
|
||||
|
||||
const size_t buf_len = qrcodegen_BUFFER_LEN_FOR_VERSION(max_ver);
|
||||
uint8_t *result = (uint8_t *)luaM_malloc(L, buf_len * 2);
|
||||
uint8_t *tempbuf = result + buf_len;
|
||||
|
||||
bool ok = qrcodegen_encodeText(text, tempbuf, result, ecl, min_ver, max_ver, mask, boost);
|
||||
if (ok) {
|
||||
lua_pushlstring(L, (const char *)result, buf_len);
|
||||
} else {
|
||||
lua_pushnil(L);
|
||||
}
|
||||
luaM_freemem(L, result, buf_len * 2);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int getSize(lua_State *L)
|
||||
{
|
||||
const uint8_t* data = (const uint8_t *)luaL_checkstring(L, 1);
|
||||
lua_pushinteger(L, qrcodegen_getSize(data));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int getPixel(lua_State *L)
|
||||
{
|
||||
const uint8_t* data = (const uint8_t *)luaL_checkstring(L, 1);
|
||||
int x = luaL_checkint(L, 2);
|
||||
int y = luaL_checkint(L, 3);
|
||||
lua_pushboolean(L, qrcodegen_getModule(data, x, y));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const LUA_REG_TYPE qrcodegen_map[] = {
|
||||
{ LSTRKEY("encodeText"), LFUNCVAL(encodeText) },
|
||||
{ LSTRKEY("getSize"), LFUNCVAL(getSize) },
|
||||
{ LSTRKEY("getPixel"), LFUNCVAL(getPixel) },
|
||||
{ LSTRKEY("LOW"), LNUMVAL(qrcodegen_Ecc_LOW) },
|
||||
{ LSTRKEY("MEDIUM"), LNUMVAL(qrcodegen_Ecc_MEDIUM) },
|
||||
{ LSTRKEY("QUARTILE"), LNUMVAL(qrcodegen_Ecc_QUARTILE) },
|
||||
{ LSTRKEY("HIGH"), LNUMVAL(qrcodegen_Ecc_HIGH) },
|
||||
{ LSTRKEY("AUTO"), LNUMVAL(qrcodegen_Mask_AUTO) },
|
||||
{ LNILKEY, LNILVAL }
|
||||
};
|
||||
|
||||
NODEMCU_MODULE(QRCODEGEN, "qrcodegen", qrcodegen_map, NULL);
|
|
@ -0,0 +1,3 @@
|
|||
COMPONENT_SRCDIRS:=qrcodegen/c
|
||||
COMPONENT_OBJS:=qrcodegen/c/qrcodegen.o
|
||||
COMPONENT_ADD_INCLUDEDIRS:=qrcodegen/c
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 3da57e5aa01ebc5c228fd9adfb1f5956c63d1648
|
|
@ -0,0 +1,69 @@
|
|||
# QR Code Generator Module
|
||||
| Since | Origin / Contributor | Maintainer | Source |
|
||||
| :----- | :-------------------- | :---------- | :------ |
|
||||
| 2018-10-20 | https://github.com/nayuki/QR-Code-generator.git integrated by [Tom Sutcliffe](https://github.com/tomsci) | [Tom Sutcliffe](https://github.com/tomsci) | [qrcodegen.c](../../../components/modules/qrcodegen.c)|
|
||||
|
||||
This module wraps the [QR Code](https://en.wikipedia.org/wiki/QR_code) Generator API written in C by https://github.com/nayuki/QR-Code-generator.git for producing a QR Code.
|
||||
|
||||
## qrcodegen.encodeText()
|
||||
Generates a QR Code from a text string. In the most optimistic case, a QR Code at version 40 with low ECC can hold any UTF-8 string up to 2953 bytes, or any alphanumeric string up to 4296 characters, or any digit string up to 7089 characters.
|
||||
|
||||
#### Syntax
|
||||
`qrcodegen.encodeText(text, [options])`
|
||||
|
||||
#### Parameters
|
||||
- `text` The text or URL to encode. Should be UTF-8 or ASCII.
|
||||
- `options` An optional table, containing any of:
|
||||
- `minver` the minimum version according to the QR Code Model 2 standard. If not specified, defaults to `1`.
|
||||
- `maxver` the maximum version according to the QR Code Model 2 standard. If not specified, defaults to `40`. Specifying a lower maximum version reduces the amount of temporary memory the function requires, so it can be worthwhile to specify a smaller value if you know the `text` will fit in a lower-version QR Code.
|
||||
- `ecl` the error correction level in a QR Code symbol. Higher error correction produces a larger QR Code. One of:
|
||||
- `qrcodegen.LOW` (default if not specified)
|
||||
- `qrcodegen.MEDIUM`
|
||||
- `qrcodegen.QUARTILE`
|
||||
- `qrcodegen.HIGH`
|
||||
- `mask` the mask pattern used in a QR Code symbol. An integer 0-7, or `qrcodegen.AUTO` (the default).
|
||||
- `boostecl` defaults to false.
|
||||
|
||||
#### Returns
|
||||
The QR Code, encoded as a string. Use `qrcodegen.getSize()` and `qrcodegen.getPixel()` to extract data from the result. If the text cannot be represented within the given version range (for example it is too long) then `nil` is returned.
|
||||
|
||||
#### Example
|
||||
```lua
|
||||
qrcode = qrcodegen.encodeText("https://nodemcu.readthedocs.io/", {minver=1, maxver=4})
|
||||
print("Size:", qrcodegen.getSize(qrcode))
|
||||
```
|
||||
|
||||
## qrcodegen.getSize()
|
||||
|
||||
#### Syntax
|
||||
`qrcodegen.getSize(qrcode)`
|
||||
|
||||
#### Parameters
|
||||
- `qrcode` a QR Code string, as returned by `qrcodegen.encodeText()`.
|
||||
|
||||
#### Returns
|
||||
Returns the side length in pixels of the given QR Code. The result is in the range [21, 177].
|
||||
|
||||
## qrcodegen.getPixel(qrcode, x, y)
|
||||
Get the color of the pixel at the given coordinates of the QR Code. `x` and `y` must be between `0` and the value returned by `qrcodegen.getSize()`.
|
||||
|
||||
#### Parameters
|
||||
- `qrcode` a QR Code string, as returned by `qrcodegen.encodeText()`.
|
||||
- `x`
|
||||
- `y`
|
||||
|
||||
#### Returns
|
||||
`true` if the given pixel is black, `false` if it is white.
|
||||
|
||||
#### Example
|
||||
```lua
|
||||
qrcode = qrcodegen.encodeText("https://nodemcu.readthedocs.io/", {maxver=4})
|
||||
size = qrcodegen.getSize(qrcode)
|
||||
for y = 0, size-1 do
|
||||
local row = {}
|
||||
for x = 0, size-1 do
|
||||
row[x + 1] = qrcodegen.getPixel(x, y) and "*" or " "
|
||||
end
|
||||
print(table.concat(row))
|
||||
end
|
||||
```
|
|
@ -48,6 +48,7 @@ pages:
|
|||
- 'net': 'en/modules/net.md'
|
||||
- 'node': 'en/modules/node.md'
|
||||
- 'ow (1-Wire)': 'en/modules/ow.md'
|
||||
- 'qrcodegen': 'en/modules/qrcodegen.md'
|
||||
- 'sdmmc': 'en/modules/sdmmc.md'
|
||||
- 'sigma delta': 'en/modules/sigma-delta.md'
|
||||
- 'sodium': 'en/modules/sodium.md'
|
||||
|
|
Loading…
Reference in New Issue