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"]
|
[submodule "components/ucg/ucg"]
|
||||||
path = components/ucg/ucg
|
path = components/ucg/ucg
|
||||||
url = https://github.com/olikraus/Ucglib_Arduino.git
|
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
|
help
|
||||||
Includes the 1-Wire (ow) module (recommended).
|
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
|
config LUA_MODULE_SDMMC
|
||||||
bool "SD-MMC module"
|
bool "SD-MMC module"
|
||||||
default "n"
|
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 "module.h"
|
||||||
|
#include "common.h"
|
||||||
#include "lauxlib.h"
|
#include "lauxlib.h"
|
||||||
#include "lmem.h"
|
#include "lmem.h"
|
||||||
#include <string.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
|
// 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)
|
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->timeout_ms = opt_checkint(L, "timeout", 10*1000); // Same default as old http module
|
||||||
config->buffer_size = check_optint(L, "bufsz", DEFAULT_HTTP_BUF_SIZE);
|
config->buffer_size = opt_checkint(L, "bufsz", DEFAULT_HTTP_BUF_SIZE);
|
||||||
int redirects = check_optint(L, "max_redirects", -1); // -1 means "not specified" here
|
int redirects = opt_checkint(L, "max_redirects", -1); // -1 means "not specified" here
|
||||||
if (redirects == 0) {
|
if (redirects == 0) {
|
||||||
config->disable_auto_redirect = true;
|
config->disable_auto_redirect = true;
|
||||||
} else if (redirects > 0) {
|
} 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
|
// 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
|
// 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.
|
// 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);
|
const char *cert = lua_tostring(L, -1);
|
||||||
context_setref(L, context, CertRef);
|
context_setref(L, context, CertRef);
|
||||||
config->cert_pem = cert;
|
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'
|
- 'net': 'en/modules/net.md'
|
||||||
- 'node': 'en/modules/node.md'
|
- 'node': 'en/modules/node.md'
|
||||||
- 'ow (1-Wire)': 'en/modules/ow.md'
|
- 'ow (1-Wire)': 'en/modules/ow.md'
|
||||||
|
- 'qrcodegen': 'en/modules/qrcodegen.md'
|
||||||
- 'sdmmc': 'en/modules/sdmmc.md'
|
- 'sdmmc': 'en/modules/sdmmc.md'
|
||||||
- 'sigma delta': 'en/modules/sigma-delta.md'
|
- 'sigma delta': 'en/modules/sigma-delta.md'
|
||||||
- 'sodium': 'en/modules/sodium.md'
|
- 'sodium': 'en/modules/sodium.md'
|
||||||
|
|
Loading…
Reference in New Issue