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
))
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) {
112 if (pbmpheader
->B
== 'M' || pbmpheader
->M
== 'B') { //0x4d42
113 PrintAndLogEx(WARNING
, "The file is not a BMP!");
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!");
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!");
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
) {
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
];
149 if ((model_nr
== M1in54B
) || (model_nr
== M2in13B
)) {
150 // for BW+Red screens:
151 *red
= calloc(WSMAPSIZE
, sizeof(uint8_t));
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};
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
;
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
) {
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
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};
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
);
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;
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
) {
324 if (width
% 8 == 0) {
327 width8
= width
/ 8 + 1;
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
];
335 if ((count
>= 8) || (X
== width
- 1)) {
336 colormap8
[X
/ 8 + Y
* width8
] = (~data
) & 0xFF;
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)) {
352 if (pbmpheader
->B
== 'M' || pbmpheader
->M
== 'B') { //0x4d42
353 PrintAndLogEx(WARNING
, "The file is not a BMP!");
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!");
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!");
373 int16_t *chanR
= calloc(width
* height
, sizeof(int16_t));
378 int16_t *chanG
= calloc(width
* height
, sizeof(int16_t));
384 int16_t *chanB
= calloc(width
* height
, sizeof(int16_t));
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
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
) {
413 uint8_t *mapRed
= calloc(width
* height
, sizeof(uint8_t));
414 if (mapRed
== NULL
) {
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
) {
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;
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
);
455 *black
= calloc(WSMAPSIZE
, sizeof(uint8_t));
456 if (*black
== NULL
) {
461 map8to1(mapBlack
, width
, height
, *black
);
463 *red
= calloc(WSMAPSIZE
, sizeof(uint8_t));
469 map8to1(mapRed
, width
, height
, *red
);
472 // for BW-only screens:
473 int16_t *chanGrey
= calloc(width
* height
, sizeof(int16_t));
474 if (chanGrey
== NULL
) {
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
) {
491 threshold_chan(chanGrey
, width
, height
, 128, mapBlack
);
493 if (save_conversions
) {
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;
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
);
522 *black
= calloc(WSMAPSIZE
, sizeof(uint8_t));
523 if (*black
== NULL
) {
527 map8to1(mapBlack
, width
, height
, *black
);
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
];
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;
557 PacketResponseNG resp
;
558 SendCommandMIX(CMD_HF_ISO14443A_READER
, ISO14A_RAW
| ISO14A_APPEND_CRC
| ISO14A_NO_DISCONNECT
, txBufLen
, 0, txBuf
, txBufLen
);
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
);
567 memcpy(rxBuf
, resp
.data
.asBytes
, resp
.oldarg
[0]);
568 *actLen
= resp
.oldarg
[0];
571 if ((retransmit
) && (rxBuf
[0] != 0 || rxBuf
[1] != 0)) {
575 PrintAndLogEx(WARNING
, "Transmission failed, please try again.");
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
) {
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
) {
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
) {
607 progress
= i
* 100 / 100;
608 PrintAndLogEx(INPLACE
, "Progress: %d %%", progress
);
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
) {
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
) {
625 progress
= i
* 100 / 100 + 50;
626 PrintAndLogEx(INPLACE
, "Progress: %d %%", progress
);
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
) {
636 PrintAndLogEx(DEBUG
, "1.54_Step9");
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
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");
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");
690 } else if (select_status
== 3) {
691 PrintAndLogEx(WARNING
, "Card doesn't support standard iso14443-3 anticollision, doesn't look like Waveshare tag");
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");
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
);
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");
711 PrintAndLogEx(DEBUG
, "model_nr = %d", model_nr
);
713 PrintAndLogEx(DEBUG
, "Step0");
714 ret
= transceive_blocking(step0
, 2, rx
, 20, actrxlen
, true); //cd 0d
715 if (ret
!= PM3_SUCCESS
) {
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
;
727 } else if (model_nr
== M2in9
) { //2.9inch
731 } else if (model_nr
== M4in2
) { //4.2inch
735 } else if (model_nr
== M7in5
) { //7.5inch
736 step1
[2] = EPD_7IN5V2
;
739 } else if (model_nr
== M2in7
) { //2.7inch
742 // Send blank data for the first time, and send other data to 0xff without processing the bottom layer
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
;
749 } else if (model_nr
== M7in5HD
) {
750 step1
[2] = EPD_7IN5HD
;
755 if (model_nr
== M1in54B
) {
756 ret
= transceive_blocking(step1
, 2, rx
, 20, actrxlen
, true); //cd 00
758 ret
= transceive_blocking(step1
, 3, rx
, 20, actrxlen
, true);
760 if (ret
!= PM3_SUCCESS
) {
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
) {
770 PrintAndLogEx(DEBUG
, "Step3: e-paper config1");
771 ret
= transceive_blocking(step3
, 2, rx
, 20, actrxlen
, true); //cd 02
772 if (ret
!= PM3_SUCCESS
) {
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
) {
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
) {
788 //1.54B Data transfer is complete and wait for refresh
790 PrintAndLogEx(DEBUG
, "Step5: e-paper config2");
791 ret
= transceive_blocking(step5
, 2, rx
, 20, actrxlen
, true); //cd 05
792 if (ret
!= PM3_SUCCESS
) {
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
) {
802 PrintAndLogEx(DEBUG
, "Step7: Data preparation");
803 ret
= transceive_blocking(step7
, 2, rx
, 20, actrxlen
, true); //cd 07
804 if (ret
!= PM3_SUCCESS
) {
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
) {
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
) {
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
) {
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
) {
845 progress
= i
* 100 / 400;
846 PrintAndLogEx(INPLACE
, "Progress: %d %%", progress
);
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
) {
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
) {
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
) {
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
) {
884 progress
= i
* 50 / 48;
885 PrintAndLogEx(INPLACE
, "Progress: %d %%", progress
);
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
) {
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
) {
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
) {
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
) {
920 progress
= i
* 50 / 26 + 50;
921 PrintAndLogEx(INPLACE
, "Progress: %d %%", progress
);
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
) {
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
937 } else if (model_nr
== M7in5HD
) {
940 uint8_t fail_num
= 0;
942 if (model_nr
== M1in54B
) {
943 // send 0xcd 0x08 with 1.54B
944 ret
= transceive_blocking(step8
, 2, rx
, 20, actrxlen
, false); //cd 08
946 ret
= transceive_blocking(step11
, 2, rx
, 20, actrxlen
, false); //cd 0a
948 if (ret
!= PM3_SUCCESS
) {
951 if (rx
[0] == 0xff && rx
[1] == 0) {
952 PrintAndLogEx(NORMAL
, "");
953 PrintAndLogEx(SUCCESS
, "E-paper Reflash OK");
958 PrintAndLogEx(WARNING
, "Update failed, please try again.");
963 PrintAndLogEx(INPLACE
, "E-paper Reflashing, Waiting");
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
) {
974 PrintAndLogEx(SUCCESS
, "E-paper Update OK");
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",
994 CLIParserContext
*ctx
;
995 CLIParserInit(&ctx
, "hf waveshare loadbmp",
996 "Load BMP file to Waveshare NFC ePaper.",
1001 snprintf(modeldesc
, sizeof(modeldesc
), "model number [0 - %d] of your tag", MEND
- 1);
1003 void *argtable
[] = {
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"),
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);
1017 char filename
[FILE_PATH_SIZE
] = {0};
1018 CLIParamStrToBuf(arg_get_str(ctx
, 3), (uint8_t *)filename
, FILE_PATH_SIZE
, &fnlen
);
1023 PrintAndLogEx(WARNING
, "Missing filename");
1026 if (model_nr
== -1) {
1027 PrintAndLogEx(WARNING
, "Missing model");
1030 if (model_nr
>= MEND
) {
1031 PrintAndLogEx(WARNING
, "Unknown model");
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
);
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");
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
) {
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
) {
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
) {
1068 PrintAndLogEx(ERR
, "Error, BMP color depth %i not supported. Must be 1 (BW), 24 (RGB) or 32 (RGBA)", depth
);
1074 start_drawing(model_nr
, black
, red
);
1076 if ((model_nr
== M1in54B
) || (model_nr
== M2in13B
)) {
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
);
1094 int CmdHFWaveshare(const char *Cmd
) {
1095 clearCommandBuffer();
1096 return CmdsParse(CommandTable
, Cmd
);