Added `cards` app

This app will be able to render cards as synchronized by Catima
pull/2977/head
Gabriele Monaco 2023-08-15 14:44:21 +02:00
parent 25251646c0
commit d5c445f472
14 changed files with 1125 additions and 0 deletions

9
apps/cards/Barcode.js Normal file
View File

@ -0,0 +1,9 @@
class Barcode{
constructor(data, options){
this.data = data;
this.text = options.text || data;
this.options = options;
}
}
module.exports = Barcode;

44
apps/cards/README.md Normal file
View File

@ -0,0 +1,44 @@
# Cards
Basic viewer for loyalty cards synced from Catima through GadgetBridge.
The app can display the cards' info (balance, expiration, note, etc.) and tapping on the appropriate field will display the code, if the type is supported.
Double tapping on the code will come back to the visualization of the card's details.
Beware that the small screen of the Banglejs 2 cannot render properly complex barcodes (in fact the resolution is very limited to render most barcodes).
### Supported codes types
* `CODE_39`
* `CODABAR`
* `QR_CODE`
### How to sync
_WIP: we currently cannot synchronize cards_
You can test it by sending on your bangle a file like this:
_android.cards.json_
```json
[
{
"id": 1,
"name": "First card",
"value": "01234",
"note": "Some stuff",
"type": "CODE_39",
"balance": "15 EUR",
"expiration": "1691102081"
},
{
"id": 2,
"name": "Second card",
"value": "Hello world",
"note": "This is a qr generated on the bangle!",
"type": "QR_CODE",
"balance": "2 P"
}
]
```

1
apps/cards/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4AYoIAjF/4v/F/4v/F/4v/FAdNAAsoADgv/F/4v/F/4vqu4AjF/4v/F/4v6poAjF/4AfFAYAGF/4v/F/4v/F/4v/F94A/AH4A/AH4A/ABo"))

183
apps/cards/app.js Normal file
View File

