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:
tomsci 2018-11-15 21:17:43 +00:00 committed by Arnim Läuger
parent 1cb1aff4cd
commit 72d28fa86e
10 changed files with 272 additions and 48 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -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"

View File

@ -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;
}
}

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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
```

View File

@ -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'