renamed 'hf mfdes readdata, writedata' to 'read/write'
[RRG-proxmark3.git] / client / src / cmdhfwaveshare.c
blobd2a0e7b6e8f073f62f87138e895a0dde325a4589
1 //-----------------------------------------------------------------------------
2 // Waveshare commands
3 //-----------------------------------------------------------------------------
4 // from ST25R3911B-NFC-Demo source code by Waveshare team
6 #include "cmdhfwaveshare.h"
8 #include <stdio.h>
9 #include <ctype.h>
10 #include "comms.h"
11 #include "cmdparser.h"
12 #include "ui.h"
13 #include "util.h"
14 #include "fileutils.h"
15 #include "util_posix.h" // msleep
16 #include "cliparser.h"
18 // Currently the largest pixel 880*528 only needs 58.08K bytes
19 #define WSMAPSIZE 60000
21 typedef struct {
22 uint8_t B;
23 uint8_t M;
24 uint32_t fsize;
25 uint16_t res1;
26 uint16_t res2;
27 uint32_t offset;
28 uint32_t Bit_Pixel;
29 uint32_t BMP_Width;
30 uint32_t BMP_Height;
31 uint16_t planes;
32 uint16_t bpp;
33 uint32_t ctype;
34 uint32_t dsize;
35 uint32_t hppm;
36 uint32_t vppm;
37 uint32_t colorsused;
38 uint32_t colorreq;
39 uint32_t Color_1; //Color palette
40 uint32_t Color_2;
41 } PACKED BMP_HEADER;
43 #define EPD_1IN54B 0
44 #define EPD_1IN54C 1
45 #define EPD_1IN54V2 2
46 #define EPD_1IN54BCV2 3
47 #define EPD_2IN13V2 4
48 #define EPD_2IN13BC 5
49 #define EPD_2IN13D 6
50 #define EPD_2IN9 7
51 #define EPD_2IN9BC 8
52 #define EPD_2IN9D 9
53 #define EPD_4IN2 10
54 #define EPD_4IN2BC 11
55 #define EPD_7IN5 12
56 #define EPD_7IN5BC 13
57 #define EPD_7IN5V2 14
58 #define EPD_7IN5BCV2 15
59 #define EPD_2IN7 16
60 #define EPD_7IN5HD 17
62 typedef struct model_s {
63 const char *desc;
64 uint8_t len; // The data sent in one time shall not be greater than 128-3
65 uint16_t width;
66 uint16_t height;
67 } model_t;
69 typedef enum {
70 M2in13 = 0,
71 M2in9,
72 M4in2,
73 M7in5,
74 M2in7,
75 M2in13B,
76 M1in54B,
77 M7in5HD,
78 MEND
79 } model_enum_t;
81 static model_t models[] = {
82 {"2.13 inch e-paper", 16, 122, 250}, // tested
83 {"2.9 inch e-paper", 16, 296, 128},
84 {"4.2 inch e-paper", 100, 400, 300}, // tested
85 {"7.5 inch e-paper", 120, 800, 480},
86 {"2.7 inch e-paper", 121, 176, 276}, // tested
87 {"2.13 inch e-paper B (with red)", 106, 104, 212}, // tested
88 {"1.54 inch e-paper B (with red)", 100, 200, 200}, // tested
89 {"7.5 inch e-paper HD", 120, 880, 528},
92 static int CmdHelp(const char *Cmd);
94 static int picture_bit_depth(const uint8_t *bmp, const size_t bmpsize, const uint8_t model_nr) {
95 if (bmpsize < sizeof(BMP_HEADER))
96 return PM3_ESOFT;
97 BMP_HEADER *pbmpheader = (BMP_HEADER *)bmp;
98 PrintAndLogEx(DEBUG, "colorsused = %d", pbmpheader->colorsused);
99 PrintAndLogEx(DEBUG, "pbmpheader->bpp = %d", pbmpheader->bpp);
100 if ((pbmpheader->BMP_Width != models[model_nr].width) || (pbmpheader->BMP_Height != models[model_nr].height)) {
101 PrintAndLogEx(WARNING, "Invalid BMP size, expected %ix%i, got %ix%i", models[model_nr].width, models[model_nr].height, pbmpheader->BMP_Width, pbmpheader->BMP_Height);
103 return pbmpheader->bpp;
106 static int read_bmp_bitmap(const uint8_t *bmp, const size_t bmpsize, uint8_t model_nr, uint8_t **black, uint8_t **red) {
107 BMP_HEADER *pbmpheader = (BMP_HEADER *)bmp;
108 // check file is bitmap
109 if (pbmpheader->bpp != 1) {
110 return PM3_ESOFT;
112 if (pbmpheader->B == 'M' || pbmpheader->M == 'B') { //0x4d42
113 PrintAndLogEx(WARNING, "The file is not a BMP!");
114 return PM3_ESOFT;
116 PrintAndLogEx(DEBUG, "file size = %d", pbmpheader->fsize);
117 PrintAndLogEx(DEBUG, "file offset = %d", pbmpheader->offset);
118 if (pbmpheader->fsize > bmpsize) {
119 PrintAndLogEx(WARNING, "The file is truncated!");
120 return PM3_ESOFT;
122 uint8_t color_flag = pbmpheader->Color_1;
123 // Get BMP file data pointer
124 uint32_t offset = pbmpheader->offset;
125 uint16_t width = pbmpheader->BMP_Width;
126 uint16_t height = pbmpheader->BMP_Height;
127 if ((width + 8) * height > WSMAPSIZE * 8) {
128 PrintAndLogEx(WARNING, "The file is too large, aborting!");
129 return PM3_ESOFT;
132 uint16_t X, Y;
133 uint16_t Image_Width_Byte = (width % 8 == 0) ? (width / 8) : (width / 8 + 1);
134 uint16_t Bmp_Width_Byte = (Image_Width_Byte % 4 == 0) ? Image_Width_Byte : ((Image_Width_Byte / 4 + 1) * 4);
136 *black = calloc(WSMAPSIZE, sizeof(uint8_t));
137 if (*black == NULL) {
138 return PM3_EMALLOC;
140 // Write data into RAM
141 for (Y = 0; Y < height; Y++) { // columns
142 for (X = 0; X < Bmp_Width_Byte; X++) { // lines
143 if ((X < Image_Width_Byte) && ((X + (height - Y - 1) * Image_Width_Byte) < WSMAPSIZE)) {
144 (*black)[X + (height - Y - 1) * Image_Width_Byte] = color_flag ? bmp[offset] : ~bmp[offset];
146 offset++;
149 if ((model_nr == M1in54B) || (model_nr == M2in13B)) {
150 // for BW+Red screens:
151 *red = calloc(WSMAPSIZE, sizeof(uint8_t));
152 if (*red == NULL) {
153 free(*black);
154 return PM3_EMALLOC;
157 return PM3_SUCCESS;
160 static void rgb_to_gray(int16_t *chanR, int16_t *chanG, int16_t *chanB, uint16_t width, uint16_t height, int16_t *chanGrey) {
161 for (uint16_t Y = 0; Y < height; Y++) {
162 for (uint16_t X = 0; X < width; X++) {
163 // greyscale conversion
164 float Clinear = 0.2126 * chanR[X + Y * width] + 0.7152 * chanG[X + Y * width] + 0.0722 * chanB[X + Y * width];
165 // Csrgb = 12.92 Clinear when Clinear <= 0.0031308
166 // Csrgb = 1.055 Clinear1/2.4 - 0.055 when Clinear > 0.0031308
167 chanGrey[X + Y * width] = Clinear;
172 // Floyd-Steinberg dithering
173 static void dither_chan_inplace(int16_t *chan, uint16_t width, uint16_t height) {
174 for (uint16_t Y = 0; Y < height; Y++) {
175 for (uint16_t X = 0; X < width; X++) {
176 int16_t oldp = chan[X + Y * width];
177 int16_t newp = oldp > 127 ? 255 : 0;
178 chan[X + Y * width] = newp;
179 int16_t err = oldp - newp;
180 float m[] = {7, 3, 5, 1};
181 if (X < width - 1) {
182 chan[X + 1 + Y * width] = chan[X + 1 + Y * width] + m[0] / 16 * err;
184 if (Y < height - 1) {
185 chan[X - 1 + (Y + 1) * width] = chan[X - 1 + (Y + 1) * width] + m[1] / 16 * err;
186 chan[X + (Y + 1) * width] = chan[X + (Y + 1) * width] + m[2] / 16 * err;
188 if ((X < width - 1) && (Y < height - 1)) {
189 chan[X + 1 + (Y + 1) * width] = chan[X + 1 + (Y + 1) * width] + m[3] / 16 * err;
195 static uint32_t color_compare(int16_t r1, int16_t g1, int16_t b1, int16_t r2, int16_t g2, int16_t b2) {
196 // Compute (square of) distance from oldR/G/B to this color
197 int16_t inR = r1 - r2;
198 int16_t inG = g1 - g2;
199 int16_t inB = b1 - b2;
200 // use RGB-to-grey weighting
201 float dist = 0.2126 * inR * inR + 0.7152 * inG * inG + 0.0722 * inB * inB;
202 return dist;
205 static void nearest_color(int16_t oldR, int16_t oldG, int16_t oldB, uint8_t *palette, uint16_t palettelen, uint8_t *newR, uint8_t *newG, uint8_t *newB) {
206 uint32_t bestdist = 0x7FFFFFFF;
207 for (uint16_t i = 0; i < palettelen; i++) {
208 uint8_t R = palette[i * 3 + 0];
209 uint8_t G = palette[i * 3 + 1];
210 uint8_t B = palette[i * 3 + 2];
211 uint32_t dist = color_compare(oldR, oldG, oldB, R, G, B);
212 if (dist < bestdist) {
213 bestdist = dist;
214 *newR = R;
215 *newG = G;
216 *newB = B;
221 static void dither_rgb_inplace(int16_t *chanR, int16_t *chanG, int16_t *chanB, uint16_t width, uint16_t height, uint8_t *palette, uint16_t palettelen) {
222 for (uint16_t Y = 0; Y < height; Y++) {
223 for (uint16_t X = 0; X < width; X++) {
224 // scan odd lines in the opposite direction
225 uint16_t XX = X;
226 if (Y % 2) {
227 XX = width - X - 1;
229 int16_t oldR = chanR[XX + Y * width];
230 int16_t oldG = chanG[XX + Y * width];
231 int16_t oldB = chanB[XX + Y * width];
232 uint8_t newR = 0, newG = 0, newB = 0;
233 nearest_color(oldR, oldG, oldB, palette, palettelen, &newR, &newG, &newB);
234 chanR[XX + Y * width] = newR;
235 chanG[XX + Y * width] = newG;
236 chanB[XX + Y * width] = newB;
237 int16_t errR = oldR - newR;
238 int16_t errG = oldG - newG;
239 int16_t errB = oldB - newB;
240 float m[] = {7, 3, 5, 1};
241 if (Y % 2) {
242 if (XX > 0) {
243 chanR[XX - 1 + Y * width] = (chanR[XX - 1 + Y * width] + m[0] / 16 * errR);
244 chanG[XX - 1 + Y * width] = (chanG[XX - 1 + Y * width] + m[0] / 16 * errG);
245 chanB[XX - 1 + Y * width] = (chanB[XX - 1 + Y * width] + m[0] / 16 * errB);
247 if (Y < height - 1) {
248 chanR[XX - 1 + (Y + 1) * width] = (chanR[XX - 1 + (Y + 1) * width] + m[3] / 16 * errR);
249 chanG[XX - 1 + (Y + 1) * width] = (chanG[XX - 1 + (Y + 1) * width] + m[3] / 16 * errG);
250 chanB[XX - 1 + (Y + 1) * width] = (chanB[XX - 1 + (Y + 1) * width] + m[3] / 16 * errB);
251 chanR[XX + (Y + 1) * width] = (chanR[XX + (Y + 1) * width] + m[2] / 16 * errR);
252 chanG[XX + (Y + 1) * width] = (chanG[XX + (Y + 1) * width] + m[2] / 16 * errG);
253 chanB[XX + (Y + 1) * width] = (chanB[XX + (Y + 1) * width] + m[2] / 16 * errB);
255 if ((XX < width - 1) && (Y < height - 1)) {
256 chanR[XX + 1 + (Y + 1) * width] = (chanR[XX + 1 + (Y + 1) * width] + m[1] / 16 * errR);
257 chanG[XX + 1 + (Y + 1) * width] = (chanG[XX + 1 + (Y + 1) * width] + m[1] / 16 * errG);
258 chanB[XX + 1 + (Y + 1) * width] = (chanB[XX + 1 + (Y + 1) * width] + m[1] / 16 * errB);
260 } else {
261 if (XX < width - 1) {
262 chanR[XX + 1 + Y * width] = (chanR[XX + 1 + Y * width] + m[0] / 16 * errR);
263 chanG[XX + 1 + Y * width] = (chanG[XX + 1 + Y * width] + m[0] / 16 * errG);
264 chanB[XX + 1 + Y * width] = (chanB[XX + 1 + Y * width] + m[0] / 16 * errB);
266 if (Y < height - 1) {
267 chanR[XX - 1 + (Y + 1) * width] = (chanR[XX - 1 + (Y + 1) * width] + m[1] / 16 * errR);
268 chanG[XX - 1 + (Y + 1) * width] = (chanG[XX - 1 + (Y + 1) * width] + m[1] / 16 * errG);
269 chanB[XX - 1 + (Y + 1) * width] = (chanB[XX - 1 + (Y + 1) * width] + m[1] / 16 * errB);
270 chanR[XX + (Y + 1) * width] = (chanR[XX + (Y + 1) * width] + m[2] / 16 * errR);
271 chanG[XX + (Y + 1) * width] = (chanG[XX + (Y + 1) * width] + m[2] / 16 * errG);
272 chanB[XX + (Y + 1) * width] = (chanB[XX + (Y + 1) * width] + m[2] / 16 * errB);
274 if ((XX < width - 1) && (Y < height - 1)) {
275 chanR[XX + 1 + (Y + 1) * width] = (chanR[XX + 1 + (Y + 1) * width] + m[3] / 16 * errR);
276 chanG[XX + 1 + (Y + 1) * width] = (chanG[XX + 1 + (Y + 1) * width] + m[3] / 16 * errG);
277 chanB[XX + 1 + (Y + 1) * width] = (chanB[XX + 1 + (Y + 1) * width] + m[3] / 16 * errB);
284 static void rgb_to_gray_red_inplace(int16_t *chanR, int16_t *chanG, int16_t *chanB, uint16_t width, uint16_t height) {
285 for (uint16_t Y = 0; Y < height; Y++) {
286 for (uint16_t X = 0; X < width; X++) {
287 float Clinear = 0.2126 * chanR[X + Y * width] + 0.7152 * chanG[X + Y * width] + 0.0722 * chanB[X + Y * width];
288 if ((chanR[X + Y * width] < chanG[X + Y * width] && chanR[X + Y * width] < chanB[X + Y * width])) {
289 chanR[X + Y * width] = Clinear;
290 chanG[X + Y * width] = Clinear;
291 chanB[X + Y * width] = Clinear;
297 static void threshold_chan(int16_t *colorchan, uint16_t width, uint16_t height, uint8_t threshold, uint8_t *colormap) {
298 for (uint16_t Y = 0; Y < height; Y++) {
299 for (uint16_t X = 0; X < width; X++) {
300 colormap[X + Y * width] = colorchan[X + Y * width] < threshold;
305 static void threshold_rgb_black_red(int16_t *chanR, int16_t *chanG, int16_t *chanB, uint16_t width, uint16_t height, uint8_t threshold_black, uint8_t threshold_red, uint8_t *blackmap, uint8_t *redmap) {
306 for (uint16_t Y = 0; Y < height; Y++) {
307 for (uint16_t X = 0; X < width; X++) {
308 if ((chanR[X + Y * width] < threshold_black) && (chanG[X + Y * width] < threshold_black) && (chanB[X + Y * width] < threshold_black)) {
309 blackmap[X + Y * width] = 1;
310 redmap[X + Y * width] = 0;
311 } else if ((chanR[X + Y * width] > threshold_red) && (chanG[X + Y * width] < threshold_black) && (chanB[X + Y * width] < threshold_black)) {
312 blackmap[X + Y * width] = 0;
313 redmap[X + Y * width] = 1;
314 } else {
315 blackmap[X + Y * width] = 0;
316 redmap[X + Y * width] = 0;
322 static void map8to1(uint8_t *colormap, uint16_t width, uint16_t height, uint8_t *colormap8) {
323 uint16_t width8;
324 if (width % 8 == 0) {
325 width8 = width / 8;
326 } else {
327 width8 = width / 8 + 1;
329 uint8_t data = 0;
330 uint8_t count = 0;
331 for (uint16_t Y = 0; Y < height; Y++) {
332 for (uint16_t X = 0; X < width; X++) {
333 data = data | colormap[X + Y * width];
334 count += 1;
335 if ((count >= 8) || (X == width - 1)) {
336 colormap8[X / 8 + Y * width8] = (~data) & 0xFF;
337 count = 0;
338 data = 0;
340 data = (data << 1) & 0xFF;
345 static int read_bmp_rgb(uint8_t *bmp, const size_t bmpsize, uint8_t model_nr, uint8_t **black, uint8_t **red, char *filename, bool save_conversions) {
346 BMP_HEADER *pbmpheader = (BMP_HEADER *)bmp;
347 // check file is full color
348 if ((pbmpheader->bpp != 24) && (pbmpheader->bpp != 32)) {
349 return PM3_ESOFT;
352 if (pbmpheader->B == 'M' || pbmpheader->M == 'B') { //0x4d42
353 PrintAndLogEx(WARNING, "The file is not a BMP!");
354 return PM3_ESOFT;
357 PrintAndLogEx(DEBUG, "file size = %d", pbmpheader->fsize);
358 PrintAndLogEx(DEBUG, "file offset = %d", pbmpheader->offset);
359 if (pbmpheader->fsize > bmpsize) {
360 PrintAndLogEx(WARNING, "The file is truncated!");
361 return PM3_ESOFT;
364 // Get BMP file data pointer
365 uint32_t offset = pbmpheader->offset;
366 uint16_t width = pbmpheader->BMP_Width;
367 uint16_t height = pbmpheader->BMP_Height;
368 if ((width + 8) * height > WSMAPSIZE * 8) {
369 PrintAndLogEx(WARNING, "The file is too large, aborting!");
370 return PM3_ESOFT;
373 int16_t *chanR = calloc(width * height, sizeof(int16_t));
374 if (chanR == NULL) {
375 return PM3_EMALLOC;
378 int16_t *chanG = calloc(width * height, sizeof(int16_t));
379 if (chanG == NULL) {
380 free(chanR);
381 return PM3_EMALLOC;
384 int16_t *chanB = calloc(width * height, sizeof(int16_t));
385 if (chanB == NULL) {
386 free(chanR);
387 free(chanG);
388 return PM3_EMALLOC;
391 // Extracting BMP chans
392 for (uint16_t Y = 0; Y < height; Y++) {
393 for (uint16_t X = 0; X < width; X++) {
394 chanB[X + (height - Y - 1) * width] = bmp[offset++];
395 chanG[X + (height - Y - 1) * width] = bmp[offset++];
396 chanR[X + (height - Y - 1) * width] = bmp[offset++];
397 if (pbmpheader->bpp == 32) // Skip Alpha chan
398 offset++;
400 // Skip line padding
401 offset += width % 4;
404 if ((model_nr == M1in54B) || (model_nr == M2in13B)) {
405 // for BW+Red screens:
406 uint8_t *mapBlack = calloc(width * height, sizeof(uint8_t));
407 if (mapBlack == NULL) {
408 free(chanR);
409 free(chanG);
410 free(chanB);
411 return PM3_EMALLOC;
413 uint8_t *mapRed = calloc(width * height, sizeof(uint8_t));
414 if (mapRed == NULL) {
415 free(chanR);
416 free(chanG);
417 free(chanB);
418 free(mapBlack);
419 return PM3_EMALLOC;
421 rgb_to_gray_red_inplace(chanR, chanG, chanB, width, height);
423 uint8_t palette[] = {0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00}; // black, white, red
424 dither_rgb_inplace(chanR, chanG, chanB, width, height, palette, sizeof(palette) / 3);
426 threshold_rgb_black_red(chanR, chanG, chanB, width, height, 128, 128, mapBlack, mapRed);
427 if (save_conversions) {
428 // fill BMP chans
429 offset = pbmpheader->offset;
430 for (uint16_t Y = 0; Y < height; Y++) {
431 for (uint16_t X = 0; X < width; X++) {
432 bmp[offset++] = chanB[X + (height - Y - 1) * width] & 0xFF;
433 bmp[offset++] = chanG[X + (height - Y - 1) * width] & 0xFF;
434 bmp[offset++] = chanR[X + (height - Y - 1) * width] & 0xFF;
435 if (pbmpheader->bpp == 32) // Fill Alpha chan
436 bmp[offset++] = 0xFF;
438 // Skip line padding
439 offset += width % 4;
441 PrintAndLogEx(INFO, "Saving red+black dithered version...");
442 if (saveFile(filename, ".bmp", bmp, offset) != PM3_SUCCESS) {
443 PrintAndLogEx(WARNING, "Could not save file " _YELLOW_("%s"), filename);
444 free(chanR);
445 free(chanG);
446 free(chanB);
447 free(mapBlack);
448 free(mapRed);
449 return PM3_EIO;
452 free(chanR);
453 free(chanG);
454 free(chanB);
455 *black = calloc(WSMAPSIZE, sizeof(uint8_t));
456 if (*black == NULL) {
457 free(mapBlack);
458 free(mapRed);
459 return PM3_EMALLOC;
461 map8to1(mapBlack, width, height, *black);
462 free(mapBlack);
463 *red = calloc(WSMAPSIZE, sizeof(uint8_t));
464 if (*red == NULL) {
465 free(mapRed);
466 free(*black);
467 return PM3_EMALLOC;
469 map8to1(mapRed, width, height, *red);
470 free(mapRed);
471 } else {
472 // for BW-only screens:
473 int16_t *chanGrey = calloc(width * height, sizeof(int16_t));
474 if (chanGrey == NULL) {
475 free(chanR);
476 free(chanG);
477 free(chanB);
478 return PM3_EMALLOC;
480 rgb_to_gray(chanR, chanG, chanB, width, height, chanGrey);
481 dither_chan_inplace(chanGrey, width, height);
483 uint8_t *mapBlack = calloc(width * height, sizeof(uint8_t));
484 if (mapBlack == NULL) {
485 free(chanR);
486 free(chanG);
487 free(chanB);
488 free(chanGrey);
489 return PM3_EMALLOC;
491 threshold_chan(chanGrey, width, height, 128, mapBlack);
493 if (save_conversions) {
494 // fill BMP chans
495 offset = pbmpheader->offset;
496 for (uint16_t Y = 0; Y < height; Y++) {
497 for (uint16_t X = 0; X < width; X++) {
498 bmp[offset++] = chanGrey[X + (height - Y - 1) * width] & 0xFF;
499 bmp[offset++] = chanGrey[X + (height - Y - 1) * width] & 0xFF;
500 bmp[offset++] = chanGrey[X + (height - Y - 1) * width] & 0xFF;
501 if (pbmpheader->bpp == 32) // Fill Alpha chan
502 bmp[offset++] = 0xFF;
504 // Skip line padding
505 offset += width % 4;
507 PrintAndLogEx(INFO, "Saving black dithered version...");
508 if (saveFile(filename, ".bmp", bmp, offset) != PM3_SUCCESS) {
509 PrintAndLogEx(WARNING, "Could not save file " _YELLOW_("%s"), filename);
510 free(chanGrey);
511 free(chanR);
512 free(chanG);
513 free(chanB);
514 free(mapBlack);
515 return PM3_EIO;
518 free(chanGrey);
519 free(chanR);
520 free(chanG);
521 free(chanB);
522 *black = calloc(WSMAPSIZE, sizeof(uint8_t));
523 if (*black == NULL) {
524 free(mapBlack);
525 return PM3_EMALLOC;
527 map8to1(mapBlack, width, height, *black);
528 free(mapBlack);
530 return PM3_SUCCESS;
533 static void read_black(uint32_t i, uint8_t *l, uint8_t model_nr, uint8_t *black) {
534 for (uint8_t j = 0; j < models[model_nr].len; j++) {
535 l[3 + j] = black[i * models[model_nr].len + j];
538 static void read_red(uint32_t i, uint8_t *l, uint8_t model_nr, uint8_t *red) {
539 // spurious warning with GCC10 (-Wstringop-overflow) when j is uint8_t, even if all len are < 128
540 for (uint16_t j = 0; j < models[model_nr].len; j++) {
541 if (model_nr == M1in54B) {
542 //1.54B needs to flip the red picture data, other screens do not need to flip data
543 l[3 + j] = ~red[i * models[model_nr].len + j];
544 } else {
545 l[3 + j] = red[i * models[model_nr].len + j];
550 static int transceive_blocking(uint8_t *txBuf, uint16_t txBufLen, uint8_t *rxBuf, uint16_t rxBufLen, uint16_t *actLen, bool retransmit) {
551 uint8_t fail_num = 0;
552 if (rxBufLen < 2) {
553 return PM3_EINVARG;
556 while (1) {
557 PacketResponseNG resp;
558 SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_DISCONNECT, txBufLen, 0, txBuf, txBufLen);
559 rxBuf[0] = 1;
560 if (WaitForResponseTimeout(CMD_ACK, &resp, 2000)) {
561 if (resp.oldarg[0] > rxBufLen) {
562 PrintAndLogEx(WARNING, "Received %"PRIu64 " bytes, rxBuf too small (%u)", resp.oldarg[0], rxBufLen);
563 memcpy(rxBuf, resp.data.asBytes, rxBufLen);
564 *actLen = rxBufLen;
565 return PM3_ESOFT;
567 memcpy(rxBuf, resp.data.asBytes, resp.oldarg[0]);
568 *actLen = resp.oldarg[0];
571 if ((retransmit) && (rxBuf[0] != 0 || rxBuf[1] != 0)) {
572 fail_num++;
573 if (fail_num > 10) {
574 PROMPT_CLEARLINE;
575 PrintAndLogEx(WARNING, "Transmission failed, please try again.");
576 DropField();
577 return PM3_ESOFT;
579 } else {
580 break;
583 return PM3_SUCCESS;
586 // 1.54B Keychain
587 // 1.54B does not share the common base and requires specific handling
588 static int start_drawing_1in54B(uint8_t model_nr, uint8_t *black, uint8_t *red) {
589 int ret;
590 uint8_t step_5[128] = {0xcd, 0x05, 100};
591 uint8_t step_4[2] = {0xcd, 0x04};
592 uint8_t step_6[2] = {0xcd, 0x06};
593 uint8_t rx[20] = {0};
594 uint16_t actrxlen[20], i = 0, progress = 0;
596 if (model_nr == M1in54B) {
597 step_5[2] = 100;
599 PrintAndLogEx(DEBUG, "1.54_Step9: e-paper config2 (black)");
600 if (model_nr == M1in54B) { //1.54inch B Keychain
601 for (i = 0; i < 50; i++) {
602 read_black(i, step_5, model_nr, black);
603 ret = transceive_blocking(step_5, 103, rx, 20, actrxlen, true); // cd 05
604 if (ret != PM3_SUCCESS) {
605 return ret;
607 progress = i * 100 / 100;
608 PrintAndLogEx(INPLACE, "Progress: %d %%", progress);
611 PROMPT_CLEARLINE;
612 PrintAndLogEx(DEBUG, "1.54_Step6: e-paper power on");
613 ret = transceive_blocking(step_4, 2, rx, 20, actrxlen, true); //cd 04
614 if (ret != PM3_SUCCESS) {
615 return ret;
617 PrintAndLogEx(DEBUG, "1.54_Step7: e-paper config2 (red)");
618 if (model_nr == M1in54B) { //1.54inch B Keychain
619 for (i = 0; i < 50; i++) {
620 read_red(i, step_5, model_nr, red);
621 ret = transceive_blocking(step_5, 103, rx, 20, actrxlen, true); // cd 05
622 if (ret != PM3_SUCCESS) {
623 return ret;
625 progress = i * 100 / 100 + 50;
626 PrintAndLogEx(INPLACE, "Progress: %d %%", progress);
629 PROMPT_CLEARLINE;
630 // Send update instructions
631 PrintAndLogEx(DEBUG, "1.54_Step8: EDP load to main");
632 ret = transceive_blocking(step_6, 2, rx, 20, actrxlen, true); //cd 06
633 if (ret != PM3_SUCCESS) {
634 return ret;
636 PrintAndLogEx(DEBUG, "1.54_Step9");
637 return PM3_SUCCESS;
640 static int start_drawing(uint8_t model_nr, uint8_t *black, uint8_t *red) {
641 uint8_t progress = 0;
642 uint8_t step0[2] = {0xcd, 0x0d};
643 uint8_t step1[3] = {0xcd, 0x00, 10}; // select e-paper type and reset e-paper
644 // 4 :2.13inch e-Paper
645 // 7 :2.9inch e-Paper
646 // 10 :4.2inch e-Paper
647 // 14 :7.5inch e-Paper
648 uint8_t step2[2] = {0xcd, 0x01}; // e-paper normal mode type:
649 uint8_t step3[2] = {0xcd, 0x02}; // e-paper config1
650 uint8_t step4[2] = {0xcd, 0x03}; // e-paper power on
651 uint8_t step5[2] = {0xcd, 0x05}; // e-paper config2
652 uint8_t step6[2] = {0xcd, 0x06}; // EDP load to main
653 uint8_t step7[2] = {0xcd, 0x07}; // Data preparation
655 uint8_t step8[123] = {0xcd, 0x08, 0x64}; // Data start command
656 // 2.13inch(0x10:Send 16 data at a time)
657 // 2.9inch(0x10:Send 16 data at a time)
658 // 4.2inch(0x64:Send 100 data at a time)
659 // 7.5inch(0x78:Send 120 data at a time)
660 uint8_t step9[2] = {0xcd, 0x18}; // e-paper power on
661 uint8_t step10[2] = {0xcd, 0x09}; // Refresh e-paper
662 uint8_t step11[2] = {0xcd, 0x0a}; // wait for ready
663 uint8_t step12[2] = {0xcd, 0x04}; // e-paper power off command
664 uint8_t step13[124] = {0xcd, 0x19, 121};
665 // uint8_t step13[2]={0xcd,0x0b}; // Judge whether the power supply is turned off successfully
666 // uint8_t step14[2]={0xcd,0x0c}; // The end of the transmission
667 uint8_t rx[20];
668 uint16_t actrxlen[20], i = 0;
672 clearCommandBuffer();
673 SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_NO_DISCONNECT, 0, 0, NULL, 0);
674 PacketResponseNG resp;
675 if (!WaitForResponseTimeout(CMD_ACK, &resp, 2500)) {
676 PrintAndLogEx(ERR, "No tag found");
677 DropField();
678 return PM3_ETIMEOUT;
681 iso14a_card_select_t card;
682 memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t));
684 uint64_t select_status = resp.oldarg[0];
686 if (select_status == 0) {
687 PrintAndLogEx(ERR, "Tag select error");
688 DropField();
689 return PM3_ERFTRANS;
690 } else if (select_status == 3) {
691 PrintAndLogEx(WARNING, "Card doesn't support standard iso14443-3 anticollision, doesn't look like Waveshare tag");
692 DropField();
693 return PM3_ESOFT;
696 if ((card.uidlen != 7) || ((memcmp(card.uid, "FSTN10m", 7) != 0) && (memcmp(card.uid, "WSDZ10m", 7) != 0))) {
697 PrintAndLogEx(WARNING, "Card doesn't look like Waveshare tag");
698 DropField();
699 return PM3_ESOFT;
701 if (((model_nr != M1in54B) && (memcmp(card.uid, "FSTN10m", 7) == 0))) {
702 PrintAndLogEx(WARNING, "Card is a Waveshare tag 1.54\", not %s", models[model_nr].desc);
703 DropField();
704 return PM3_ESOFT;
706 if (((model_nr == M1in54B) && (memcmp(card.uid, "FSTN10m", 7) != 0))) {
707 PrintAndLogEx(WARNING, "Card is not a Waveshare tag 1.54\", check your model number");
708 DropField();
709 return PM3_ESOFT;
711 PrintAndLogEx(DEBUG, "model_nr = %d", model_nr);
712 int ret;
713 PrintAndLogEx(DEBUG, "Step0");
714 ret = transceive_blocking(step0, 2, rx, 20, actrxlen, true); //cd 0d
715 if (ret != PM3_SUCCESS) {
716 return ret;
718 PrintAndLogEx(DEBUG, "Step1: e-paper config");
719 //step1[2] screen model
720 //step8[2] nr of bytes sent at once
721 //step13[2] nr of bytes sent for the second time
722 // generally, step8 sends a black image, step13 sends a red image
723 if (model_nr == M2in13) { //2.13inch
724 step1[2] = EPD_2IN13V2;
725 step8[2] = 16;
726 step13[2] = 0;
727 } else if (model_nr == M2in9) { //2.9inch
728 step1[2] = EPD_2IN9;
729 step8[2] = 16;
730 step13[2] = 0;
731 } else if (model_nr == M4in2) { //4.2inch
732 step1[2] = EPD_4IN2;
733 step8[2] = 100;
734 step13[2] = 0;
735 } else if (model_nr == M7in5) { //7.5inch
736 step1[2] = EPD_7IN5V2;
737 step8[2] = 120;
738 step13[2] = 0;
739 } else if (model_nr == M2in7) { //2.7inch
740 step1[2] = EPD_2IN7;
741 step8[2] = 121;
742 // Send blank data for the first time, and send other data to 0xff without processing the bottom layer
743 step13[2] = 121;
744 //Sending the second data is the real image data. If the previous 0xff is not sent, the last output image is abnormally black
745 } else if (model_nr == M2in13B) { //2.13inch B
746 step1[2] = EPD_2IN13BC;
747 step8[2] = 106;
748 step13[2] = 106;
749 } else if (model_nr == M7in5HD) {
750 step1[2] = EPD_7IN5HD;
751 step8[2] = 120;
752 step13[2] = 0;
755 if (model_nr == M1in54B) {
756 ret = transceive_blocking(step1, 2, rx, 20, actrxlen, true); //cd 00
757 } else {
758 ret = transceive_blocking(step1, 3, rx, 20, actrxlen, true);
760 if (ret != PM3_SUCCESS) {
761 return ret;
763 msleep(100);
764 PrintAndLogEx(DEBUG, "Step2: e-paper normal mode type");
765 ret = transceive_blocking(step2, 2, rx, 20, actrxlen, true); //cd 01
766 if (ret != PM3_SUCCESS) {
767 return ret;
769 msleep(100);
770 PrintAndLogEx(DEBUG, "Step3: e-paper config1");
771 ret = transceive_blocking(step3, 2, rx, 20, actrxlen, true); //cd 02
772 if (ret != PM3_SUCCESS) {
773 return ret;
775 msleep(200);
776 PrintAndLogEx(DEBUG, "Step4: e-paper power on");
777 ret = transceive_blocking(step4, 2, rx, 20, actrxlen, true); //cd 03
778 if (ret != PM3_SUCCESS) {
779 return ret;
781 if (model_nr == M1in54B) {
782 // 1.54B Keychain handler
783 PrintAndLogEx(DEBUG, "Start_Drawing_1in54B");
784 ret = start_drawing_1in54B(model_nr, black, red);
785 if (ret != PM3_SUCCESS) {
786 return ret;
788 //1.54B Data transfer is complete and wait for refresh
789 } else {
790 PrintAndLogEx(DEBUG, "Step5: e-paper config2");
791 ret = transceive_blocking(step5, 2, rx, 20, actrxlen, true); //cd 05
792 if (ret != PM3_SUCCESS) {
793 return ret;
795 msleep(100);
796 PrintAndLogEx(DEBUG, "Step6: EDP load to main") ;
797 ret = transceive_blocking(step6, 2, rx, 20, actrxlen, true); //cd 06
798 if (ret != PM3_SUCCESS) {
799 return ret;
801 msleep(100);
802 PrintAndLogEx(DEBUG, "Step7: Data preparation");
803 ret = transceive_blocking(step7, 2, rx, 20, actrxlen, true); //cd 07
804 if (ret != PM3_SUCCESS) {
805 return ret;
807 PrintAndLogEx(DEBUG, "Step8: Start data transfer");
808 if (model_nr == M2in13) { //2.13inch
809 for (i = 0; i < 250; i++) {
810 read_black(i, step8, model_nr, black);
811 ret = transceive_blocking(step8, 19, rx, 20, actrxlen, true); // cd 08
812 if (ret != PM3_SUCCESS) {
813 return ret;
815 progress = i * 100 / 250;
816 PrintAndLogEx(INPLACE, "Progress: %d %%", progress);
818 } else if (model_nr == M2in9) {
819 for (i = 0; i < 296; i++) {
820 read_black(i, step8, model_nr, black);
821 ret = transceive_blocking(step8, 19, rx, 20, actrxlen, true); // cd 08
822 if (ret != PM3_SUCCESS) {
823 return ret;
825 progress = i * 100 / 296;
826 PrintAndLogEx(INPLACE, "Progress: %d %%", progress);
828 } else if (model_nr == M4in2) { //4.2inch
829 for (i = 0; i < 150; i++) {
830 read_black(i, step8, model_nr, black);
831 ret = transceive_blocking(step8, 103, rx, 20, actrxlen, true); // cd 08
832 if (ret != PM3_SUCCESS) {
833 return ret;
835 progress = i * 100 / 150;
836 PrintAndLogEx(INPLACE, "Progress: %d %%", progress);
838 } else if (model_nr == M7in5) { //7.5inch
839 for (i = 0; i < 400; i++) {
840 read_black(i, step8, model_nr, black);
841 ret = transceive_blocking(step8, 123, rx, 20, actrxlen, true); // cd 08
842 if (ret != PM3_SUCCESS) {
843 return ret;
845 progress = i * 100 / 400;
846 PrintAndLogEx(INPLACE, "Progress: %d %%", progress);
847 msleep(6);
849 } else if (model_nr == M2in13B) { //2.13inch B
850 for (i = 0; i < 26; i++) {
851 read_black(i, step8, model_nr, black);
852 ret = transceive_blocking(step8, 109, rx, 20, actrxlen, false); // cd 08
853 if (ret != PM3_SUCCESS) {
854 return ret;
856 progress = i * 50 / 26;
857 PrintAndLogEx(INPLACE, "Progress: %d %%", progress);
859 } else if (model_nr == M7in5HD) { //7.5HD
861 for (i = 0; i < 484; i++) {
862 read_black(i, step8, model_nr, black);
863 //memset(&step8[3], 0xf0, 120);
864 ret = transceive_blocking(step8, 123, rx, 20, actrxlen, true); // cd 08
865 if (ret != PM3_SUCCESS) {
866 return ret;
868 progress = i * 100 / 484;
869 PrintAndLogEx(INPLACE, "Progress: %d %%", progress);
871 memset(&step8[3], 0xff, 120);
872 ret = transceive_blocking(step8, 110 + 3, rx, 20, actrxlen, true); // cd 08
873 if (ret != PM3_SUCCESS) {
874 return ret;
876 } else if (model_nr == M2in7) { //2.7inch
877 for (i = 0; i < 48; i++) {
878 //read_black(i,step8, model_nr, black);
879 memset(&step8[3], 0xFF, sizeof(step8) - 3);
880 ret = transceive_blocking(step8, 124, rx, 20, actrxlen, true); // cd 08
881 if (ret != PM3_SUCCESS) {
882 return ret;
884 progress = i * 50 / 48;
885 PrintAndLogEx(INPLACE, "Progress: %d %%", progress);
888 PROMPT_CLEARLINE;
889 PrintAndLogEx(DEBUG, "Step9: e-paper power on");
890 if (model_nr == M2in13 || model_nr == M2in9 || model_nr == M4in2 || model_nr == M7in5 || model_nr == M7in5HD) {
891 ret = transceive_blocking(step9, 2, rx, 20, actrxlen, true); //cd 18
892 // The black-and-white screen sending backplane is also shielded, with no effect. Except 2.7
893 if (ret != PM3_SUCCESS) {
894 return ret;
896 } else if (model_nr == M2in13B || model_nr == M2in7) {
897 ret = transceive_blocking(step9, 2, rx, 20, actrxlen, true); //cd 18
898 if (ret != PM3_SUCCESS) {
899 return ret;
901 PrintAndLogEx(DEBUG, "Step9b");
902 if (model_nr == M2in7) {
903 for (i = 0; i < 48; i++) {
904 read_black(i, step13, model_nr, black);
905 ret = transceive_blocking(step13, 124, rx, 20, actrxlen, true); //CD 19
906 if (ret != PM3_SUCCESS) {
907 return ret;
909 progress = i * 50 / 48 + 50;
910 PrintAndLogEx(INPLACE, "Progress: %d %%", progress);
912 } else if (model_nr == M2in13B) {
913 for (i = 0; i < 26; i++) {
914 read_red(i, step13, model_nr, red);
915 //memset(&step13[3], 0xfE, 106);
916 ret = transceive_blocking(step13, 109, rx, 20, actrxlen, false);
917 if (ret != PM3_SUCCESS) {
918 return ret;
920 progress = i * 50 / 26 + 50;
921 PrintAndLogEx(INPLACE, "Progress: %d %%", progress);
924 PROMPT_CLEARLINE;
926 PrintAndLogEx(DEBUG, "Step10: Refresh e-paper");
927 ret = transceive_blocking(step10, 2, rx, 20, actrxlen, true); //cd 09 refresh command
928 if (ret != PM3_SUCCESS) {
929 return ret;
931 msleep(200);
933 PrintAndLogEx(DEBUG, "Step11: Wait tag to be ready");
934 PrintAndLogEx(INPLACE, "E-paper Reflashing, Waiting");
935 if (model_nr == M2in13B || model_nr == M1in54B) { // Black, white and red screen refresh time is longer, wait first
936 msleep(9000);
937 } else if (model_nr == M7in5HD) {
938 msleep(1000);
940 uint8_t fail_num = 0;
941 while (1) {
942 if (model_nr == M1in54B) {
943 // send 0xcd 0x08 with 1.54B
944 ret = transceive_blocking(step8, 2, rx, 20, actrxlen, false); //cd 08
945 } else {
946 ret = transceive_blocking(step11, 2, rx, 20, actrxlen, false); //cd 0a
948 if (ret != PM3_SUCCESS) {
949 return ret;
951 if (rx[0] == 0xff && rx[1] == 0) {
952 PrintAndLogEx(NORMAL, "");
953 PrintAndLogEx(SUCCESS, "E-paper Reflash OK");
954 msleep(200);
955 break;
956 } else {
957 if (fail_num > 50) {
958 PrintAndLogEx(WARNING, "Update failed, please try again.");
959 DropField();
960 return PM3_ESOFT;
961 } else {
962 fail_num++;
963 PrintAndLogEx(INPLACE, "E-paper Reflashing, Waiting");
964 msleep(400);
968 PrintAndLogEx(DEBUG, "Step12: e-paper power off command");
969 ret = transceive_blocking(step12, 2, rx, 20, actrxlen, true); //cd 04
970 if (ret != PM3_SUCCESS) {
971 return ret;
973 msleep(200);
974 PrintAndLogEx(SUCCESS, "E-paper Update OK");
975 msleep(200);
976 DropField();
977 return PM3_SUCCESS;
980 static int CmdHF14AWSLoadBmp(const char *Cmd) {
982 char desc[800] = {0};
983 for (uint8_t i = 0; i < MEND; i++) {
984 snprintf(desc + strlen(desc),
985 sizeof(desc) - strlen(desc),
986 "hf waveshare loadbmp -f myfile -m %2u -> %s ( %u, %u )\n",
988 models[i].desc,
989 models[i].width,
990 models[i].height
994 CLIParserContext *ctx;
995 CLIParserInit(&ctx, "hf waveshare loadbmp",
996 "Load BMP file to Waveshare NFC ePaper.",
997 desc
1000 char modeldesc[40];
1001 snprintf(modeldesc, sizeof(modeldesc), "model number [0 - %d] of your tag", MEND - 1);
1003 void *argtable[] = {
1004 arg_param_begin,
1005 arg_int1("m", NULL, "<nr>", modeldesc),
1006 arg_lit0("s", "save", "save dithered version in filename-[n].bmp, only for RGB BMP"),
1007 arg_str1("f", "file", "<filename>", "filename[.bmp] to upload to tag"),
1008 arg_param_end
1011 CLIExecWithReturn(ctx, Cmd, argtable, false);
1013 int model_nr = arg_get_int_def(ctx, 1, -1);
1014 bool save_conversions = arg_get_lit(ctx, 2);
1016 int fnlen = 0;
1017 char filename[FILE_PATH_SIZE] = {0};
1018 CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
1019 CLIParserFree(ctx);
1021 //Validations
1022 if (fnlen < 1) {
1023 PrintAndLogEx(WARNING, "Missing filename");
1024 return PM3_EINVARG;
1026 if (model_nr == -1) {
1027 PrintAndLogEx(WARNING, "Missing model");
1028 return PM3_EINVARG;
1030 if (model_nr >= MEND) {
1031 PrintAndLogEx(WARNING, "Unknown model");
1032 return PM3_EINVARG;
1035 uint8_t *bmp = NULL;
1036 uint8_t *black = NULL;
1037 uint8_t *red = NULL;
1038 size_t bytes_read = 0;
1039 if (loadFile_safe(filename, ".bmp", (void **)&bmp, &bytes_read) != PM3_SUCCESS) {
1040 PrintAndLogEx(WARNING, "Could not find file " _YELLOW_("%s"), filename);
1041 return PM3_EIO;
1044 int depth = picture_bit_depth(bmp, bytes_read, model_nr);
1045 if (depth == PM3_ESOFT) {
1046 PrintAndLogEx(ERR, "Error, BMP file is too small");
1047 free(bmp);
1048 return PM3_ESOFT;
1049 } else if (depth == 1) {
1050 PrintAndLogEx(DEBUG, "BMP file is a bitmap");
1051 if (read_bmp_bitmap(bmp, bytes_read, model_nr, &black, &red) != PM3_SUCCESS) {
1052 free(bmp);
1053 return PM3_ESOFT;
1055 } else if (depth == 24) {
1056 PrintAndLogEx(DEBUG, "BMP file is a RGB");
1057 if (read_bmp_rgb(bmp, bytes_read, model_nr, &black, &red, filename, save_conversions) != PM3_SUCCESS) {
1058 free(bmp);
1059 return PM3_ESOFT;
1061 } else if (depth == 32) {
1062 PrintAndLogEx(DEBUG, "BMP file is a RGBA, we will ignore the Alpha channel");
1063 if (read_bmp_rgb(bmp, bytes_read, model_nr, &black, &red, filename, save_conversions) != PM3_SUCCESS) {
1064 free(bmp);
1065 return PM3_ESOFT;
1067 } else {
1068 PrintAndLogEx(ERR, "Error, BMP color depth %i not supported. Must be 1 (BW), 24 (RGB) or 32 (RGBA)", depth);
1069 free(bmp);
1070 return PM3_ESOFT;
1072 free(bmp);
1074 start_drawing(model_nr, black, red);
1075 free(black);
1076 if ((model_nr == M1in54B) || (model_nr == M2in13B)) {
1077 free(red);
1079 return PM3_SUCCESS;
1082 static command_t CommandTable[] = {
1083 {"help", CmdHelp, AlwaysAvailable, "This help"},
1084 {"loadbmp", CmdHF14AWSLoadBmp, IfPm3Iso14443a, "Load BMP file to Waveshare NFC ePaper"},
1085 {NULL, NULL, NULL, NULL}
1088 static int CmdHelp(const char *Cmd) {
1089 (void)Cmd; // Cmd is not used so far
1090 CmdsHelp(CommandTable);
1091 return PM3_SUCCESS;
1094 int CmdHFWaveshare(const char *Cmd) {
1095 clearCommandBuffer();
1096 return CmdsParse(CommandTable, Cmd);