@ -0,0 +1,183 @@
/* CARDS is a list of:
{id:int,
name,
value,
type,
expiration,
color,
balance,
note,
...
}
*/
Bangle.loadWidgets();
Bangle.drawWidgets();
var FILE = "android.cards.json";
var Locale = require("locale");
var fontSmall = "6x8";
var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2";
var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2";
var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4";
var CARDS = require("Storage").readJSON("android.cards.json",true)||[];
var settings = require("Storage").readJSON("cards.settings.json",true)||{};
function getDate(timestamp) {
return new Date(timestamp*1000);
}
function formatDay(date) {
let formattedDate = Locale.dow(date,1) + " " + Locale.date(date).replace(/\d\d\d\d/,"");
if (!settings.useToday) {
return formattedDate;
}
const today = new Date(Date.now());
if (date.getDay() == today.getDay() && date.getMonth() == today.getMonth())
return /*LANG*/"Today ";
else {
const tomorrow = new Date(Date.now() + 86400 * 1000);
if (date.getDay() == tomorrow.getDay() && date.getMonth() == tomorrow.getMonth()) {
return /*LANG*/"Tomorrow ";
}
return formattedDate;
}
}
function printSquareCode(binary, size) {
var ratio = g.getWidth()/size;
for (var y = 0; y < size; y++) {
for (var x = 0; x < size; x++) {
if (binary[x + y * size]) {
g.setColor(g.theme.bg).fillRect({x:x*ratio, y:y*ratio, w:ratio, h:ratio});
} else {
g.setColor(g.theme.fg).fillRect({x:x*ratio, y:y*ratio, w:ratio, h:ratio});
}
}
}
}
function printLinearCode(binary) {
var yFrom = 0;
var width = g.getWidth()/binary.length;
for(var b = 0; b < binary.length; b++){
var x = b * width;
if(binary[b] === "1"){
g.setColor(g.theme.fg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight()});
}
else if(binary[b]){
g.setColor(g.theme.bg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight()});
}
}
}
function showCode(card) {
var code;
//FIXME doesn't work..
var listener = (data) => {
if(data.double) showCard(card);
Bangle.removeListener("tap", listener);
};
Bangle.on("tap", listener);
switch (card.type) {
case "QR_CODE":
const getBinaryQR = require("cards.qrcode.js");
code = getBinaryQR(card.value);
printSquareCode(code.data, code.size);
break;
case "CODE_39":
const CODE39 = require("cards.code39.js");
code = new CODE39(card.value, {});
printLinearCode(code.encode().data);
break;
case "CODABAR":
const codabar = require("cards.codabar.js");
code = new codabar(card.value, {});
printLinearCode(code.encode().data);
break;
default:
g.clear(true);
g.setFont("Vector:15");
g.setFontAlign(0,0);
g.drawString(card.value, g.getWidth()/2, g.getHeight()/2);
}
}
function showCard(card) {
var lines = [];
var bodyFont = fontBig;
if(!card) return;
g.setFont(bodyFont);
//var lines = [];
if (card.name) lines = g.wrapString(card.name, g.getWidth()-10);
var titleCnt = lines.length;
var start = getDate(card.expiration);
var includeDay = true;
if (titleCnt) lines.push(""); // add blank line after name
lines = lines.concat("", /*LANG*/"Tap here to see the value");
var valueLine = lines.length - 1;
if (card.expiration)
lines = lines.concat("",/*LANG*/"Expires"+": ", g.wrapString(formatDay(getDate(card.expiration)), g.getWidth()-10));
if(card.balance)
lines = lines.concat("",/*LANG*/"Balance"+": ", g.wrapString(card.balance, g.getWidth()-10));
if(card.note && card.note.trim())
lines = lines.concat("",g.wrapString(card.note, g.getWidth()-10));
lines = lines.concat("",/*LANG*/"< Back");
E.showScroller({
h : g.getFontHeight(), // height of each menu item in pixels
c : lines.length, // number of menu items
// a function to draw a menu item
draw : function(idx, r) {
// FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12
g.setBgColor(idx<titleCnt ? g.theme.bg2 : g.theme.bg).
setColor(idx<titleCnt ? g.theme.fg2 : g.theme.fg).
clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
g.setFont(bodyFont).drawString(lines[idx], r.x, r.y);
}, select : function(idx) {
if (idx>=lines.length-2)
showList();
if (idx>=valueLine)
showCode(card);
},
back : () => showList()
});
}
// https://github.com/metafloor/bwip-js
// https://github.com/lindell/JsBarcode
function showList() {
if(CARDS.length == 0) {
E.showMessage(/*LANG*/"No cards");
return;
}
E.showScroller({
h : 52,
c : Math.max(CARDS.length,3), // workaround for 2v10.219 firmware (min 3 not needed for 2v11)
draw : function(idx, r) {"ram"
var card = CARDS[idx];
g.setColor(g.theme.fg);
g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h);
if (!card) return;
var isPast = false;
var x = r.x+2, name = card.name;
var body = card.expiration ? formatDay(getDate(card.expiration)) : "";
if (card.balance) body += "\n" + card.balance;
if (name) g.setFontAlign(-1,-1).setFont(fontBig)
.setColor(isPast ? "#888" : g.theme.fg).drawString(name, x+4,r.y+2);
if (body) {
g.setFontAlign(-1,-1).setFont(fontMedium).setColor(isPast ? "#888" : g.theme.fg);
g.drawString(body, x+10,r.y+20);
}
g.setColor("#888").fillRect(r.x,r.y+r.h-1,r.x+r.w-1,r.y+r.h-1); // dividing line between items
if(card.color) {
g.setColor("#"+(0x1000000+Number(card.color)).toString(16).padStart(6,"0"));
g.fillRect(r.x,r.y+4,r.x+3, r.y+r.h-4);
}
},
select : idx => showCard(CARDS[idx]),
back : () => load()
});
}
showList();

BIN
apps/cards/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 B

63
apps/cards/codabar.js Normal file
View File

