discstation 0.1.38 → 0.1.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
#include <ESPmDNS.h>
|
|
9
9
|
#include <Preferences.h>
|
|
10
10
|
#include <ArduinoOTA.h>
|
|
11
|
-
#include
|
|
11
|
+
#include "qrcode_lite.h"
|
|
12
12
|
|
|
13
13
|
// Optional build-time Wi-Fi override for power users: copy secrets.h.example
|
|
14
14
|
// to secrets.h (git-ignored) and set WIFI_SSID / WIFI_PASS / OTA_PASSWORD.
|
|
@@ -0,0 +1,872 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MIT License (MIT)
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2017 Richard Moore
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in
|
|
14
|
+
* all copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
* THE SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Special thanks to Nayuki (https://www.nayuki.io/) from which this library was
|
|
27
|
+
* heavily inspired and compared against.
|
|
28
|
+
*
|
|
29
|
+
* See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
#include "qrcode_lite.h"
|
|
33
|
+
|
|
34
|
+
#include <stdlib.h>
|
|
35
|
+
#include <string.h>
|
|
36
|
+
|
|
37
|
+
#pragma mark - Error Correction Lookup tables
|
|
38
|
+
|
|
39
|
+
#if LOCK_VERSION == 0
|
|
40
|
+
|
|
41
|
+
static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = {
|
|
42
|
+
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
|
|
43
|
+
{ 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}, // Medium
|
|
44
|
+
{ 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}, // Low
|
|
45
|
+
{ 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}, // High
|
|
46
|
+
{ 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}, // Quartile
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = {
|
|
50
|
+
// Version: (note that index 0 is for padding, and is set to an illegal value)
|
|
51
|
+
// 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
|
|
52
|
+
{ 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}, // Medium
|
|
53
|
+
{ 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}, // Low
|
|
54
|
+
{ 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}, // High
|
|
55
|
+
{ 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}, // Quartile
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
static const uint16_t NUM_RAW_DATA_MODULES[40] = {
|
|
59
|
+
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
|
|
60
|
+
208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523,
|
|
61
|
+
// 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
|
|
62
|
+
7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587,
|
|
63
|
+
// 32, 33, 34, 35, 36, 37, 38, 39, 40
|
|
64
|
+
19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// @TODO: Put other LOCK_VERSIONS here
|
|
68
|
+
#elif LOCK_VERSION == 3
|
|
69
|
+
|
|
70
|
+
static const int16_t NUM_ERROR_CORRECTION_CODEWORDS[4] = {
|
|
71
|
+
26, 15, 44, 36
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
static const int8_t NUM_ERROR_CORRECTION_BLOCKS[4] = {
|
|
75
|
+
1, 1, 2, 2
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
static const uint16_t NUM_RAW_DATA_MODULES = 567;
|
|
79
|
+
|
|
80
|
+
#else
|
|
81
|
+
|
|
82
|
+
#error Unsupported LOCK_VERSION (add it...)
|
|
83
|
+
|
|
84
|
+
#endif
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
static int max(int a, int b) {
|
|
88
|
+
if (a > b) { return a; }
|
|
89
|
+
return b;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/*
|
|
93
|
+
static int abs(int value) {
|
|
94
|
+
if (value < 0) { return -value; }
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
*/
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
#pragma mark - Mode testing and conversion
|
|
101
|
+
|
|
102
|
+
static int8_t getAlphanumeric(char c) {
|
|
103
|
+
|
|
104
|
+
if (c >= '0' && c <= '9') { return (c - '0'); }
|
|
105
|
+
if (c >= 'A' && c <= 'Z') { return (c - 'A' + 10); }
|
|
106
|
+
|
|
107
|
+
switch (c) {
|
|
108
|
+
case ' ': return 36;
|
|
109
|
+
case '$': return 37;
|
|
110
|
+
case '%': return 38;
|
|
111
|
+
case '*': return 39;
|
|
112
|
+
case '+': return 40;
|
|
113
|
+
case '-': return 41;
|
|
114
|
+
case '.': return 42;
|
|
115
|
+
case '/': return 43;
|
|
116
|
+
case ':': return 44;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return -1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static bool isAlphanumeric(const char *text, uint16_t length) {
|
|
123
|
+
while (length != 0) {
|
|
124
|
+
if (getAlphanumeric(text[--length]) == -1) { return false; }
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
static bool isNumeric(const char *text, uint16_t length) {
|
|
131
|
+
while (length != 0) {
|
|
132
|
+
char c = text[--length];
|
|
133
|
+
if (c < '0' || c > '9') { return false; }
|
|
134
|
+
}
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
#pragma mark - Counting
|
|
140
|
+
|
|
141
|
+
// We store the following tightly packed (less 8) in modeInfo
|
|
142
|
+
// <=9 <=26 <= 40
|
|
143
|
+
// NUMERIC ( 10, 12, 14);
|
|
144
|
+
// ALPHANUMERIC ( 9, 11, 13);
|
|
145
|
+
// BYTE ( 8, 16, 16);
|
|
146
|
+
static char getModeBits(uint8_t version, uint8_t mode) {
|
|
147
|
+
// Note: We use 15 instead of 16; since 15 doesn't exist and we cannot store 16 (8 + 8) in 3 bits
|
|
148
|
+
// hex(int("".join(reversed([('00' + bin(x - 8)[2:])[-3:] for x in [10, 9, 8, 12, 11, 15, 14, 13, 15]])), 2))
|
|
149
|
+
unsigned int modeInfo = 0x7bbb80a;
|
|
150
|
+
|
|
151
|
+
#if LOCK_VERSION == 0 || LOCK_VERSION > 9
|
|
152
|
+
if (version > 9) { modeInfo >>= 9; }
|
|
153
|
+
#endif
|
|
154
|
+
|
|
155
|
+
#if LOCK_VERSION == 0 || LOCK_VERSION > 26
|
|
156
|
+
if (version > 26) { modeInfo >>= 9; }
|
|
157
|
+
#endif
|
|
158
|
+
|
|
159
|
+
char result = 8 + ((modeInfo >> (3 * mode)) & 0x07);
|
|
160
|
+
if (result == 15) { result = 16; }
|
|
161
|
+
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
#pragma mark - BitBucket
|
|
167
|
+
|
|
168
|
+
typedef struct BitBucket {
|
|
169
|
+
uint32_t bitOffsetOrWidth;
|
|
170
|
+
uint16_t capacityBytes;
|
|
171
|
+
uint8_t *data;
|
|
172
|
+
} BitBucket;
|
|
173
|
+
|
|
174
|
+
/*
|
|
175
|
+
void bb_dump(BitBucket *bitBuffer) {
|
|
176
|
+
printf("Buffer: ");
|
|
177
|
+
for (uint32_t i = 0; i < bitBuffer->capacityBytes; i++) {
|
|
178
|
+
printf("%02x", bitBuffer->data[i]);
|
|
179
|
+
if ((i % 4) == 3) { printf(" "); }
|
|
180
|
+
}
|
|
181
|
+
printf("\n");
|
|
182
|
+
}
|
|
183
|
+
*/
|
|
184
|
+
|
|
185
|
+
static uint16_t bb_getGridSizeBytes(uint8_t size) {
|
|
186
|
+
return (((size * size) + 7) / 8);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
static uint16_t bb_getBufferSizeBytes(uint32_t bits) {
|
|
190
|
+
return ((bits + 7) / 8);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
static void bb_initBuffer(BitBucket *bitBuffer, uint8_t *data, int32_t capacityBytes) {
|
|
194
|
+
bitBuffer->bitOffsetOrWidth = 0;
|
|
195
|
+
bitBuffer->capacityBytes = capacityBytes;
|
|
196
|
+
bitBuffer->data = data;
|
|
197
|
+
|
|
198
|
+
memset(data, 0, bitBuffer->capacityBytes);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
static void bb_initGrid(BitBucket *bitGrid, uint8_t *data, uint8_t size) {
|
|
202
|
+
bitGrid->bitOffsetOrWidth = size;
|
|
203
|
+
bitGrid->capacityBytes = bb_getGridSizeBytes(size);
|
|
204
|
+
bitGrid->data = data;
|
|
205
|
+
|
|
206
|
+
memset(data, 0, bitGrid->capacityBytes);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
static void bb_appendBits(BitBucket *bitBuffer, uint32_t val, uint8_t length) {
|
|
210
|
+
uint32_t offset = bitBuffer->bitOffsetOrWidth;
|
|
211
|
+
for (int8_t i = length - 1; i >= 0; i--, offset++) {
|
|
212
|
+
bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7));
|
|
213
|
+
}
|
|
214
|
+
bitBuffer->bitOffsetOrWidth = offset;
|
|
215
|
+
}
|
|
216
|
+
/*
|
|
217
|
+
void bb_setBits(BitBucket *bitBuffer, uint32_t val, int offset, uint8_t length) {
|
|
218
|
+
for (int8_t i = length - 1; i >= 0; i--, offset++) {
|
|
219
|
+
bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
*/
|
|
223
|
+
static void bb_setBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool on) {
|
|
224
|
+
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
|
|
225
|
+
uint8_t mask = 1 << (7 - (offset & 0x07));
|
|
226
|
+
if (on) {
|
|
227
|
+
bitGrid->data[offset >> 3] |= mask;
|
|
228
|
+
} else {
|
|
229
|
+
bitGrid->data[offset >> 3] &= ~mask;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
static void bb_invertBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool invert) {
|
|
234
|
+
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
|
|
235
|
+
uint8_t mask = 1 << (7 - (offset & 0x07));
|
|
236
|
+
bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0);
|
|
237
|
+
if (on ^ invert) {
|
|
238
|
+
bitGrid->data[offset >> 3] |= mask;
|
|
239
|
+
} else {
|
|
240
|
+
bitGrid->data[offset >> 3] &= ~mask;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
static bool bb_getBit(BitBucket *bitGrid, uint8_t x, uint8_t y) {
|
|
245
|
+
uint32_t offset = y * bitGrid->bitOffsetOrWidth + x;
|
|
246
|
+
return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
#pragma mark - Drawing Patterns
|
|
251
|
+
|
|
252
|
+
// XORs the data modules in this QR Code with the given mask pattern. Due to XOR's mathematical
|
|
253
|
+
// properties, calling applyMask(m) twice with the same value is equivalent to no change at all.
|
|
254
|
+
// This means it is possible to apply a mask, undo it, and try another mask. Note that a final
|
|
255
|
+
// well-formed QR Code symbol needs exactly one mask applied (not zero, not two, etc.).
|
|
256
|
+
static void applyMask(BitBucket *modules, BitBucket *isFunction, uint8_t mask) {
|
|
257
|
+
uint8_t size = modules->bitOffsetOrWidth;
|
|
258
|
+
|
|
259
|
+
for (uint8_t y = 0; y < size; y++) {
|
|
260
|
+
for (uint8_t x = 0; x < size; x++) {
|
|
261
|
+
if (bb_getBit(isFunction, x, y)) { continue; }
|
|
262
|
+
|
|
263
|
+
bool invert = 0;
|
|
264
|
+
switch (mask) {
|
|
265
|
+
case 0: invert = (x + y) % 2 == 0; break;
|
|
266
|
+
case 1: invert = y % 2 == 0; break;
|
|
267
|
+
case 2: invert = x % 3 == 0; break;
|
|
268
|
+
case 3: invert = (x + y) % 3 == 0; break;
|
|
269
|
+
case 4: invert = (x / 3 + y / 2) % 2 == 0; break;
|
|
270
|
+
case 5: invert = x * y % 2 + x * y % 3 == 0; break;
|
|
271
|
+
case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break;
|
|
272
|
+
case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break;
|
|
273
|
+
}
|
|
274
|
+
bb_invertBit(modules, x, y, invert);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
static void setFunctionModule(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y, bool on) {
|
|
280
|
+
bb_setBit(modules, x, y, on);
|
|
281
|
+
bb_setBit(isFunction, x, y, true);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Draws a 9*9 finder pattern including the border separator, with the center module at (x, y).
|
|
285
|
+
static void drawFinderPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
|
|
286
|
+
uint8_t size = modules->bitOffsetOrWidth;
|
|
287
|
+
|
|
288
|
+
for (int8_t i = -4; i <= 4; i++) {
|
|
289
|
+
for (int8_t j = -4; j <= 4; j++) {
|
|
290
|
+
uint8_t dist = max(abs(i), abs(j)); // Chebyshev/infinity norm
|
|
291
|
+
int16_t xx = x + j, yy = y + i;
|
|
292
|
+
if (0 <= xx && xx < size && 0 <= yy && yy < size) {
|
|
293
|
+
setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Draws a 5*5 alignment pattern, with the center module at (x, y).
|
|
300
|
+
static void drawAlignmentPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) {
|
|
301
|
+
for (int8_t i = -2; i <= 2; i++) {
|
|
302
|
+
for (int8_t j = -2; j <= 2; j++) {
|
|
303
|
+
setFunctionModule(modules, isFunction, x + j, y + i, max(abs(i), abs(j)) != 1);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Draws two copies of the format bits (with its own error correction code)
|
|
309
|
+
// based on the given mask and this object's error correction level field.
|
|
310
|
+
static void drawFormatBits(BitBucket *modules, BitBucket *isFunction, uint8_t ecc, uint8_t mask) {
|
|
311
|
+
|
|
312
|
+
uint8_t size = modules->bitOffsetOrWidth;
|
|
313
|
+
|
|
314
|
+
// Calculate error correction code and pack bits
|
|
315
|
+
uint32_t data = ecc << 3 | mask; // errCorrLvl is uint2, mask is uint3
|
|
316
|
+
uint32_t rem = data;
|
|
317
|
+
for (int i = 0; i < 10; i++) {
|
|
318
|
+
rem = (rem << 1) ^ ((rem >> 9) * 0x537);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
data = data << 10 | rem;
|
|
322
|
+
data ^= 0x5412; // uint15
|
|
323
|
+
|
|
324
|
+
// Draw first copy
|
|
325
|
+
for (uint8_t i = 0; i <= 5; i++) {
|
|
326
|
+
setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0);
|
|
330
|
+
setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0);
|
|
331
|
+
setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0);
|
|
332
|
+
|
|
333
|
+
for (int8_t i = 9; i < 15; i++) {
|
|
334
|
+
setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Draw second copy
|
|
338
|
+
for (int8_t i = 0; i <= 7; i++) {
|
|
339
|
+
setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
for (int8_t i = 8; i < 15; i++) {
|
|
343
|
+
setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
setFunctionModule(modules, isFunction, 8, size - 8, true);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
// Draws two copies of the version bits (with its own error correction code),
|
|
351
|
+
// based on this object's version field (which only has an effect for 7 <= version <= 40).
|
|
352
|
+
static void drawVersion(BitBucket *modules, BitBucket *isFunction, uint8_t version) {
|
|
353
|
+
|
|
354
|
+
int8_t size = modules->bitOffsetOrWidth;
|
|
355
|
+
|
|
356
|
+
#if LOCK_VERSION != 0 && LOCK_VERSION < 7
|
|
357
|
+
return;
|
|
358
|
+
|
|
359
|
+
#else
|
|
360
|
+
if (version < 7) { return; }
|
|
361
|
+
|
|
362
|
+
// Calculate error correction code and pack bits
|
|
363
|
+
uint32_t rem = version; // version is uint6, in the range [7, 40]
|
|
364
|
+
for (uint8_t i = 0; i < 12; i++) {
|
|
365
|
+
rem = (rem << 1) ^ ((rem >> 11) * 0x1F25);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
uint32_t data = version << 12 | rem; // uint18
|
|
369
|
+
|
|
370
|
+
// Draw two copies
|
|
371
|
+
for (uint8_t i = 0; i < 18; i++) {
|
|
372
|
+
bool bit = ((data >> i) & 1) != 0;
|
|
373
|
+
uint8_t a = size - 11 + i % 3, b = i / 3;
|
|
374
|
+
setFunctionModule(modules, isFunction, a, b, bit);
|
|
375
|
+
setFunctionModule(modules, isFunction, b, a, bit);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
#endif
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction, uint8_t version, uint8_t ecc) {
|
|
382
|
+
|
|
383
|
+
uint8_t size = modules->bitOffsetOrWidth;
|
|
384
|
+
|
|
385
|
+
// Draw the horizontal and vertical timing patterns
|
|
386
|
+
for (uint8_t i = 0; i < size; i++) {
|
|
387
|
+
setFunctionModule(modules, isFunction, 6, i, i % 2 == 0);
|
|
388
|
+
setFunctionModule(modules, isFunction, i, 6, i % 2 == 0);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
|
|
392
|
+
drawFinderPattern(modules, isFunction, 3, 3);
|
|
393
|
+
drawFinderPattern(modules, isFunction, size - 4, 3);
|
|
394
|
+
drawFinderPattern(modules, isFunction, 3, size - 4);
|
|
395
|
+
|
|
396
|
+
#if LOCK_VERSION == 0 || LOCK_VERSION > 1
|
|
397
|
+
|
|
398
|
+
if (version > 1) {
|
|
399
|
+
|
|
400
|
+
// Draw the numerous alignment patterns
|
|
401
|
+
|
|
402
|
+
uint8_t alignCount = version / 7 + 2;
|
|
403
|
+
uint8_t step;
|
|
404
|
+
if (version != 32) {
|
|
405
|
+
step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2; // ceil((size - 13) / (2*numAlign - 2)) * 2
|
|
406
|
+
} else { // C-C-C-Combo breaker!
|
|
407
|
+
step = 26;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
uint8_t alignPositionIndex = alignCount - 1;
|
|
411
|
+
uint8_t alignPosition[alignCount];
|
|
412
|
+
|
|
413
|
+
alignPosition[0] = 6;
|
|
414
|
+
|
|
415
|
+
uint8_t size = version * 4 + 17;
|
|
416
|
+
for (uint8_t i = 0, pos = size - 7; i < alignCount - 1; i++, pos -= step) {
|
|
417
|
+
alignPosition[alignPositionIndex--] = pos;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
for (uint8_t i = 0; i < alignCount; i++) {
|
|
421
|
+
for (uint8_t j = 0; j < alignCount; j++) {
|
|
422
|
+
if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) {
|
|
423
|
+
continue; // Skip the three finder corners
|
|
424
|
+
} else {
|
|
425
|
+
drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
#endif
|
|
432
|
+
|
|
433
|
+
// Draw configuration data
|
|
434
|
+
drawFormatBits(modules, isFunction, ecc, 0); // Dummy mask value; overwritten later in the constructor
|
|
435
|
+
drawVersion(modules, isFunction, version);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire
|
|
440
|
+
// data area of this QR Code symbol. Function modules need to be marked off before this is called.
|
|
441
|
+
static void drawCodewords(BitBucket *modules, BitBucket *isFunction, BitBucket *codewords) {
|
|
442
|
+
|
|
443
|
+
uint32_t bitLength = codewords->bitOffsetOrWidth;
|
|
444
|
+
uint8_t *data = codewords->data;
|
|
445
|
+
|
|
446
|
+
uint8_t size = modules->bitOffsetOrWidth;
|
|
447
|
+
|
|
448
|
+
// Bit index into the data
|
|
449
|
+
uint32_t i = 0;
|
|
450
|
+
|
|
451
|
+
// Do the funny zigzag scan
|
|
452
|
+
for (int16_t right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair
|
|
453
|
+
if (right == 6) { right = 5; }
|
|
454
|
+
|
|
455
|
+
for (uint8_t vert = 0; vert < size; vert++) { // Vertical counter
|
|
456
|
+
for (int j = 0; j < 2; j++) {
|
|
457
|
+
uint8_t x = right - j; // Actual x coordinate
|
|
458
|
+
bool upwards = ((right & 2) == 0) ^ (x < 6);
|
|
459
|
+
uint8_t y = upwards ? size - 1 - vert : vert; // Actual y coordinate
|
|
460
|
+
if (!bb_getBit(isFunction, x, y) && i < bitLength) {
|
|
461
|
+
bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0);
|
|
462
|
+
i++;
|
|
463
|
+
}
|
|
464
|
+
// If there are any remainder bits (0 to 7), they are already
|
|
465
|
+
// set to 0/false/white when the grid of modules was initialized
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
#pragma mark - Penalty Calculation
|
|
474
|
+
|
|
475
|
+
#define PENALTY_N1 3
|
|
476
|
+
#define PENALTY_N2 3
|
|
477
|
+
#define PENALTY_N3 40
|
|
478
|
+
#define PENALTY_N4 10
|
|
479
|
+
|
|
480
|
+
// Calculates and returns the penalty score based on state of this QR Code's current modules.
|
|
481
|
+
// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
|
|
482
|
+
// @TODO: This can be optimized by working with the bytes instead of bits.
|
|
483
|
+
static uint32_t getPenaltyScore(BitBucket *modules) {
|
|
484
|
+
uint32_t result = 0;
|
|
485
|
+
|
|
486
|
+
uint8_t size = modules->bitOffsetOrWidth;
|
|
487
|
+
|
|
488
|
+
// Adjacent modules in row having same color
|
|
489
|
+
for (uint8_t y = 0; y < size; y++) {
|
|
490
|
+
|
|
491
|
+
bool colorX = bb_getBit(modules, 0, y);
|
|
492
|
+
for (uint8_t x = 1, runX = 1; x < size; x++) {
|
|
493
|
+
bool cx = bb_getBit(modules, x, y);
|
|
494
|
+
if (cx != colorX) {
|
|
495
|
+
colorX = cx;
|
|
496
|
+
runX = 1;
|
|
497
|
+
|
|
498
|
+
} else {
|
|
499
|
+
runX++;
|
|
500
|
+
if (runX == 5) {
|
|
501
|
+
result += PENALTY_N1;
|
|
502
|
+
} else if (runX > 5) {
|
|
503
|
+
result++;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Adjacent modules in column having same color
|
|
510
|
+
for (uint8_t x = 0; x < size; x++) {
|
|
511
|
+
bool colorY = bb_getBit(modules, x, 0);
|
|
512
|
+
for (uint8_t y = 1, runY = 1; y < size; y++) {
|
|
513
|
+
bool cy = bb_getBit(modules, x, y);
|
|
514
|
+
if (cy != colorY) {
|
|
515
|
+
colorY = cy;
|
|
516
|
+
runY = 1;
|
|
517
|
+
} else {
|
|
518
|
+
runY++;
|
|
519
|
+
if (runY == 5) {
|
|
520
|
+
result += PENALTY_N1;
|
|
521
|
+
} else if (runY > 5) {
|
|
522
|
+
result++;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
uint16_t black = 0;
|
|
529
|
+
for (uint8_t y = 0; y < size; y++) {
|
|
530
|
+
uint16_t bitsRow = 0, bitsCol = 0;
|
|
531
|
+
for (uint8_t x = 0; x < size; x++) {
|
|
532
|
+
bool color = bb_getBit(modules, x, y);
|
|
533
|
+
|
|
534
|
+
// 2*2 blocks of modules having same color
|
|
535
|
+
if (x > 0 && y > 0) {
|
|
536
|
+
bool colorUL = bb_getBit(modules, x - 1, y - 1);
|
|
537
|
+
bool colorUR = bb_getBit(modules, x, y - 1);
|
|
538
|
+
bool colorL = bb_getBit(modules, x - 1, y);
|
|
539
|
+
if (color == colorUL && color == colorUR && color == colorL) {
|
|
540
|
+
result += PENALTY_N2;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Finder-like pattern in rows and columns
|
|
545
|
+
bitsRow = ((bitsRow << 1) & 0x7FF) | color;
|
|
546
|
+
bitsCol = ((bitsCol << 1) & 0x7FF) | bb_getBit(modules, y, x);
|
|
547
|
+
|
|
548
|
+
// Needs 11 bits accumulated
|
|
549
|
+
if (x >= 10) {
|
|
550
|
+
if (bitsRow == 0x05D || bitsRow == 0x5D0) {
|
|
551
|
+
result += PENALTY_N3;
|
|
552
|
+
}
|
|
553
|
+
if (bitsCol == 0x05D || bitsCol == 0x5D0) {
|
|
554
|
+
result += PENALTY_N3;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Balance of black and white modules
|
|
559
|
+
if (color) { black++; }
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Find smallest k such that (45-5k)% <= dark/total <= (55+5k)%
|
|
564
|
+
uint16_t total = size * size;
|
|
565
|
+
for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) {
|
|
566
|
+
result += PENALTY_N4;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return result;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
#pragma mark - Reed-Solomon Generator
|
|
574
|
+
|
|
575
|
+
static uint8_t rs_multiply(uint8_t x, uint8_t y) {
|
|
576
|
+
// Russian peasant multiplication
|
|
577
|
+
// See: https://en.wikipedia.org/wiki/Ancient_Egyptian_multiplication
|
|
578
|
+
uint16_t z = 0;
|
|
579
|
+
for (int8_t i = 7; i >= 0; i--) {
|
|
580
|
+
z = (z << 1) ^ ((z >> 7) * 0x11D);
|
|
581
|
+
z ^= ((y >> i) & 1) * x;
|
|
582
|
+
}
|
|
583
|
+
return z;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
static void rs_init(uint8_t degree, uint8_t *coeff) {
|
|
587
|
+
memset(coeff, 0, degree);
|
|
588
|
+
coeff[degree - 1] = 1;
|
|
589
|
+
|
|
590
|
+
// Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
|
|
591
|
+
// drop the highest term, and store the rest of the coefficients in order of descending powers.
|
|
592
|
+
// Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
|
|
593
|
+
uint16_t root = 1;
|
|
594
|
+
for (uint8_t i = 0; i < degree; i++) {
|
|
595
|
+
// Multiply the current product by (x - r^i)
|
|
596
|
+
for (uint8_t j = 0; j < degree; j++) {
|
|
597
|
+
coeff[j] = rs_multiply(coeff[j], root);
|
|
598
|
+
if (j + 1 < degree) {
|
|
599
|
+
coeff[j] ^= coeff[j + 1];
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
root = (root << 1) ^ ((root >> 7) * 0x11D); // Multiply by 0x02 mod GF(2^8/0x11D)
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
static void rs_getRemainder(uint8_t degree, uint8_t *coeff, uint8_t *data, uint8_t length, uint8_t *result, uint8_t stride) {
|
|
607
|
+
// Compute the remainder by performing polynomial division
|
|
608
|
+
|
|
609
|
+
//for (uint8_t i = 0; i < degree; i++) { result[] = 0; }
|
|
610
|
+
//memset(result, 0, degree);
|
|
611
|
+
|
|
612
|
+
for (uint8_t i = 0; i < length; i++) {
|
|
613
|
+
uint8_t factor = data[i] ^ result[0];
|
|
614
|
+
for (uint8_t j = 1; j < degree; j++) {
|
|
615
|
+
result[(j - 1) * stride] = result[j * stride];
|
|
616
|
+
}
|
|
617
|
+
result[(degree - 1) * stride] = 0;
|
|
618
|
+
|
|
619
|
+
for (uint8_t j = 0; j < degree; j++) {
|
|
620
|
+
result[j * stride] ^= rs_multiply(coeff[j], factor);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
#pragma mark - QrCode
|
|
628
|
+
|
|
629
|
+
static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, uint16_t length, uint8_t version) {
|
|
630
|
+
int8_t mode = MODE_BYTE;
|
|
631
|
+
|
|
632
|
+
if (isNumeric((char*)text, length)) {
|
|
633
|
+
mode = MODE_NUMERIC;
|
|
634
|
+
bb_appendBits(dataCodewords, 1 << MODE_NUMERIC, 4);
|
|
635
|
+
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_NUMERIC));
|
|
636
|
+
|
|
637
|
+
uint16_t accumData = 0;
|
|
638
|
+
uint8_t accumCount = 0;
|
|
639
|
+
for (uint16_t i = 0; i < length; i++) {
|
|
640
|
+
accumData = accumData * 10 + ((char)(text[i]) - '0');
|
|
641
|
+
accumCount++;
|
|
642
|
+
if (accumCount == 3) {
|
|
643
|
+
bb_appendBits(dataCodewords, accumData, 10);
|
|
644
|
+
accumData = 0;
|
|
645
|
+
accumCount = 0;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// 1 or 2 digits remaining
|
|
650
|
+
if (accumCount > 0) {
|
|
651
|
+
bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
} else if (isAlphanumeric((char*)text, length)) {
|
|
655
|
+
mode = MODE_ALPHANUMERIC;
|
|
656
|
+
bb_appendBits(dataCodewords, 1 << MODE_ALPHANUMERIC, 4);
|
|
657
|
+
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_ALPHANUMERIC));
|
|
658
|
+
|
|
659
|
+
uint16_t accumData = 0;
|
|
660
|
+
uint8_t accumCount = 0;
|
|
661
|
+
for (uint16_t i = 0; i < length; i++) {
|
|
662
|
+
accumData = accumData * 45 + getAlphanumeric((char)(text[i]));
|
|
663
|
+
accumCount++;
|
|
664
|
+
if (accumCount == 2) {
|
|
665
|
+
bb_appendBits(dataCodewords, accumData, 11);
|
|
666
|
+
accumData = 0;
|
|
667
|
+
accumCount = 0;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// 1 character remaining
|
|
672
|
+
if (accumCount > 0) {
|
|
673
|
+
bb_appendBits(dataCodewords, accumData, 6);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
} else {
|
|
677
|
+
bb_appendBits(dataCodewords, 1 << MODE_BYTE, 4);
|
|
678
|
+
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_BYTE));
|
|
679
|
+
for (uint16_t i = 0; i < length; i++) {
|
|
680
|
+
bb_appendBits(dataCodewords, (char)(text[i]), 8);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
//bb_setBits(dataCodewords, length, 4, getModeBits(version, mode));
|
|
685
|
+
|
|
686
|
+
return mode;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket *data) {
|
|
690
|
+
|
|
691
|
+
// See: http://www.thonky.com/qr-code-tutorial/structure-final-message
|
|
692
|
+
|
|
693
|
+
#if LOCK_VERSION == 0
|
|
694
|
+
uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1];
|
|
695
|
+
uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1];
|
|
696
|
+
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
|
|
697
|
+
#else
|
|
698
|
+
uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc];
|
|
699
|
+
uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc];
|
|
700
|
+
uint16_t moduleCount = NUM_RAW_DATA_MODULES;
|
|
701
|
+
#endif
|
|
702
|
+
|
|
703
|
+
uint8_t blockEccLen = totalEcc / numBlocks;
|
|
704
|
+
uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks;
|
|
705
|
+
uint8_t shortBlockLen = moduleCount / 8 / numBlocks;
|
|
706
|
+
|
|
707
|
+
uint8_t shortDataBlockLen = shortBlockLen - blockEccLen;
|
|
708
|
+
|
|
709
|
+
uint8_t result[data->capacityBytes];
|
|
710
|
+
memset(result, 0, sizeof(result));
|
|
711
|
+
|
|
712
|
+
uint8_t coeff[blockEccLen];
|
|
713
|
+
rs_init(blockEccLen, coeff);
|
|
714
|
+
|
|
715
|
+
uint16_t offset = 0;
|
|
716
|
+
uint8_t *dataBytes = data->data;
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
// Interleave all short blocks
|
|
720
|
+
for (uint8_t i = 0; i < shortDataBlockLen; i++) {
|
|
721
|
+
uint16_t index = i;
|
|
722
|
+
uint8_t stride = shortDataBlockLen;
|
|
723
|
+
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
|
|
724
|
+
result[offset++] = dataBytes[index];
|
|
725
|
+
|
|
726
|
+
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
|
|
727
|
+
if (blockNum == numShortBlocks) { stride++; }
|
|
728
|
+
#endif
|
|
729
|
+
index += stride;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Version less than 5 only have short blocks
|
|
734
|
+
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
|
|
735
|
+
{
|
|
736
|
+
// Interleave long blocks
|
|
737
|
+
uint16_t index = shortDataBlockLen * (numShortBlocks + 1);
|
|
738
|
+
uint8_t stride = shortDataBlockLen;
|
|
739
|
+
for (uint8_t blockNum = 0; blockNum < numBlocks - numShortBlocks; blockNum++) {
|
|
740
|
+
result[offset++] = dataBytes[index];
|
|
741
|
+
|
|
742
|
+
if (blockNum == 0) { stride++; }
|
|
743
|
+
index += stride;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
#endif
|
|
747
|
+
|
|
748
|
+
// Add all ecc blocks, interleaved
|
|
749
|
+
uint8_t blockSize = shortDataBlockLen;
|
|
750
|
+
for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) {
|
|
751
|
+
|
|
752
|
+
#if LOCK_VERSION == 0 || LOCK_VERSION >= 5
|
|
753
|
+
if (blockNum == numShortBlocks) { blockSize++; }
|
|
754
|
+
#endif
|
|
755
|
+
rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks);
|
|
756
|
+
dataBytes += blockSize;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
memcpy(data->data, result, data->capacityBytes);
|
|
760
|
+
data->bitOffsetOrWidth = moduleCount;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// We store the Format bits tightly packed into a single byte (each of the 4 modes is 2 bits)
|
|
764
|
+
// The format bits can be determined by ECC_FORMAT_BITS >> (2 * ecc)
|
|
765
|
+
static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0);
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
#pragma mark - Public QRCode functions
|
|
769
|
+
|
|
770
|
+
uint16_t qrcode_getBufferSize(uint8_t version) {
|
|
771
|
+
return bb_getGridSizeBytes(4 * version + 17);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// @TODO: Return error if data is too big.
|
|
775
|
+
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length) {
|
|
776
|
+
uint8_t size = version * 4 + 17;
|
|
777
|
+
qrcode->version = version;
|
|
778
|
+
qrcode->size = size;
|
|
779
|
+
qrcode->ecc = ecc;
|
|
780
|
+
qrcode->modules = modules;
|
|
781
|
+
|
|
782
|
+
uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03;
|
|
783
|
+
|
|
784
|
+
#if LOCK_VERSION == 0
|
|
785
|
+
uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1];
|
|
786
|
+
uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1];
|
|
787
|
+
#else
|
|
788
|
+
version = LOCK_VERSION;
|
|
789
|
+
uint16_t moduleCount = NUM_RAW_DATA_MODULES;
|
|
790
|
+
uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits];
|
|
791
|
+
#endif
|
|
792
|
+
|
|
793
|
+
struct BitBucket codewords;
|
|
794
|
+
uint8_t codewordBytes[bb_getBufferSizeBytes(moduleCount)];
|
|
795
|
+
bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes));
|
|
796
|
+
|
|
797
|
+
// Place the data code words into the buffer
|
|
798
|
+
int8_t mode = encodeDataCodewords(&codewords, data, length, version);
|
|
799
|
+
|
|
800
|
+
if (mode < 0) { return -1; }
|
|
801
|
+
qrcode->mode = mode;
|
|
802
|
+
|
|
803
|
+
// Add terminator and pad up to a byte if applicable
|
|
804
|
+
uint32_t padding = (dataCapacity * 8) - codewords.bitOffsetOrWidth;
|
|
805
|
+
if (padding > 4) { padding = 4; }
|
|
806
|
+
bb_appendBits(&codewords, 0, padding);
|
|
807
|
+
bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8);
|
|
808
|
+
|
|
809
|
+
// Pad with alternate bytes until data capacity is reached
|
|
810
|
+
for (uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) {
|
|
811
|
+
bb_appendBits(&codewords, padByte, 8);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
BitBucket modulesGrid;
|
|
815
|
+
bb_initGrid(&modulesGrid, modules, size);
|
|
816
|
+
|
|
817
|
+
BitBucket isFunctionGrid;
|
|
818
|
+
uint8_t isFunctionGridBytes[bb_getGridSizeBytes(size)];
|
|
819
|
+
bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size);
|
|
820
|
+
|
|
821
|
+
// Draw function patterns, draw all codewords, do masking
|
|
822
|
+
drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits);
|
|
823
|
+
performErrorCorrection(version, eccFormatBits, &codewords);
|
|
824
|
+
drawCodewords(&modulesGrid, &isFunctionGrid, &codewords);
|
|
825
|
+
|
|
826
|
+
// Find the best (lowest penalty) mask
|
|
827
|
+
uint8_t mask = 0;
|
|
828
|
+
int32_t minPenalty = INT32_MAX;
|
|
829
|
+
for (uint8_t i = 0; i < 8; i++) {
|
|
830
|
+
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i);
|
|
831
|
+
applyMask(&modulesGrid, &isFunctionGrid, i);
|
|
832
|
+
int penalty = getPenaltyScore(&modulesGrid);
|
|
833
|
+
if (penalty < minPenalty) {
|
|
834
|
+
mask = i;
|
|
835
|
+
minPenalty = penalty;
|
|
836
|
+
}
|
|
837
|
+
applyMask(&modulesGrid, &isFunctionGrid, i); // Undoes the mask due to XOR
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
qrcode->mask = mask;
|
|
841
|
+
|
|
842
|
+
// Overwrite old format bits
|
|
843
|
+
drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask);
|
|
844
|
+
|
|
845
|
+
// Apply the final choice of mask
|
|
846
|
+
applyMask(&modulesGrid, &isFunctionGrid, mask);
|
|
847
|
+
|
|
848
|
+
return 0;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data) {
|
|
852
|
+
return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data));
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) {
|
|
856
|
+
if (x < 0 || x >= qrcode->size || y < 0 || y >= qrcode->size) {
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
uint32_t offset = y * qrcode->size + x;
|
|
861
|
+
return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/*
|
|
865
|
+
uint8_t qrcode_getHexLength(QRCode *qrcode) {
|
|
866
|
+
return ((qrcode->size * qrcode->size) + 7) / 4;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
void qrcode_getHex(QRCode *qrcode, char *result) {
|
|
870
|
+
|
|
871
|
+
}
|
|
872
|
+
*/
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MIT License (MIT)
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) 2017 Richard Moore
|
|
5
|
+
*
|
|
6
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
* in the Software without restriction, including without limitation the rights
|
|
9
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
* furnished to do so, subject to the following conditions:
|
|
12
|
+
*
|
|
13
|
+
* The above copyright notice and this permission notice shall be included in
|
|
14
|
+
* all copies or substantial portions of the Software.
|
|
15
|
+
*
|
|
16
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
* THE SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Special thanks to Nayuki (https://www.nayuki.io/) from which this library was
|
|
27
|
+
* heavily inspired and compared against.
|
|
28
|
+
*
|
|
29
|
+
* See: https://github.com/nayuki/QR-Code-generator/tree/master/cpp
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
#ifndef __QRCODE_H_
|
|
34
|
+
#define __QRCODE_H_
|
|
35
|
+
|
|
36
|
+
#ifndef __cplusplus
|
|
37
|
+
typedef unsigned char bool;
|
|
38
|
+
static const bool false = 0;
|
|
39
|
+
static const bool true = 1;
|
|
40
|
+
#endif
|
|
41
|
+
|
|
42
|
+
#include <stdint.h>
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
// QR Code Format Encoding
|
|
46
|
+
#define MODE_NUMERIC 0
|
|
47
|
+
#define MODE_ALPHANUMERIC 1
|
|
48
|
+
#define MODE_BYTE 2
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
// Error Correction Code Levels
|
|
52
|
+
#define ECC_LOW 0
|
|
53
|
+
#define ECC_MEDIUM 1
|
|
54
|
+
#define ECC_QUARTILE 2
|
|
55
|
+
#define ECC_HIGH 3
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
// If set to non-zero, this library can ONLY produce QR codes at that version
|
|
59
|
+
// This saves a lot of dynamic memory, as the codeword tables are skipped
|
|
60
|
+
#ifndef LOCK_VERSION
|
|
61
|
+
#define LOCK_VERSION 0
|
|
62
|
+
#endif
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
typedef struct QRCode {
|
|
66
|
+
uint8_t version;
|
|
67
|
+
uint8_t size;
|
|
68
|
+
uint8_t ecc;
|
|
69
|
+
uint8_t mode;
|
|
70
|
+
uint8_t mask;
|
|
71
|
+
uint8_t *modules;
|
|
72
|
+
} QRCode;
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
#ifdef __cplusplus
|
|
76
|
+
extern "C"{
|
|
77
|
+
#endif /* __cplusplus */
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
uint16_t qrcode_getBufferSize(uint8_t version);
|
|
82
|
+
|
|
83
|
+
int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data);
|
|
84
|
+
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length);
|
|
85
|
+
|
|
86
|
+
bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y);
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
#ifdef __cplusplus
|
|
91
|
+
}
|
|
92
|
+
#endif /* __cplusplus */
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
#endif /* __QRCODE_H_ */
|
package/package.json
CHANGED
package/src/discstation_host.py
CHANGED
|
@@ -184,6 +184,22 @@ def _mac_optical_device():
|
|
|
184
184
|
|
|
185
185
|
_last_disc_device = None
|
|
186
186
|
|
|
187
|
+
|
|
188
|
+
class NoMediaError(FileNotFoundError):
|
|
189
|
+
"""The optical drive is present but has no disc it can see."""
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _mac_drive_name():
|
|
193
|
+
"""Drive model from ioreg, which lists it whether or not a disc is loaded
|
|
194
|
+
(the /dev/diskN node only exists while media is)."""
|
|
195
|
+
try:
|
|
196
|
+
out = subprocess.run(["/usr/sbin/ioreg", "-r", "-c", "IODVDServices", "-l"],
|
|
197
|
+
capture_output=True, text=True, check=False, timeout=3).stdout
|
|
198
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
199
|
+
return None
|
|
200
|
+
m = re.search(r'"Product Name"="([^"]+)"', out)
|
|
201
|
+
return m.group(1) if m else None
|
|
202
|
+
|
|
187
203
|
# --- Windows: IMAPI2 / WMI probes via bundled PowerShell helpers ---------------
|
|
188
204
|
_WIN_DIR = Path(__file__).resolve().parent / "win"
|
|
189
205
|
_win_info_cache = (0.0, None)
|
|
@@ -259,6 +275,9 @@ def disc_device():
|
|
|
259
275
|
# known node so the caller can still probe it (it just reports no media).
|
|
260
276
|
if _last_disc_device:
|
|
261
277
|
return _last_disc_device
|
|
278
|
+
drive = _mac_drive_name()
|
|
279
|
+
if drive:
|
|
280
|
+
raise NoMediaError(f"Drive found ({drive}) but no disc is readable")
|
|
262
281
|
elif override:
|
|
263
282
|
return override
|
|
264
283
|
|
|
@@ -272,7 +291,7 @@ def disc_device():
|
|
|
272
291
|
return str(drives[0])
|
|
273
292
|
elif system == "darwin":
|
|
274
293
|
try:
|
|
275
|
-
status = subprocess.run(["/usr/bin/drutil", "status"], capture_output=True, text=True, check=False, timeout=
|
|
294
|
+
status = subprocess.run(["/usr/bin/drutil", "status"], capture_output=True, text=True, check=False, timeout=15)
|
|
276
295
|
except (OSError, subprocess.TimeoutExpired):
|
|
277
296
|
status = None
|
|
278
297
|
if status:
|
|
@@ -304,7 +323,7 @@ def drive_status():
|
|
|
304
323
|
return "unknown"
|
|
305
324
|
try:
|
|
306
325
|
r = subprocess.run(["/usr/bin/drutil", "status"], capture_output=True,
|
|
307
|
-
text=True, check=False, timeout=
|
|
326
|
+
text=True, check=False, timeout=15)
|
|
308
327
|
except (OSError, subprocess.TimeoutExpired):
|
|
309
328
|
return "unknown"
|
|
310
329
|
text = (r.stdout + r.stderr).lower()
|
|
@@ -330,7 +349,7 @@ def _tag_rewritable(props, media_type):
|
|
|
330
349
|
|
|
331
350
|
|
|
332
351
|
def _drutil_media_properties():
|
|
333
|
-
result = subprocess.run(["/usr/bin/drutil", "status"], capture_output=True, text=True, check=False, timeout=
|
|
352
|
+
result = subprocess.run(["/usr/bin/drutil", "status"], capture_output=True, text=True, check=False, timeout=15)
|
|
334
353
|
text = result.stdout + result.stderr
|
|
335
354
|
lowered = text.lower()
|
|
336
355
|
if result.returncode != 0 or "no media" in lowered:
|
|
@@ -382,7 +401,9 @@ def media_properties(device):
|
|
|
382
401
|
mount_point = fields.get("Mount Point", "")
|
|
383
402
|
if mount_point and mount_point != "Not applicable":
|
|
384
403
|
props["ID_MOUNT_POINT"] = mount_point
|
|
385
|
-
if label.lower() == "audio cd":
|
|
404
|
+
if label.lower() == "audio cd" or props.get("ID_FS_TYPE") == "cddafs":
|
|
405
|
+
# cddafs is macOS's filesystem for Red Book audio - the only signal
|
|
406
|
+
# that works for a burned CD-R, whose media type ("CD-R") has an "r".
|
|
386
407
|
props["ID_CDROM_MEDIA_TYPE"] = "audio"
|
|
387
408
|
elif "cd" in optical.lower() and "r" not in optical.lower():
|
|
388
409
|
props["ID_CDROM_MEDIA_TYPE"] = "audio"
|
|
@@ -430,7 +451,7 @@ def media_capacity_bytes(device):
|
|
|
430
451
|
try:
|
|
431
452
|
result = subprocess.run(["/usr/sbin/diskutil", "info", device], capture_output=True, text=True, check=False, timeout=3)
|
|
432
453
|
except subprocess.TimeoutExpired:
|
|
433
|
-
result = subprocess.run(["/usr/bin/drutil", "status"], capture_output=True, text=True, check=False, timeout=
|
|
454
|
+
result = subprocess.run(["/usr/bin/drutil", "status"], capture_output=True, text=True, check=False, timeout=15)
|
|
434
455
|
match = re.search(r"blocks:\s*(\d+)\s*/", result.stdout + result.stderr, re.IGNORECASE)
|
|
435
456
|
if match:
|
|
436
457
|
return int(match.group(1)) * 2048
|