1 //-----------------------------------------------------------------------------
3 //-----------------------------------------------------------------------------
4 // from ST25R3911B-NFC-Demo source code by Waveshare team
6 #include "cmdhfwaveshare.h"
11 #include "cmdparser.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
39 uint32_t Color_1
; //Color palette
46 #define EPD_1IN54BCV2 3
58 #define EPD_7IN5BCV2 15
62 typedef struct model_s
{
64 uint8_t len
; // The data sent in one time shall not be greater than 128-3
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
)) {
99 BMP_HEADER
*pbmpheader
= (BMP_HEADER
*)bmp
;
100 PrintAndLogEx(DEBUG
, "colorsused = %d", pbmpheader
->colorsused
);
101 PrintAndLogEx(DEBUG
, "pbmpheader->bpp = %d", pbmpheader
->bpp
);
102 if ((pbmpheader
->BMP_Width
!= models
[model_nr
].width
) || (pbmpheader
->BMP_Height
!= models
[model_nr
].height
)) {
103 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
);
105 return pbmpheader
->bpp
;
108 static int read_bmp_bitmap(const uint8_t *bmp
, const size_t bmpsize
, uint8_t model_nr
, uint8_t **black
, uint8_t **red
) {
109 BMP_HEADER
*pbmpheader
= (BMP_HEADER
*)bmp
;
110 // check file is bitmap
111 if (pbmpheader
->bpp
!= 1) {
114 if (pbmpheader
->B
== 'M' || pbmpheader
->M
== 'B') { //0x4d42
115 PrintAndLogEx(WARNING
, "The file is not a BMP!");
118 PrintAndLogEx(DEBUG
, "file size = %d", pbmpheader
->fsize
);
119 PrintAndLogEx(DEBUG
, "file offset = %d", pbmpheader
->offset
);
120 if (pbmpheader
->fsize
> bmpsize
) {
121 PrintAndLogEx(WARNING
, "The file is truncated!");
124 uint8_t color_flag
= pbmpheader
->Color_1
;
125 // Get BMP file data pointer
126 uint32_t offset
= pbmpheader
->offset
;
127 uint16_t width
= pbmpheader
->BMP_Width
;
128 uint16_t height
= pbmpheader
->BMP_Height
;
129 if ((width
+ 8) * height
> WSMAPSIZE
* 8) {
130 PrintAndLogEx(WARNING
, "The file is too large, aborting!");
135 uint16_t Image_Width_Byte
= (width
% 8 == 0) ? (width
/ 8) : (width
/ 8 + 1);
136 uint16_t Bmp_Width_Byte
= (Image_Width_Byte
% 4 == 0) ? Image_Width_Byte
: ((Image_Width_Byte
/ 4 + 1) * 4);
138 *black
= calloc(WSMAPSIZE
, sizeof(uint8_t));
139 if (*black
== NULL
) {
142 // Write data into RAM
143 for (Y
= 0; Y
< height
; Y
++) { // columns
144 for (X
= 0; X
< Bmp_Width_Byte
; X
++) { // lines
145 if ((X
< Image_Width_Byte
) && ((X
+ (height
- Y
- 1) * Image_Width_Byte
) < WSMAPSIZE
)) {
146 (*black
)[X
+ (height
- Y
- 1) * Image_Width_Byte
] = color_flag
? bmp
[offset
] : ~bmp
[offset
];
151 if ((model_nr
== M1in54B
) || (model_nr
== M2in13B
)) {
152 // for BW+Red screens:
153 *red
= calloc(WSMAPSIZE
, sizeof(uint8_t));
162 static void rgb_to_gray(int16_t *chanR
, int16_t *chanG
, int16_t *chanB
, uint16_t width
, uint16_t height
, int16_t *chanGrey
) {
163 for (uint16_t Y
= 0; Y
< height
; Y
++) {
164 for (uint16_t X
= 0; X
< width
; X
++) {
165 // greyscale conversion
166 float Clinear
= 0.2126 * chanR
[X
+ Y
* width
] + 0.7152 * chanG
[X
+ Y
* width
] + 0.0722 * chanB
[X
+ Y
* width
];
167 // Csrgb = 12.92 Clinear when Clinear <= 0.0031308
168 // Csrgb = 1.055 Clinear1/2.4 - 0.055 when Clinear > 0.0031308
169 chanGrey
[X
+ Y
* width
] = Clinear
;
174 // Floyd-Steinberg dithering
175 static void dither_chan_inplace(int16_t *chan
, uint16_t width
, uint16_t height
) {
176 for (uint16_t Y
= 0; Y
< height
; Y
++) {
177 for (uint16_t X
= 0; X
< width
; X
++) {
178 int16_t oldp
= chan
[X
+ Y
* width
];
179 int16_t newp
= oldp
> 127 ? 255 : 0;
180 chan
[X
+ Y
* width
] = newp
;
181 int16_t err
= oldp
- newp
;
182 float m
[] = {7, 3, 5, 1};
184 chan
[X
+ 1 + Y
* width
] = chan
[X
+ 1 + Y
* width
] + m
[0] / 16 * err
;
186 if (Y
< height
- 1) {
187 chan
[X
- 1 + (Y
+ 1) * width
] = chan
[X
- 1 + (Y
+ 1) * width
] + m
[1] / 16 * err
;
188 chan
[X
+ (Y
+ 1) * width
] = chan
[X
+ (Y
+ 1) * width
] + m
[2] / 16 * err
;
190 if ((X
< width
- 1) && (Y
< height
- 1)) {
191 chan
[X
+ 1 + (Y
+ 1) * width
] = chan
[X
+ 1 + (Y
+ 1) * width
] + m
[3] / 16 * err
;
197 static uint32_t color_compare(int16_t r1
, int16_t g1
, int16_t b1
, int16_t r2
, int16_t g2
, int16_t b2
) {
198 // Compute (square of) distance from oldR/G/B to this color
199 int16_t inR
= r1
- r2
;
200 int16_t inG
= g1
- g2
;
201 int16_t inB
= b1
- b2
;
202 // use RGB-to-grey weighting
203 float dist
= 0.2126 * inR
* inR
+ 0.7152 * inG
* inG
+ 0.0722 * inB
* inB
;
207 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
) {
208 uint32_t bestdist
= 0x7FFFFFFF;
209 for (uint16_t i
= 0; i
< palettelen
; i
++) {
210 uint8_t R
= palette
[i
* 3 + 0];
211 uint8_t G
= palette
[i
* 3 + 1];
212 uint8_t B
= palette
[i
* 3 + 2];
213 uint32_t dist
= color_compare(oldR
, oldG
, oldB
, R
, G
, B
);
214 if (dist
< bestdist
) {
223 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
) {
224 for (uint16_t Y
= 0; Y
< height
; Y
++) {
225 for (uint16_t X
= 0; X
< width
; X
++) {
226 // scan odd lines in the opposite direction
231 int16_t oldR
= chanR
[XX
+ Y
* width
];
232 int16_t oldG
= chanG
[XX
+ Y
* width
];
233 int16_t oldB
= chanB
[XX
+ Y
* width
];
234 uint8_t newR
= 0, newG
= 0, newB
= 0;
235 nearest_color(oldR
, oldG
, oldB
, palette
, palettelen
, &newR
, &newG
, &newB
);
236 chanR
[XX
+ Y
* width
] = newR
;
237 chanG
[XX
+ Y
* width
] = newG
;
238 chanB
[XX
+ Y
* width
] = newB
;
239 int16_t errR
= oldR
- newR
;
240 int16_t errG
= oldG
- newG
;
241 int16_t errB
= oldB
- newB
;
242 float m
[] = {7, 3, 5, 1};
245 chanR
[XX
- 1 + Y
* width
] = (chanR
[XX
- 1 + Y
* width
] + m
[0] / 16 * errR
);
246 chanG
[XX
- 1 + Y
* width
] = (chanG
[XX
- 1 + Y
* width
] + m
[0] / 16 * errG
);
247 chanB
[XX
- 1 + Y
* width
] = (chanB
[XX
- 1 + Y
* width
] + m
[0] / 16 * errB
);
249 if (Y
< height
- 1) {
250 chanR
[XX
- 1 + (Y
+ 1) * width
] = (chanR
[XX
- 1 + (Y
+ 1) * width
] + m
[3] / 16 * errR
);
251 chanG
[XX
- 1 + (Y
+ 1) * width
] = (chanG
[XX
- 1 + (Y
+ 1) * width
] + m
[3] / 16 * errG
);
252 chanB
[XX
- 1 + (Y
+ 1) * width
] = (chanB
[XX
- 1 + (Y
+ 1) * width
] + m
[3] / 16 * errB
);
253 chanR
[XX
+ (Y
+ 1) * width
] = (chanR
[XX
+ (Y
+ 1) * width
] + m
[2] / 16 * errR
);
254 chanG
[XX
+ (Y
+ 1) * width
] = (chanG
[XX
+ (Y
+ 1) * width
] + m
[2] / 16 * errG
);
255 chanB
[XX
+ (Y
+ 1) * width
] = (chanB
[XX
+ (Y
+ 1) * width
] + m
[2] / 16 * errB
);
257 if ((XX
< width
- 1) && (Y
< height
- 1)) {
258 chanR
[XX
+ 1 + (Y
+ 1) * width
] = (chanR
[XX
+ 1 + (Y
+ 1) * width
] + m
[1] / 16 * errR
);
259 chanG
[XX
+ 1 + (Y
+ 1) * width
] = (chanG
[XX
+ 1 + (Y
+ 1) * width
] + m
[1] / 16 * errG
);
260 chanB
[XX
+ 1 + (Y
+ 1) * width
] = (chanB
[XX
+ 1 + (Y
+ 1) * width
] + m
[1] / 16 * errB
);
263 if (XX
< width
- 1) {
264 chanR
[XX
+ 1 + Y
* width
] = (chanR
[XX
+ 1 + Y
* width
] + m
[0] / 16 * errR
);
265 chanG
[XX
+ 1 + Y
* width
] = (chanG
[XX
+ 1 + Y
* width
] + m
[0] / 16 * errG
);
266 chanB
[XX
+ 1 + Y
* width
] = (chanB
[XX
+ 1 + Y
* width
] + m
[0] / 16 * errB
);
268 if (Y
< height
- 1) {
269 chanR
[XX
- 1 + (Y
+ 1) * width
] = (chanR
[XX
- 1 + (Y
+ 1) * width
] + m
[1] / 16 * errR
);
270 chanG
[XX
- 1 + (Y
+ 1) * width
] = (chanG
[XX
- 1 + (Y
+ 1) * width
] + m
[1] / 16 * errG
);
271 chanB
[XX
- 1 + (Y
+ 1) * width
] = (chanB
[XX
- 1 + (Y
+ 1) * width
] + m
[1] / 16 * errB
);
272 chanR
[XX
+ (Y
+ 1) * width
] = (chanR
[XX
+ (Y
+ 1) * width
] + m
[2] / 16 * errR
);
273 chanG
[XX
+ (Y
+ 1) * width
] = (chanG
[XX
+ (Y
+ 1) * width
] + m
[2] / 16 * errG
);
274 chanB
[XX
+ (Y
+ 1) * width
] = (chanB
[XX
+ (Y
+ 1) * width
] + m
[2] / 16 * errB
);
276 if ((XX
< width
- 1) && (Y
< height
- 1)) {
277 chanR
[XX
+ 1 + (Y
+ 1) * width
] = (chanR
[XX
+ 1 + (Y
+ 1) * width
] + m
[3] / 16 * errR
);
278 chanG
[XX
+ 1 + (Y
+ 1) * width
] = (chanG
[XX
+ 1 + (Y
+ 1) * width
] + m
[3] / 16 * errG
);
279 chanB
[XX
+ 1 + (Y
+ 1) * width
] = (chanB
[XX
+ 1 + (Y
+ 1) * width
] + m
[3] / 16 * errB
);
286 static void rgb_to_gray_red_inplace(int16_t *chanR
, int16_t *chanG
, int16_t *chanB
, uint16_t width
, uint16_t height
) {
287 for (uint16_t Y
= 0; Y
< height
; Y
++) {
288 for (uint16_t X
= 0; X
< width
; X
++) {
289 float Clinear
= 0.2126 * chanR
[X
+ Y
* width
] + 0.7152 * chanG
[X
+ Y
* width
] + 0.0722 * chanB
[X
+ Y
* width
];
290 if ((chanR
[X
+ Y
* width
] < chanG
[X
+ Y
* width
] && chanR
[X
+ Y
* width
] < chanB
[X
+ Y
* width
])) {
291 chanR
[X
+ Y
* width
] = Clinear
;
292 chanG
[X
+ Y
* width
] = Clinear
;
293 chanB
[X
+ Y
* width
] = Clinear
;
299 static void threshold_chan(int16_t *colorchan
, uint16_t width
, uint16_t height
, uint8_t threshold
, uint8_t *colormap
) {
300 for (uint16_t Y
= 0; Y
< height
; Y
++) {
301 for (uint16_t X
= 0; X
< width
; X
++) {
302 colormap
[X
+ Y
* width
] = colorchan
[X
+ Y
* width
] < threshold
;
307 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
) {
308 for (uint16_t Y
= 0; Y
< height
; Y
++) {
309 for (uint16_t X
= 0; X
< width
; X
++) {
310 if ((chanR
[X
+ Y
* width
] < threshold_black
) && (chanG
[X
+ Y
* width
] < threshold_black
) && (chanB
[X
+ Y
* width
] < threshold_black
)) {
311 blackmap
[X
+ Y
* width
] = 1;
312 redmap
[X
+ Y
* width
] = 0;
313 } else if ((chanR
[X
+ Y
* width
] > threshold_red
) && (chanG
[X
+ Y
* width
] < threshold_black
) && (chanB
[X
+ Y
* width
] < threshold_black
)) {
314 blackmap
[X
+ Y
* width
] = 0;
315 redmap
[X
+ Y
* width
] = 1;
317 blackmap
[X
+ Y
* width
] = 0;
318 redmap
[X
+ Y
* width
] = 0;
324 static void map8to1(uint8_t *colormap
, uint16_t width
, uint16_t height
, uint8_t *colormap8
) {
326 if (width
% 8 == 0) {
329 width8
= width
/ 8 + 1;
333 for (uint16_t Y
= 0; Y
< height
; Y
++) {
334 for (uint16_t X
= 0; X
< width
; X
++) {
335 data
= data
| colormap
[X
+ Y
* width
];
337 if ((count
>= 8) || (X
== width
- 1)) {
338 colormap8
[X
/ 8 + Y
* width8
] = (~data
) & 0xFF;
342 data
= (data
<< 1) & 0xFF;
347 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
) {
348 BMP_HEADER
*pbmpheader
= (BMP_HEADER
*)bmp
;
349 // check file is full color
350 if ((pbmpheader
->bpp
!= 24) && (pbmpheader
->bpp
!= 32)) {
354 if (pbmpheader
->B
== 'M' || pbmpheader
->M
== 'B') { //0x4d42
355 PrintAndLogEx(WARNING
, "The file is not a BMP!");
359 PrintAndLogEx(DEBUG
, "file size = %d", pbmpheader
->fsize
);
360 PrintAndLogEx(DEBUG
, "file offset = %d", pbmpheader
->offset
);
361 if (pbmpheader
->fsize
> bmpsize
) {
362 PrintAndLogEx(WARNING
, "The file is truncated!");
366 // Get BMP file data pointer
367 uint32_t offset
= pbmpheader
->offset
;
368 uint16_t width
= pbmpheader
->BMP_Width
;
369 uint16_t height
= pbmpheader
->BMP_Height
;
370 if ((width
+ 8) * height
> WSMAPSIZE
* 8) {
371 PrintAndLogEx(WARNING
, "The file is too large, aborting!");
375 int16_t *chanR
= calloc(width
* height
, sizeof(int16_t));
380 int16_t *chanG
= calloc(width
* height
, sizeof(int16_t));
386 int16_t *chanB
= calloc(width
* height
, sizeof(int16_t));
393 // Extracting BMP chans
394 for (uint16_t Y
= 0; Y
< height
; Y
++) {
395 for (uint16_t X
= 0; X
< width
; X
++) {
396 chanB
[X
+ (height
- Y
- 1) * width
] = bmp
[offset
++];
397 chanG
[X
+ (height
- Y
- 1) * width
] = bmp
[offset
++];
398 chanR
[X
+ (height
- Y
- 1) * width
] = bmp
[offset
++];
399 if (pbmpheader
->bpp
== 32) // Skip Alpha chan
406 if ((model_nr
== M1in54B
) || (model_nr
== M2in13B
)) {
407 // for BW+Red screens:
408 uint8_t *mapBlack
= calloc((uint32_t)(width
* height
), sizeof(uint8_t));
409 if (mapBlack
== NULL
) {
415 uint8_t *mapRed
= calloc((uint32_t)(width
* height
), sizeof(uint8_t));
416 if (mapRed
== NULL
) {
423 rgb_to_gray_red_inplace(chanR
, chanG
, chanB
, width
, height
);
425 uint8_t palette
[] = {0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00}; // black, white, red
426 dither_rgb_inplace(chanR
, chanG
, chanB
, width
, height
, palette
, sizeof(palette
) / 3);
428 threshold_rgb_black_red(chanR
, chanG
, chanB
, width
, height
, 128, 128, mapBlack
, mapRed
);
429 if (save_conversions
) {
431 offset
= pbmpheader
->offset
;
432 for (uint16_t Y
= 0; Y
< height
; Y
++) {
433 for (uint16_t X
= 0; X
< width
; X
++) {
434 bmp
[offset
++] = chanB
[X
+ (height
- Y
- 1) * width
] & 0xFF;
435 bmp
[offset
++] = chanG
[X
+ (height
- Y
- 1) * width
] & 0xFF;
436 bmp
[offset
++] = chanR
[X
+ (height
- Y
- 1) * width
] & 0xFF;
437 if (pbmpheader
->bpp
== 32) // Fill Alpha chan
438 bmp
[offset
++] = 0xFF;
443 PrintAndLogEx(INFO
, "Saving red+black dithered version...");
444 if (saveFile(filename
, ".bmp", bmp
, offset
) != PM3_SUCCESS
) {
445 PrintAndLogEx(WARNING
, "Could not save file " _YELLOW_("%s"), filename
);
457 *black
= calloc(WSMAPSIZE
, sizeof(uint8_t));
458 if (*black
== NULL
) {
463 map8to1(mapBlack
, width
, height
, *black
);
465 *red
= calloc(WSMAPSIZE
, sizeof(uint8_t));
471 map8to1(mapRed
, width
, height
, *red
);
474 // for BW-only screens:
475 int16_t *chanGrey
= calloc(width
* height
, sizeof(int16_t));
476 if (chanGrey
== NULL
) {
482 rgb_to_gray(chanR
, chanG
, chanB
, width
, height
, chanGrey
);
483 dither_chan_inplace(chanGrey
, width
, height
);
485 uint8_t *mapBlack
= calloc(width
* height
, sizeof(uint8_t));
486 if (mapBlack
== NULL
) {
493 threshold_chan(chanGrey
, width
, height
, 128, mapBlack
);
495 if (save_conversions
) {
497 offset
= pbmpheader
->offset
;
498 for (uint16_t Y
= 0; Y
< height
; Y
++) {
499 for (uint16_t X
= 0; X
< width
; X
++) {
500 bmp
[offset
++] = chanGrey
[X
+ (height
- Y
- 1) * width
] & 0xFF;
501 bmp
[offset
++] = chanGrey
[X
+ (height
- Y
- 1) * width
] & 0xFF;
502 bmp
[offset
++] = chanGrey
[X
+ (height
- Y
- 1) * width
] & 0xFF;
503 if (pbmpheader
->bpp
== 32) // Fill Alpha chan
504 bmp
[offset
++] = 0xFF;
509 PrintAndLogEx(INFO
, "Saving black dithered version...");
510 if (saveFile(filename
, ".bmp", bmp
, offset
) != PM3_SUCCESS
) {
511 PrintAndLogEx(WARNING
, "Could not save file " _YELLOW_("%s"), filename
);
524 *black
= calloc(WSMAPSIZE
, sizeof(uint8_t));
525 if (*black
== NULL
) {
529 map8to1(mapBlack
, width
, height
, *black
);
535 static void read_black(uint32_t i
, uint8_t *l
, uint8_t model_nr
, uint8_t *black
) {
536 for (uint8_t j
= 0; j
< models
[model_nr
].len
; j
++) {
537 l
[3 + j
] = black
[i
* models
[model_nr
].len
+ j
];
540 static void read_red(uint32_t i
, uint8_t *l
, uint8_t model_nr
, uint8_t *red
) {
541 // spurious warning with GCC10 (-Wstringop-overflow) when j is uint8_t, even if all len are < 128
542 for (uint16_t j
= 0; j
< models
[model_nr
].len
; j
++) {
543 if (model_nr
== M1in54B
) {
544 //1.54B needs to flip the red picture data, other screens do not need to flip data
545 l
[3 + j
] = ~red
[i
* models
[model_nr
].len
+ j
];
547 l
[3 + j
] = red
[i
* models
[model_nr
].len
+ j
];
552 static int transceive_blocking(uint8_t *txBuf
, uint16_t txBufLen
, uint8_t *rxBuf
, uint16_t rxBufLen
, uint16_t *actLen
, bool retransmit
) {
553 uint8_t fail_num
= 0;
559 PacketResponseNG resp
;
560 SendCommandMIX(CMD_HF_ISO14443A_READER
, ISO14A_RAW
| ISO14A_APPEND_CRC
| ISO14A_NO_DISCONNECT
, txBufLen
, 0, txBuf
, txBufLen
);
562 if (WaitForResponseTimeout(CMD_ACK
, &resp
, 2000)) {
563 if (resp
.oldarg
[0] > rxBufLen
) {
564 PrintAndLogEx(WARNING
, "Received %"PRIu64
" bytes, rxBuf too small (%u)", resp
.oldarg
[0], rxBufLen
);
565 memcpy(rxBuf
, resp
.data
.asBytes
, rxBufLen
);
569 memcpy(rxBuf
, resp
.data
.asBytes
, resp
.oldarg
[0]);
570 *actLen
= resp
.oldarg
[0];
573 if ((retransmit
) && (rxBuf
[0] != 0 || rxBuf
[1] != 0)) {
577 PrintAndLogEx(WARNING
, "Transmission failed, please try again.");
589 // 1.54B does not share the common base and requires specific handling
590 static int start_drawing_1in54B(uint8_t model_nr
, uint8_t *black
, uint8_t *red
) {
592 uint8_t step_5
[128] = {0xcd, 0x05, 100};
593 uint8_t step_4
[2] = {0xcd, 0x04};
594 uint8_t step_6
[2] = {0xcd, 0x06};
595 uint8_t rx
[20] = {0};
596 uint16_t actrxlen
[20], i
= 0, progress
= 0;
598 if (model_nr
== M1in54B
) {
601 PrintAndLogEx(DEBUG
, "1.54_Step9: e-paper config2 (black)");
602 if (model_nr
== M1in54B
) { //1.54inch B Keychain
603 for (i
= 0; i
< 50; i
++) {
604 read_black(i
, step_5
, model_nr
, black
);
605 ret
= transceive_blocking(step_5
, 103, rx
, 20, actrxlen
, true); // cd 05
606 if (ret
!= PM3_SUCCESS
) {
609 progress
= i
* 100 / 100;
610 PrintAndLogEx(INPLACE
, "Progress: %d %%", progress
);
614 PrintAndLogEx(DEBUG
, "1.54_Step6: e-paper power on");
615 ret
= transceive_blocking(step_4
, 2, rx
, 20, actrxlen
, true); //cd 04
616 if (ret
!= PM3_SUCCESS
) {
619 PrintAndLogEx(DEBUG
, "1.54_Step7: e-paper config2 (red)");
620 if (model_nr
== M1in54B
) { //1.54inch B Keychain
621 for (i
= 0; i
< 50; i
++) {
622 read_red(i
, step_5
, model_nr
, red
);
623 ret
= transceive_blocking(step_5
, 103, rx
, 20, actrxlen
, true); // cd 05
624 if (ret
!= PM3_SUCCESS
) {
627 progress
= i
* 100 / 100 + 50;
628 PrintAndLogEx(INPLACE
, "Progress: %d %%", progress
);
632 // Send update instructions
633 PrintAndLogEx(DEBUG
, "1.54_Step8: EDP load to main");
634 ret
= transceive_blocking(step_6
, 2, rx
, 20, actrxlen
, true); //cd 06
635 if (ret
!= PM3_SUCCESS
) {
638 PrintAndLogEx(DEBUG
, "1.54_Step9");
642 static int start_drawing(uint8_t model_nr
, uint8_t *black
, uint8_t *red
) {
643 uint8_t progress
= 0;
644 uint8_t step0
[2] = {0xcd, 0x0d};
645 uint8_t step1
[3] = {0xcd, 0x00, 10}; // select e-paper type and reset e-paper
646 // 4 :2.13inch e-Paper
647 // 7 :2.9inch e-Paper
648 // 10 :4.2inch e-Paper
649 // 14 :7.5inch e-Paper
650 uint8_t step2
[2] = {0xcd, 0x01}; // e-paper normal mode type:
651 uint8_t step3
[2] = {0xcd, 0x02}; // e-paper config1
652 uint8_t step4
[2] = {0xcd, 0x03}; // e-paper power on
653 uint8_t step5
[2] = {0xcd, 0x05}; // e-paper config2
654 uint8_t step6
[2] = {0xcd, 0x06}; // EDP load to main
655 uint8_t step7
[2] = {0xcd, 0x07}; // Data preparation
657 uint8_t step8
[123] = {0xcd, 0x08, 0x64}; // Data start command
658 // 2.13inch(0x10:Send 16 data at a time)
659 // 2.9inch(0x10:Send 16 data at a time)
660 // 4.2inch(0x64:Send 100 data at a time)
661 // 7.5inch(0x78:Send 120 data at a time)
662 uint8_t step9
[2] = {0xcd, 0x18}; // e-paper power on
663 uint8_t step10
[2] = {0xcd, 0x09}; // Refresh e-paper
664 uint8_t step11
[2] = {0xcd, 0x0a}; // wait for ready
665 uint8_t step12
[2] = {0xcd, 0x04}; // e-paper power off command
666 uint8_t step13
[124] = {0xcd, 0x19, 121};
667 // uint8_t step13[2]={0xcd,0x0b}; // Judge whether the power supply is turned off successfully
668 // uint8_t step14[2]={0xcd,0x0c}; // The end of the transmission
670 uint16_t actrxlen
[20], i
= 0;
674 clearCommandBuffer();
675 SendCommandMIX(CMD_HF_ISO14443A_READER
, ISO14A_CONNECT
| ISO14A_NO_DISCONNECT
, 0, 0, NULL
, 0);
676 PacketResponseNG resp
;
677 if (!WaitForResponseTimeout(CMD_ACK
, &resp
, 2500)) {
678 PrintAndLogEx(ERR
, "No tag found");
683 iso14a_card_select_t card
;
684 memcpy(&card
, (iso14a_card_select_t
*)resp
.data
.asBytes
, sizeof(iso14a_card_select_t
));
686 uint64_t select_status
= resp
.oldarg
[0];
688 if (select_status
== 0) {
689 PrintAndLogEx(ERR
, "Tag select error");
692 } else if (select_status
== 3) {
693 PrintAndLogEx(WARNING
, "Card doesn't support standard iso14443-3 anticollision, doesn't look like Waveshare tag");
698 if ((card
.uidlen
!= 7) || ((memcmp(card
.uid
, "FSTN10m", 7) != 0) && (memcmp(card
.uid
, "WSDZ10m", 7) != 0))) {
699 PrintAndLogEx(WARNING
, "Card doesn't look like Waveshare tag");
703 if (((model_nr
!= M1in54B
) && (memcmp(card
.uid
, "FSTN10m", 7) == 0))) {
704 PrintAndLogEx(WARNING
, "Card is a Waveshare tag 1.54\", not %s", models
[model_nr
].desc
);
708 if (((model_nr
== M1in54B
) && (memcmp(card
.uid
, "FSTN10m", 7) != 0))) {
709 PrintAndLogEx(WARNING
, "Card is not a Waveshare tag 1.54\", check your model number");
713 PrintAndLogEx(DEBUG
, "model_nr = %d", model_nr
);
715 PrintAndLogEx(DEBUG
, "Step0");
716 ret
= transceive_blocking(step0
, 2, rx
, 20, actrxlen
, true); //cd 0d
717 if (ret
!= PM3_SUCCESS
) {
720 PrintAndLogEx(DEBUG
, "Step1: e-paper config");
721 //step1[2] screen model
722 //step8[2] nr of bytes sent at once
723 //step13[2] nr of bytes sent for the second time
724 // generally, step8 sends a black image, step13 sends a red image
725 if (model_nr
== M2in13
) { //2.13inch
726 step1
[2] = EPD_2IN13V2
;
729 } else if (model_nr
== M2in9
) { //2.9inch
733 } else if (model_nr
== M4in2
) { //4.2inch
737 } else if (model_nr
== M7in5
) { //7.5inch
738 step1
[2] = EPD_7IN5V2
;
741 } else if (model_nr
== M2in7
) { //2.7inch
744 // Send blank data for the first time, and send other data to 0xff without processing the bottom layer
746 //Sending the second data is the real image data. If the previous 0xff is not sent, the last output image is abnormally black
747 } else if (model_nr
== M2in13B
) { //2.13inch B
748 step1
[2] = EPD_2IN13BC
;
751 } else if (model_nr
== M7in5HD
) {
752 step1
[2] = EPD_7IN5HD
;
757 if (model_nr
== M1in54B
) {
758 ret
= transceive_blocking(step1
, 2, rx
, 20, actrxlen
, true); //cd 00
760 ret
= transceive_blocking(step1
, 3, rx
, 20, actrxlen
, true);
762 if (ret
!= PM3_SUCCESS
) {
766 PrintAndLogEx(DEBUG
, "Step2: e-paper normal mode type");
767 ret
= transceive_blocking(step2
, 2, rx
, 20, actrxlen
, true); //cd 01
768 if (ret
!= PM3_SUCCESS
) {
772 PrintAndLogEx(DEBUG
, "Step3: e-paper config1");
773 ret
= transceive_blocking(step3
, 2, rx
, 20, actrxlen
, true); //cd 02
774 if (ret
!= PM3_SUCCESS
) {
778 PrintAndLogEx(DEBUG
, "Step4: e-paper power on");
779 ret
= transceive_blocking(step4
, 2, rx
, 20, actrxlen
, true); //cd 03
780 if (ret
!= PM3_SUCCESS
) {
783 if (model_nr
== M1in54B
) {
784 // 1.54B Keychain handler
785 PrintAndLogEx(DEBUG
, "Start_Drawing_1in54B");
786 ret
= start_drawing_1in54B(model_nr
, black
, red
);
787 if (ret
!= PM3_SUCCESS
) {
790 //1.54B Data transfer is complete and wait for refresh
792 PrintAndLogEx(DEBUG
, "Step5: e-paper config2");
793 ret
= transceive_blocking(step5
, 2, rx
, 20, actrxlen
, true); //cd 05
794 if (ret
!= PM3_SUCCESS
) {
798 PrintAndLogEx(DEBUG
, "Step6: EDP load to main") ;
799 ret
= transceive_blocking(step6
, 2, rx
, 20, actrxlen
, true); //cd 06
800 if (ret
!= PM3_SUCCESS
) {
804 PrintAndLogEx(DEBUG
, "Step7: Data preparation");
805 ret
= transceive_blocking(step7
, 2, rx
, 20, actrxlen
, true); //cd 07
806 if (ret
!= PM3_SUCCESS
) {
809 PrintAndLogEx(DEBUG
, "Step8: Start data transfer");
810 if (model_nr
== M2in13
) { //2.13inch
811 for (i
= 0; i
< 250; i
++) {
812 read_black(i
, step8
, model_nr
, black
);
813 ret
= transceive_blocking(step8
, 19, rx
, 20, actrxlen
, true); // cd 08
814 if (ret
!= PM3_SUCCESS
) {
817 progress
= i
* 100 / 250;
818 PrintAndLogEx(INPLACE
, "Progress: %d %%", progress
);
820 } else if (model_nr
== M2in9
) {
821 for (i
= 0; i
< 296; i
++) {
822 read_black(i
, step8
, model_nr
, black
);
823 ret
= transceive_blocking(step8
, 19, rx
, 20, actrxlen
, true); // cd 08
824 if (ret
!= PM3_SUCCESS
) {
827 progress
= i
* 100 / 296;
828 PrintAndLogEx(INPLACE
, "Progress: %d %%", progress
);
830 } else if (model_nr
== M4in2
) { //4.2inch
831 for (i
= 0; i
< 150; i
++) {
832 read_black(i
, step8
, model_nr
, black
);
833 ret
= transceive_blocking(step8
, 103, rx
, 20, actrxlen
, true); // cd 08
834 if (ret
!= PM3_SUCCESS
) {
837 progress
= i
* 100 / 150;
838 PrintAndLogEx(INPLACE
, "Progress: %d %%", progress
);
840 } else if (model_nr
== M7in5
) { //7.5inch
841 for (i
= 0; i
< 400; i
++) {
842 read_black(i
, step8
, model_nr
, black
);
843 ret
= transceive_blocking(step8
, 123, rx
, 20, actrxlen
, true); // cd 08
844 if (ret
!= PM3_SUCCESS
) {
847 progress
= i
* 100 / 400;
848 PrintAndLogEx(INPLACE
, "Progress: %d %%", progress
);
851 } else if (model_nr
== M2in13B
) { //2.13inch B
852 for (i
= 0; i
< 26; i
++) {
853 read_black(i
, step8
, model_nr
, black
);
854 ret
= transceive_blocking(step8
, 109, rx
, 20, actrxlen
, false); // cd 08
855 if (ret
!= PM3_SUCCESS
) {
858 progress
= i
* 50 / 26;
859 PrintAndLogEx(INPLACE
, "Progress: %d %%", progress
);
861 } else if (model_nr
== M7in5HD
) { //7.5HD
863 for (i
= 0; i
< 484; i
++) {
864 read_black(i
, step8
, model_nr
, black
);
865 //memset(&step8[3], 0xf0, 120);
866 ret
= transceive_blocking(step8
, 123, rx
, 20, actrxlen
, true); // cd 08
867 if (ret
!= PM3_SUCCESS
) {
870 progress
= i
* 100 / 484;
871 PrintAndLogEx(INPLACE
, "Progress: %d %%", progress
);
873 memset(&step8
[3], 0xff, 120);
874 ret
= transceive_blocking(step8
, 110 + 3, rx
, 20, actrxlen
, true); // cd 08
875 if (ret
!= PM3_SUCCESS
) {
878 } else if (model_nr
== M2in7
) { //2.7inch
879 for (i
= 0; i
< 48; i
++) {
880 //read_black(i,step8, model_nr, black);
881 memset(&step8
[3], 0xFF, sizeof(step8
) - 3);
882 ret
= transceive_blocking(step8
, 124, rx
, 20, actrxlen
, true); // cd 08
883 if (ret
!= PM3_SUCCESS
) {
886 progress
= i
* 50 / 48;
887 PrintAndLogEx(INPLACE
, "Progress: %d %%", progress
);
891 PrintAndLogEx(DEBUG
, "Step9: e-paper power on");
892 if (model_nr
== M2in13
|| model_nr
== M2in9
|| model_nr
== M4in2
|| model_nr
== M7in5
|| model_nr
== M7in5HD
) {
893 ret
= transceive_blocking(step9
, 2, rx
, 20, actrxlen
, true); //cd 18
894 // The black-and-white screen sending backplane is also shielded, with no effect. Except 2.7
895 if (ret
!= PM3_SUCCESS
) {
898 } else if (model_nr
== M2in13B
|| model_nr
== M2in7
) {
899 ret
= transceive_blocking(step9
, 2, rx
, 20, actrxlen
, true); //cd 18
900 if (ret
!= PM3_SUCCESS
) {
903 PrintAndLogEx(DEBUG
, "Step9b");
904 if (model_nr
== M2in7
) {
905 for (i
= 0; i
< 48; i
++) {
906 read_black(i
, step13
, model_nr
, black
);
907 ret
= transceive_blocking(step13
, 124, rx
, 20, actrxlen
, true); //CD 19
908 if (ret
!= PM3_SUCCESS
) {
911 progress
= i
* 50 / 48 + 50;
912 PrintAndLogEx(INPLACE
, "Progress: %d %%", progress
);
914 } else if (model_nr
== M2in13B
) {
915 for (i
= 0; i
< 26; i
++) {
916 read_red(i
, step13
, model_nr
, red
);
917 //memset(&step13[3], 0xfE, 106);
918 ret
= transceive_blocking(step13
, 109, rx
, 20, actrxlen
, false);
919 if (ret
!= PM3_SUCCESS
) {
922 progress
= i
* 50 / 26 + 50;
923 PrintAndLogEx(INPLACE
, "Progress: %d %%", progress
);
928 PrintAndLogEx(DEBUG
, "Step10: Refresh e-paper");
929 ret
= transceive_blocking(step10
, 2, rx
, 20, actrxlen
, true); //cd 09 refresh command
930 if (ret
!= PM3_SUCCESS
) {
935 PrintAndLogEx(DEBUG
, "Step11: Wait tag to be ready");
936 PrintAndLogEx(INPLACE
, "E-paper Reflashing, Waiting");
937 if (model_nr
== M2in13B
|| model_nr
== M1in54B
) { // Black, white and red screen refresh time is longer, wait first
939 } else if (model_nr
== M7in5HD
) {
942 uint8_t fail_num
= 0;
944 if (model_nr
== M1in54B
) {
945 // send 0xcd 0x08 with 1.54B
946 ret
= transceive_blocking(step8
, 2, rx
, 20, actrxlen
, false); //cd 08
948 ret
= transceive_blocking(step11
, 2, rx
, 20, actrxlen
, false); //cd 0a
950 if (ret
!= PM3_SUCCESS
) {
953 if (rx
[0] == 0xff && rx
[1] == 0) {
954 PrintAndLogEx(NORMAL
, "");
955 PrintAndLogEx(SUCCESS
, "E-paper Reflash OK");
960 PrintAndLogEx(WARNING
, "Update failed, please try again.");
965 PrintAndLogEx(INPLACE
, "E-paper Reflashing, Waiting");
970 PrintAndLogEx(DEBUG
, "Step12: e-paper power off command");
971 ret
= transceive_blocking(step12
, 2, rx
, 20, actrxlen
, true); //cd 04
972 if (ret
!= PM3_SUCCESS
) {
976 PrintAndLogEx(SUCCESS
, "E-paper Update OK");
982 static int CmdHF14AWSLoadBmp(const char *Cmd
) {
984 char desc
[800] = {0};
985 for (uint8_t i
= 0; i
< MEND
; i
++) {
986 snprintf(desc
+ strlen(desc
),
987 sizeof(desc
) - strlen(desc
),
988 "hf waveshare loadbmp -f myfile -m %2u -> %s ( %u, %u )\n",
996 CLIParserContext
*ctx
;
997 CLIParserInit(&ctx
, "hf waveshare loadbmp",
998 "Load BMP file to Waveshare NFC ePaper.",
1003 snprintf(modeldesc
, sizeof(modeldesc
), "model number [0 - %d] of your tag", MEND
- 1);
1005 void *argtable
[] = {
1007 arg_int1("m", NULL
, "<nr>", modeldesc
),
1008 arg_lit0("s", "save", "save dithered version in filename-[n].bmp, only for RGB BMP"),
1009 arg_str1("f", "file", "<filename>", "filename[.bmp] to upload to tag"),
1013 CLIExecWithReturn(ctx
, Cmd
, argtable
, false);
1015 int model_nr
= arg_get_int_def(ctx
, 1, -1);
1016 bool save_conversions
= arg_get_lit(ctx
, 2);
1019 char filename
[FILE_PATH_SIZE
] = {0};
1020 CLIParamStrToBuf(arg_get_str(ctx
, 3), (uint8_t *)filename
, FILE_PATH_SIZE
, &fnlen
);
1025 PrintAndLogEx(WARNING
, "Missing filename");
1028 if (model_nr
== -1) {
1029 PrintAndLogEx(WARNING
, "Missing model");
1032 if (model_nr
>= MEND
) {
1033 PrintAndLogEx(WARNING
, "Unknown model");
1037 uint8_t *bmp
= NULL
;
1038 uint8_t *black
= NULL
;
1039 uint8_t *red
= NULL
;
1040 size_t bytes_read
= 0;
1041 if (loadFile_safe(filename
, ".bmp", (void **)&bmp
, &bytes_read
) != PM3_SUCCESS
) {
1042 PrintAndLogEx(WARNING
, "Could not find file " _YELLOW_("%s"), filename
);
1046 int depth
= picture_bit_depth(bmp
, bytes_read
, model_nr
);
1047 if (depth
== PM3_ESOFT
) {
1048 PrintAndLogEx(ERR
, "Error, BMP file is too small");
1051 } else if (depth
== 1) {
1052 PrintAndLogEx(DEBUG
, "BMP file is a bitmap");
1053 if (read_bmp_bitmap(bmp
, bytes_read
, model_nr
, &black
, &red
) != PM3_SUCCESS
) {
1057 } else if (depth
== 24) {
1058 PrintAndLogEx(DEBUG
, "BMP file is a RGB");
1059 if (read_bmp_rgb(bmp
, bytes_read
, model_nr
, &black
, &red
, filename
, save_conversions
) != PM3_SUCCESS
) {
1063 } else if (depth
== 32) {
1064 PrintAndLogEx(DEBUG
, "BMP file is a RGBA, we will ignore the Alpha channel");
1065 if (read_bmp_rgb(bmp
, bytes_read
, model_nr
, &black
, &red
, filename
, save_conversions
) != PM3_SUCCESS
) {
1070 PrintAndLogEx(ERR
, "Error, BMP color depth %i not supported. Must be 1 (BW), 24 (RGB) or 32 (RGBA)", depth
);
1076 start_drawing(model_nr
, black
, red
);
1078 if ((model_nr
== M1in54B
) || (model_nr
== M2in13B
)) {
1084 static command_t CommandTable
[] = {
1085 {"help", CmdHelp
, AlwaysAvailable
, "This help"},
1086 {"loadbmp", CmdHF14AWSLoadBmp
, IfPm3Iso14443a
, "Load BMP file to Waveshare NFC ePaper"},
1087 {NULL
, NULL
, NULL
, NULL
}
1090 static int CmdHelp(const char *Cmd
) {
1091 (void)Cmd
; // Cmd is not used so far
1092 CmdsHelp(CommandTable
);
1096 int CmdHFWaveshare(const char *Cmd
) {
1097 clearCommandBuffer();
1098 return CmdsParse(CommandTable
, Cmd
);