@ -0,0 +1,63 @@
// Encoding specification:
// http://www.barcodeisland.com/codabar.phtml
const Barcode = require("cards.Barcode.js");
class codabar extends Barcode{
constructor(data, options){
if (/^[0-9\-\$\:\.\+\/]+$/.test(data)) {
data = "A" + data + "A";
}
super(data.toUpperCase(), options);
this.text = this.options.text || this.text.replace(/[A-D]/g, '');
}
valid(){
return /^[A-D][0-9\-\$\:\.\+\/]+[A-D]$/.test(this.data)
}
encode(){
var result = [];
var encodings = this.getEncodings();
for(var i = 0; i < this.data.length; i++){
result.push(encodings[this.data.charAt(i)]);
// for all characters except the last, append a narrow-space ("0")
if (i !== this.data.length - 1) {
result.push("0");
}
}
return {
text: this.text,
data: result.join('')
};
}
getEncodings(){
return {
"0": "101010011",
"1": "101011001",
"2": "101001011",
"3": "110010101",
"4": "101101001",
"5": "110101001",
"6": "100101011",
"7": "100101101",
"8": "100110101",
"9": "110100101",
"-": "101001101",
"$": "101100101",
":": "1101011011",
"/": "1101101011",
".": "1101101101",
"+": "1011011011",
"A": "1011001001",
"B": "1001001011",
"C": "1010010011",
"D": "1010011001"
};
}
}
module.exports = codabar

105
apps/cards/code39.js Normal file
View File

@ -0,0 +1,105 @@
// Encoding documentation:
// https://en.wikipedia.org/wiki/Code_39#Encoding
const Barcode = require("cards.Barcode.js");
class CODE39 extends Barcode {
constructor(data, options){
data = data.toUpperCase();
// Calculate mod43 checksum if enabled
if(options.mod43){
data += getCharacter(mod43checksum(data));
}
super(data, options);
}
encode(){
// First character is always a *
var result = getEncoding("*");
// Take every character and add the binary representation to the result
for(let i = 0; i < this.data.length; i++){
result += getEncoding(this.data[i]) + "0";
}
// Last character is always a *
result += getEncoding("*");
return {
data: result,
text: this.text
};
}
valid(){
return /^[0-9A-Z\-\.\ \$\/\+\%]+$/.test(this.data);
}
}
// All characters. The position in the array is the (checksum) value
var characters = [
"0", "1", "2", "3",
"4", "5", "6", "7",
"8", "9", "A", "B",
"C", "D", "E", "F",
"G", "H", "I", "J",
"K", "L", "M", "N",
"O", "P", "Q", "R",
"S", "T", "U", "V",
"W", "X", "Y", "Z",
"-", ".", " ", "$",
"/", "+", "%", "*"
];
// The decimal representation of the characters, is converted to the
// corresponding binary with the getEncoding function
var encodings = [
20957, 29783, 23639, 30485,
20951, 29813, 23669, 20855,
29789, 23645, 29975, 23831,
30533, 22295, 30149, 24005,
21623, 29981, 23837, 22301,
30023, 23879, 30545, 22343,
30161, 24017, 21959, 30065,
23921, 22385, 29015, 18263,
29141, 17879, 29045, 18293,
17783, 29021, 18269, 17477,
17489, 17681, 20753, 35770
];
// Get the binary representation of a character by converting the encodings
// from decimal to binary
function getEncoding(character){
return getBinary(characterValue(character));
}
function getBinary(characterValue){
return encodings[characterValue].toString(2);
}
function getCharacter(characterValue){
return characters[characterValue];
}
function characterValue(character){
return characters.indexOf(character);
}
function mod43checksum(data){
var checksum = 0;
for(let i = 0; i < data.length; i++){
checksum += characterValue(data[i]);
}
checksum = checksum % 43;
return checksum;
}
module.exports = CODE39;

21
apps/cards/metadata.json Normal file
View File

@ -0,0 +1,21 @@
{
"id": "cards",
"name": "Cards",
"version": "0.1",
"description": "Display loyalty cards",
"icon": "app.png",
"screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_event1.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}],
"tags": "cards",
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"cards.app.js","url":"app.js"},
{"name":"cards.settings.js","url":"settings.js"},
{"name":"cards.Barcode.js","url":"Barcode.js"},
{"name":"cards.qrcode.js","url":"qrcode.js"},
{"name":"cards.codabar.js","url":"codabar.js"},
{"name":"cards.code39.js","url":"code39.js"},
{"name":"cards.img","url":"app-icon.js","evaluate":true}
],
"data": [{"name":"cards.settings.json"}]
}

675
apps/cards/qrcode.js Normal file
View File

@ -0,0 +1,675 @@
var c = E.compiledC(`
// int get_qr(int, int)
typedef signed char __int8_t;
typedef unsigned char __uint8_t;
typedef signed short int __int16_t;
typedef unsigned short int __uint16_t;
typedef signed int __int32_t;
typedef unsigned int __uint32_t;
typedef __int8_t int8_t;
typedef __int16_t int16_t;
typedef __int32_t int32_t;
typedef __uint8_t uint8_t;
typedef __uint16_t uint16_t;
typedef __uint32_t uint32_t;
typedef struct QRCode {
uint8_t version;
uint8_t size;
uint8_t ecc;
uint8_t mode;
uint8_t mask;
uint8_t *modules;
} QRCode;
uint16_t qrcode_getBufferSize(uint8_t version);
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data);
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length);
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y);
static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = {
{ 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372},
{ 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750},
{ 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430},
{ 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040},
};
static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = {
{ 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49},
{ 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25},
{ 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81},
{ 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68},
};
static const uint16_t NUM_RAW_DATA_MODULES[40] = {
208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523,
7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587,
19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648
};
static int max(int a, int b) {
if (a > b) { return a; }
return b;
}
static int abs(int value) {
if (value < 0) { return -value; }
return value;
}
static void *memset(void *s, int c, int n) {
char *arr = (char *)s;
for (int i = 0; i<n; i++) {
arr[i] = c;
}
return arr;
}
static void *memcpy(void *dest, const void *src, int n) {
char *arr1 = (char *)dest;
const char *arr2 = (char *)src;
for (int i = 0; i<n; i++) {
arr1[i] = arr2[i];
}
return arr1;
}
int strlen(const char *s) {
int i = 0;
while (s[i]) ++i;
return i;
}
static int8_t getAlphanumeric(char c) {
if (c >= '0' && c <= '9') { return (c - '0'); }
if (c >= 'A' && c <= 'Z') { return (c - 'A' + 10); }
switch (c) {
case ' ': return 36;
case '$': return 37;
case '%': return 38;
case '*': return 39;
case '+': return 40;
case '-': return 41;
case '.': return 42;
case '/': return 43;
case ':': return 44;
}
return -1;
}
static bool isAlphanumeric(const char *text, uint16_t length) {
while (length != 0) {
if (getAlphanumeric(text[--length]) == -1) { return false; }
}
return true;
}
static bool isNumeric(const char *text, uint16_t length) {
while (length != 0) {
char c = text[--length];
if (c < '0' || c > '9') { return false; }
}
return true;
}
static char getModeBits(uint8_t version, uint8_t mode) {
unsigned int modeInfo = 0x7bbb80a;
if (version > 9) { modeInfo >>= 9; }
if (version > 26) { modeInfo >>= 9; }
char result = 8 + ((modeInfo >> (3 * mode)) & 0x07);
if (result == 15) { result = 16; }
return result;
}
typedef struct BitBucket {
uint32_t bitOffsetOrWidth;
uint16_t capacityBytes;
uint8_t *data;
} BitBucket;
static uint16_t bb_getGridSizeBytes(uint8_t size) {
return (((size * size) + 7) / 8);
}
static uint16_t bb_getBufferSizeBytes(uint32_t bits) {
return ((bits + 7) / 8);
}
static void bb_initBuffer(BitBucket *bitBuffer, uint8_t *data, int32_t capacityBytes) {
bitBuffer->bitOffsetOrWidth = 0;
bitBuffer->capacityBytes = capacityBytes;
bitBuffer->data = data;
memset(data, 0, bitBuffer->capacityBytes);
}
static void bb_initGrid(BitBucket *bitGrid, uint8_t *data, uint8_t size) {
bitGrid->bitOffsetOrWidth = size;
bitGrid->capacityBytes = bb_getGridSizeBytes(size);
bitGrid->data = data;
memset(data, 0, bitGrid->capacityBytes);
}
static void bb_appendBits(BitBucket *bitBuffer, uint32_t val, uint8_t length) {
uint32_t offset = bitBuffer->bitOffsetOrWidth;
for (int8_t i = length - 1; i >= 0; i--, offset++) {
bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7));
}
bitBuffer->bitOffsetOrWidth = offset;
}
static void bb_setBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool on) {
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
uint8_t mask = 1 << (7 - (offset & 0x07));
if (on) {
bitGrid->data[offset >> 3] |= mask;
} else {
bitGrid->data[offset >> 3] &= ~mask;
}
}
static void bb_invertBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool invert) {
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
uint8_t mask = 1 << (7 - (offset & 0x07));
bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0);
if (on ^ invert) {
bitGrid->data[offset >> 3] |= mask;
} else {
bitGrid->data[offset >> 3] &= ~mask;
}
}
static bool bb_getBit(BitBucket *bitGrid, uint8_t x, uint8_t y) {
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
}
static void applyMask(BitBucket *modules, BitBucket *isFunction, uint8_t mask) {
uint8_t size = modules->bitOffsetOrWidth;
for (uint8_t y = 0; y < size; y++) {
for (uint8_t x = 0; x < size; x++) {
if (bb_getBit(isFunction, x, y)) { continue; }
bool invert = 0;
switch (mask) {
case 0: invert = (x + y) % 2 == 0; break;
case 1: invert = y % 2 == 0; break;
case 2: invert = x % 3 == 0; break;
case 3: invert = (x + y) % 3 == 0; break;
case 4: invert = (x / 3 + y / 2) % 2 == 0; break;
case 5: invert = x * y % 2 + x * y % 3 == 0; break;
case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break;
case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break;
}
bb_invertBit(modules, x, y, invert);
}
}
}
static void setFunctionModule(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y, bool on) {
bb_setBit(modules, x, y, on);
bb_setBit(isFunction, x, y, true);
}
static void drawFinderPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
uint8_t size = modules->bitOffsetOrWidth;
for (int8_t i = -4; i <= 4; i++) {
for (int8_t j = -4; j <= 4; j++) {
uint8_t dist = max(abs(i), abs(j));
int16_t xx = x + j, yy = y + i;
if (0 <= xx && xx < size && 0 <= yy && yy < size) {
setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4);
}
}
}
}
static void drawAlignmentPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
for (int8_t i = -2; i <= 2; i++) {
for (int8_t j = -2; j <= 2; j++) {
setFunctionModule(modules, isFunction, x + j, y + i, max(abs(i), abs(j)) != 1);
}
}
}
static void drawFormatBits(BitBucket *modules, BitBucket *isFunction, uint8_t ecc, uint8_t mask) {
uint8_t size = modules->bitOffsetOrWidth;
uint32_t data = ecc << 3 | mask;
uint32_t rem = data;
for (int i = 0; i < 10; i++) {
rem = (rem << 1) ^ ((rem >> 9) * 0x537);
}
data = data << 10 | rem;
data ^= 0x5412;
for (uint8_t i = 0; i <= 5; i++) {
setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0);
}
setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0);
setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0);
setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0);
for (int8_t i = 9; i < 15; i++) {
setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0);
}
for (int8_t i = 0; i <= 7; i++) {
setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0);
}
for (int8_t i = 8; i < 15; i++) {
setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0);
}
setFunctionModule(modules, isFunction, 8, size - 8, true);
}
static void drawVersion(BitBucket *modules, BitBucket *isFunction, uint8_t version) {
int8_t size = modules->bitOffsetOrWidth;
if (version < 7) { return; }
uint32_t rem = version;
for (uint8_t i = 0; i < 12; i++) {
rem = (rem << 1) ^ ((rem >> 11) * 0x1F25);
}
uint32_t data = version << 12 | rem;
for (uint8_t i = 0; i < 18; i++) {
bool bit = ((data >> i) & 1) != 0;
uint8_t a = size - 11 + i % 3, b = i / 3;
setFunctionModule(modules, isFunction, a, b, bit);
setFunctionModule(modules, isFunction, b, a, bit);
}
}
static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction, uint8_t version, uint8_t ecc) {
uint8_t size = modules->bitOffsetOrWidth;
for (uint8_t i = 0; i < size; i++) {
setFunctionModule(modules, isFunction, 6, i, i % 2 == 0);
setFunctionModule(modules, isFunction, i, 6, i % 2 == 0);
}
drawFinderPattern(modules, isFunction, 3, 3);
drawFinderPattern(modules, isFunction, size - 4, 3);
drawFinderPattern(modules, isFunction, 3, size - 4);
if (version > 1) {
uint8_t alignCount = version / 7 + 2;
uint8_t step;
if (version != 32) {
step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2;
} else {
step = 26;
}
uint8_t alignPositionIndex = alignCount - 1;
uint8_t alignPosition[alignCount];
alignPosition[0] = 6;
uint8_t l_size = version * 4 + 17;
for (uint8_t i = 0, pos = l_size - 7; i < alignCount - 1; i++, pos -= step) {
alignPosition[alignPositionIndex--] = pos;
}
for (uint8_t i = 0; i < alignCount; i++) {
for (uint8_t j = 0; j < alignCount; j++) {
if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) {
continue;
} else {
drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]);
}
}
}
}
drawFormatBits(modules, isFunction, ecc, 0);
drawVersion(modules, isFunction, version);
}
static void drawCodewords(BitBucket *modules, BitBucket *isFunction, BitBucket *codewords) {
uint32_t bitLength = codewords->bitOffsetOrWidth;
uint8_t *data = codewords->data;
uint8_t size = modules->bitOffsetOrWidth;
uint32_t i = 0;
for (int16_t right = size - 1; right >= 1; right -= 2) {
if (right == 6) { right = 5; }
for (uint8_t vert = 0; vert < size; vert++) {
for (int j = 0; j < 2; j++) {
uint8_t x = right - j;
bool upwards = ((right & 2) == 0) ^ (x < 6);
uint8_t y = upwards ? size - 1 - vert : vert;
if (!bb_getBit(isFunction, x, y) && i < bitLength) {
bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0);
i++;
}
}
}
}
}
static uint32_t getPenaltyScore(BitBucket *modules) {
uint32_t result = 0;
uint8_t size = modules->bitOffsetOrWidth;
for (uint8_t y = 0; y < size; y++) {
bool colorX = bb_getBit(modules, 0, y);
for (uint8_t x = 1, runX = 1; x < size; x++) {
bool cx = bb_getBit(modules, x, y);
if (cx != colorX) {
colorX = cx;
runX = 1;
} else {
runX++;
if (runX == 5) {
result += 3;
} else if (runX > 5) {
result++;
}
}
}
}
for (uint8_t x = 0; x < size; x++) {
bool colorY = bb_getBit(modules, x, 0);
for (uint8_t y = 1, runY = 1; y < size; y++) {
bool cy = bb_getBit(modules, x, y);
if (cy != colorY) {
colorY = cy;
runY = 1;
} else {
runY++;
if (runY == 5) {
result += 3;
} else if (runY > 5) {
result++;
}
}
}
}
uint16_t black = 0;
for (uint8_t y = 0; y < size; y++) {
uint16_t bitsRow = 0, bitsCol = 0;
for (uint8_t x = 0; x < size; x++) {
bool color = bb_getBit(modules, x, y);
if (x > 0 && y > 0) {
bool colorUL = bb_getBit(modules, x - 1, y - 1);
bool colorUR = bb_getBit(modules, x, y - 1);
bool colorL = bb_getBit(modules, x - 1, y);
if (color == colorUL && color == colorUR && color == colorL) {
result += 3;
}
}
bitsRow = ((bitsRow << 1) & 0x7FF) | color;
bitsCol = ((bitsCol << 1) & 0x7FF) | bb_getBit(modules, y, x);
if (x >= 10) {
if (bitsRow == 0x05D || bitsRow == 0x5D0) {
result += 40;
}
if (bitsCol == 0x05D || bitsCol == 0x5D0) {
result += 40;
}
}
if (color) { black++; }
}
}
uint16_t total = size * size;
for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) {
result += 10;
}
return result;
}
static uint8_t rs_multiply(uint8_t x, uint8_t y) {
uint16_t z = 0;
for (int8_t i = 7; i >= 0; i--) {
z = (z << 1) ^ ((z >> 7) * 0x11D);
z ^= ((y >> i) & 1) * x;
}
return z;
}
static void rs_init(uint8_t degree, uint8_t *coeff) {
memset(coeff, 0, degree);
coeff[degree - 1] = 1;
uint16_t root = 1;
for (uint8_t i = 0; i < degree; i++) {
for (uint8_t j = 0; j < degree; j++) {
coeff[j] = rs_multiply(coeff[j], root);
if (j + 1 < degree) {
coeff[j] ^= coeff[j + 1];
}
}
root = (root << 1) ^ ((root >> 7) * 0x11D);
}
}
static void rs_getRemainder(uint8_t degree, uint8_t *coeff, const uint8_t *data, uint8_t length, uint8_t *result, uint8_t stride) {
for (uint8_t i = 0; i < length; i++) {
uint8_t factor = data[i] ^ result[0];
for (uint8_t j = 1; j < degree; j++) {
result[(j - 1) * stride] = result[j * stride];
}
result[(degree - 1) * stride] = 0;
for (uint8_t j = 0; j < degree; j++) {
result[j * stride] ^= rs_multiply(coeff[j], factor);
}
}
}
static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, uint16_t length, uint8_t version) {
int8_t mode = 2;
if (isNumeric((char*)text, length)) {
mode = 0;
bb_appendBits(dataCodewords, 1 << 0, 4);
bb_appendBits(dataCodewords, length, getModeBits(version, 0));
uint16_t accumData = 0;
uint8_t accumCount = 0;
for (uint16_t i = 0; i < length; i++) {
accumData = accumData * 10 + ((char)(text[i]) - '0');
accumCount++;
if (accumCount == 3) {
bb_appendBits(dataCodewords, accumData, 10);
accumData = 0;
accumCount = 0;
}
}
if (accumCount > 0) {
bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1);
}
} else if (isAlphanumeric((char*)text, length)) {
mode = 1;
bb_appendBits(dataCodewords, 1 << 1, 4);
bb_appendBits(dataCodewords, length, getModeBits(version, 1));
uint16_t accumData = 0;
uint8_t accumCount = 0;
for (uint16_t i = 0; i < length; i++) {
accumData = accumData * 45 + getAlphanumeric((char)(text[i]));
accumCount++;
if (accumCount == 2) {
bb_appendBits(dataCodewords, accumData, 11);
accumData = 0;
accumCount = 0;
}
}
if (accumCount > 0) {
bb_appendBits(dataCodewords, accumData, 6);
}
} else {
bb_appendBits(dataCodewords, 1 << 2, 4);
bb_appendBits(dataCodewords, length, getModeBits(version, 2));
for (uint16_t i = 0; i < length; i++) {
bb_appendBits(dataCodewords, (char)(text[i]), 8);
}
}
return mode;
}
static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket *data) {
uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1];
uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1];
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
uint8_t blockEccLen = totalEcc / numBlocks;
uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks;
uint8_t shortBlockLen = moduleCount / 8 / numBlocks;
uint8_t shortDataBlockLen = shortBlockLen - blockEccLen;
uint8_t result[data->capacityBytes];
memset(result, 0, sizeof(result));
uint8_t coeff[blockEccLen];
rs_init(blockEccLen, coeff);
uint16_t offset = 0;
uint8_t *dataBytes = data->data;
for (uint8_t i = 0; i < shortDataBlockLen; i++) {
uint16_t index = i;
uint8_t stride = shortDataBlockLen;
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
result[offset++] = dataBytes[index];
if (blockNum == numShortBlocks) { stride++; }
index += stride;
}
}
{
uint16_t index = shortDataBlockLen * (numShortBlocks + 1);
uint8_t stride = shortDataBlockLen;
for (uint8_t blockNum = 0; blockNum < numBlocks - numShortBlocks; blockNum++) {
result[offset++] = dataBytes[index];
if (blockNum == 0) { stride++; }
index += stride;
}
}
uint8_t blockSize = shortDataBlockLen;
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
if (blockNum == numShortBlocks) { blockSize++; }
rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks);
dataBytes += blockSize;
}
memcpy(data->data, result, data->capacityBytes);
data->bitOffsetOrWidth = moduleCount;
}
static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0);
uint16_t qrcode_getBufferSize(uint8_t version) {
return bb_getGridSizeBytes(4 * version + 17);
}
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length) {
uint8_t size = version * 4 + 17;
qrcode->version = version;
qrcode->size = size;
qrcode->ecc = ecc;
qrcode->modules = modules;
uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03;
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1];
struct BitBucket codewords;
uint8_t codewordBytes[bb_getBufferSizeBytes(moduleCount)];
bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes));
int8_t mode = encodeDataCodewords(&codewords, data, length, version);
if (mode < 0) { return -1; }
qrcode->mode = mode;
uint32_t padding = (dataCapacity * 8) - codewords.bitOffsetOrWidth;
if (padding > 4) { padding = 4; }
bb_appendBits(&codewords, 0, padding);
bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8);
for (uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) {
bb_appendBits(&codewords, padByte, 8);
}
BitBucket modulesGrid;
bb_initGrid(&modulesGrid, modules, size);
BitBucket isFunctionGrid;
uint8_t isFunctionGridBytes[bb_getGridSizeBytes(size)];
bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size);
drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits);
performErrorCorrection(version, eccFormatBits, &codewords);
drawCodewords(&modulesGrid, &isFunctionGrid, &codewords);
uint8_t mask = 0;
int32_t minPenalty = (2147483647);
for (uint8_t i = 0; i < 8; i++) {
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i);
applyMask(&modulesGrid, &isFunctionGrid, i);
int penalty = getPenaltyScore(&modulesGrid);
if (penalty < minPenalty) {
mask = i;
minPenalty = penalty;
}
applyMask(&modulesGrid, &isFunctionGrid, i);
}
qrcode->mask = mask;
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask);
applyMask(&modulesGrid, &isFunctionGrid, mask);
return 0;
}
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data) {
return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data));
}
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) {
if (x >= qrcode->size || y >= qrcode->size) {
return false;
}
uint32_t offset = y * qrcode->size + x;
return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
}
int get_qr (char *string, uint8_t *qrcodeBitmap) {
// The structure to manage the QR code
QRCode qrcode;
// Allocate a chunk of memory to store the QR code
uint8_t qrcodeBytes[qrcode_getBufferSize(3)];
qrcode_initText(&qrcode, qrcodeBytes, 3, 0, string);
for (uint8_t y = 0; y < qrcode.size; y++) {
for (uint8_t x = 0; x < qrcode.size; x++) {
qrcodeBitmap[x + y * qrcode.size] = qrcode_getModule(&qrcode, x, y);
}
}
return qrcode.size;
}
`);
function getBinaryQR (value) {
var qrcodeBitmap = new Uint8Array(850);
var flatValue = Uint8Array(E.toArrayBuffer(E.toFlatString(value ,0)));
var valueAddr = E.getAddressOf(flatValue, true);
var qrAddr = E.getAddressOf(qrcodeBitmap, true);
if (valueAddr == 0 || qrAddr == 0) {
console.log ("Failed to get flat arrays..");
//return;
}
var qrsize = c.get_qr(valueAddr, qrAddr);
return { data: qrcodeBitmap, size: qrsize };
}
module.exports = getBinaryQR;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

24
apps/cards/settings.js Normal file
View File

@ -0,0 +1,24 @@
(function(back) {
function gbSend(message) {
Bluetooth.println("");
Bluetooth.println(JSON.stringify(message));
}
var settings = require("Storage").readJSON("cards.settings.json",1)||{};
function updateSettings() {
require("Storage").writeJSON("cards.settings.json", settings);
}
var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[];
var mainmenu = {
"" : { "title" : "Cards" },
"< Back" : back,
/*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?/*LANG*/"Yes":/*LANG*/"No" },
/*LANG*/"Use 'Today',..." : {
value : !!settings.useToday,
onchange: v => {
settings.useToday = v;
updateSettings();
}
},
};
E.showMenu(mainmenu);
})