//----------------------------------------------------------------------------- // Waveshare commands //----------------------------------------------------------------------------- // from ST25R3911B-NFC-Demo source code by Waveshare team #include "cmdhfwaveshare.h" #include #include #include "comms.h" #include "cmdparser.h" #include "ui.h" #include "util.h" #include "fileutils.h" #include "util_posix.h" // msleep // Currently the largest pixel 880*528 only needs 58.08K bytes #define WSMAPSIZE 60000 typedef struct { uint8_t B; uint8_t M; uint32_t fsize; uint16_t res1; uint16_t res2; uint32_t offset; uint32_t Bit_Pixel; uint32_t BMP_Width; uint32_t BMP_Height; uint16_t planes; uint16_t bpp; uint32_t ctype; uint32_t dsize; uint32_t hppm; uint32_t vppm; uint32_t colorsused; uint32_t colorreq; uint32_t Color_1; //Color palette uint32_t Color_2; } PACKED BMP_HEADER; #define EPD_1IN54B 0 #define EPD_1IN54C 1 #define EPD_1IN54V2 2 #define EPD_1IN54BCV2 3 #define EPD_2IN13V2 4 #define EPD_2IN13BC 5 #define EPD_2IN13D 6 #define EPD_2IN9 7 #define EPD_2IN9BC 8 #define EPD_2IN9D 9 #define EPD_4IN2 10 #define EPD_4IN2BC 11 #define EPD_7IN5 12 #define EPD_7IN5BC 13 #define EPD_7IN5V2 14 #define EPD_7IN5BCV2 15 #define EPD_2IN7 16 #define EPD_7IN5HD 17 typedef struct model_s { const char *desc; uint8_t len; // The data sent in one time shall not be greater than 128-3 uint16_t width; uint16_t height; } model_t; typedef enum { M2in13 = 0, M2in9, M4in2, M7in5, M2in7, M2in13B, M1in54B, M7in5HD, MEND } model_enum_t; static model_t models[] = { {"2.13 inch e-paper", 16, 122, 250}, // tested {"2.9 inch e-paper", 16, 296, 128}, {"4.2 inch e-paper", 100, 400, 300}, // tested {"7.5 inch e-paper", 120, 800, 480}, {"2.7 inch e-paper", 121, 276, 176}, {"2.13 inch e-paper B (with red)", 106, 212, 104}, {"1.54 inch e-paper B (with red)", 100, 200, 200}, {"7.5 inch e-paper HD", 120, 880, 528}, }; static int CmdHelp(const char *Cmd); static int usage_hf_waveshare_loadbmp(void) { PrintAndLogEx(NORMAL, "Load BMP file to Waveshare NFC ePaper."); PrintAndLogEx(NORMAL, "Usage: hf waveshare loadbmp [h] f m [s]"); PrintAndLogEx(NORMAL, " Options :"); PrintAndLogEx(NORMAL, " f : " _YELLOW_("filename[.bmp]") " to upload to tag"); PrintAndLogEx(NORMAL, " m : " _YELLOW_("model number") " of your tag"); PrintAndLogEx(NORMAL, " s : save dithered version in filename-[n].bmp, only for RGB BMP"); for (uint8_t i = 0; i < MEND; i++) { PrintAndLogEx(NORMAL, " m %2i : %s", i, models[i].desc); } PrintAndLogEx(NORMAL, ""); PrintAndLogEx(NORMAL, "Examples:"); PrintAndLogEx(NORMAL, _YELLOW_(" hf waveshare loadbmp m 0 f myfile")); PrintAndLogEx(NORMAL, ""); return PM3_SUCCESS; } static int picture_bit_depth(const uint8_t *bmp, const size_t bmpsize, const uint8_t model_nr) { if (bmpsize < sizeof(BMP_HEADER)) return PM3_ESOFT; BMP_HEADER *pbmpheader = (BMP_HEADER *)bmp; PrintAndLogEx(DEBUG, "colorsused = %d", pbmpheader->colorsused); PrintAndLogEx(DEBUG, "pbmpheader->bpp = %d", pbmpheader->bpp); if ((pbmpheader->BMP_Width != models[model_nr].width) || (pbmpheader->BMP_Height != models[model_nr].height)) { 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); } return pbmpheader->bpp; } static int read_bmp_bitmap(const uint8_t *bmp, const size_t bmpsize, uint8_t model_nr, uint8_t **black, uint8_t **red) { BMP_HEADER *pbmpheader = (BMP_HEADER *)bmp; // check file is bitmap if (pbmpheader->bpp != 1) { return PM3_ESOFT; } if (pbmpheader->B == 'M' || pbmpheader->M == 'B') { //0x4d42 PrintAndLogEx(WARNING, "The file is not a BMP!"); return PM3_ESOFT; } PrintAndLogEx(DEBUG, "file size = %d", pbmpheader->fsize); PrintAndLogEx(DEBUG, "file offset = %d", pbmpheader->offset); if (pbmpheader->fsize > bmpsize) { PrintAndLogEx(WARNING, "The file is truncated!"); return PM3_ESOFT; } uint8_t color_flag = pbmpheader->Color_1; // Get BMP file data pointer uint32_t offset = pbmpheader->offset; uint16_t X, Y; uint16_t Image_Width_Byte = (pbmpheader->BMP_Width % 8 == 0) ? (pbmpheader->BMP_Width / 8) : (pbmpheader->BMP_Width / 8 + 1); uint16_t Bmp_Width_Byte = (Image_Width_Byte % 4 == 0) ? Image_Width_Byte : ((Image_Width_Byte / 4 + 1) * 4); *black = calloc(WSMAPSIZE, sizeof(uint8_t)); if (*black == NULL) { return PM3_EMALLOC; } // Write data into RAM for (Y = 0; Y < pbmpheader->BMP_Height; Y++) { // columns for (X = 0; X < Bmp_Width_Byte; X++) { // lines if ((X < Image_Width_Byte) && ((X + (pbmpheader->BMP_Height - Y - 1) * Image_Width_Byte) < WSMAPSIZE)) { (*black)[X + (pbmpheader->BMP_Height - Y - 1) * Image_Width_Byte] = color_flag ? bmp[offset] : ~bmp[offset]; } offset++; } } if ((model_nr == M1in54B) || (model_nr == M2in13B)) { // for BW+Red screens: *red = calloc(WSMAPSIZE, sizeof(uint8_t)); if (*red == NULL) { free(*black); return PM3_EMALLOC; } } return PM3_SUCCESS; } static void rgb_to_gray(int16_t *chanR, int16_t *chanG, int16_t *chanB, uint16_t width, uint16_t height, int16_t *chanGrey) { for (uint16_t Y = 0; Y < height; Y++) { for (uint16_t X = 0; X < width; X++) { // greyscale conversion float Clinear = 0.2126 * chanR[X + Y * width] + 0.7152 * chanG[X + Y * width] + 0.0722 * chanB[X + Y * width]; // Csrgb = 12.92 Clinear when Clinear <= 0.0031308 // Csrgb = 1.055 Clinear1/2.4 - 0.055 when Clinear > 0.0031308 chanGrey[X + Y * width] = Clinear; } } } // Floyd-Steinberg dithering static void dither_chan_inplace(int16_t *chan, uint16_t width, uint16_t height) { for (uint16_t Y = 0; Y < height; Y++) { for (uint16_t X = 0; X < width; X++) { int16_t oldp = chan[X + Y * width]; int16_t newp = oldp > 127 ? 255 : 0; chan[X + Y * width] = newp; int16_t err = oldp - newp; float m[] = {7, 3, 5, 1}; if (X < width - 1) { chan[X + 1 + Y * width] = chan[X + 1 + Y * width] + m[0] / 16 * err; } if (Y < height - 1) { chan[X - 1 + (Y + 1) * width] = chan[X - 1 + (Y + 1) * width] + m[1] / 16 * err; } if (Y < height - 1) { chan[X + (Y + 1) * width] = chan[X + (Y + 1) * width] + m[2] / 16 * err; } if ((X < width - 1) && (Y < height - 1)) { chan[X + 1 + (Y + 1) * width] = chan[X + 1 + (Y + 1) * width] + m[3] / 16 * err; } } } } static uint32_t color_compare(int16_t r1, int16_t g1, int16_t b1, int16_t r2, int16_t g2, int16_t b2) { // Compute (square of) distance from oldR/G/B to this color int16_t inR = r1 - r2; int16_t inG = g1 - g2; int16_t inB = b1 - b2; // use RGB-to-grey weighting float dist = 0.2126 * inR * inR + 0.7152 * inG * inG + 0.0722 * inB * inB; return dist; } 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) { uint32_t bestdist = 0x7FFFFFFF; for (uint16_t i = 0; i < palettelen; i++) { uint8_t R = palette[i * 3 + 0]; uint8_t G = palette[i * 3 + 1]; uint8_t B = palette[i * 3 + 2]; uint32_t dist = color_compare(oldR, oldG, oldB, R, G, B); if (dist < bestdist) { bestdist = dist; *newR = R; *newG = G; *newB = B; } } } 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) { for (uint16_t Y = 0; Y < height; Y++) { for (uint16_t X = 0; X < width; X++) { // scan odd lines in the opposite direction uint16_t XX = X; if (Y % 2) { XX = width - X - 1; } int16_t oldR = chanR[XX + Y * width]; int16_t oldG = chanG[XX + Y * width]; int16_t oldB = chanB[XX + Y * width]; uint8_t newR, newG, newB; nearest_color(oldR, oldG, oldB, palette, palettelen, &newR, &newG, &newB); chanR[XX + Y * width] = newR; chanG[XX + Y * width] = newG; chanB[XX + Y * width] = newB; int16_t errR = oldR - newR; int16_t errG = oldG - newG; int16_t errB = oldB - newB; float m[] = {7, 3, 5, 1}; if (Y % 2) { if (XX > 0) { chanR[XX - 1 + Y * width] = (chanR[XX - 1 + Y * width] + m[0] / 16 * errR); chanG[XX - 1 + Y * width] = (chanG[XX - 1 + Y * width] + m[0] / 16 * errG); chanB[XX - 1 + Y * width] = (chanB[XX - 1 + Y * width] + m[0] / 16 * errB); } if (Y < height - 1) { chanR[XX - 1 + (Y + 1) * width] = (chanR[XX - 1 + (Y + 1) * width] + m[3] / 16 * errR); chanG[XX - 1 + (Y + 1) * width] = (chanG[XX - 1 + (Y + 1) * width] + m[3] / 16 * errG); chanB[XX - 1 + (Y + 1) * width] = (chanB[XX - 1 + (Y + 1) * width] + m[3] / 16 * errB); } if (Y < height - 1) { chanR[XX + (Y + 1) * width] = (chanR[XX + (Y + 1) * width] + m[2] / 16 * errR); chanG[XX + (Y + 1) * width] = (chanG[XX + (Y + 1) * width] + m[2] / 16 * errG); chanB[XX + (Y + 1) * width] = (chanB[XX + (Y + 1) * width] + m[2] / 16 * errB); } if ((XX < width - 1) && (Y < height - 1)) { chanR[XX + 1 + (Y + 1) * width] = (chanR[XX + 1 + (Y + 1) * width] + m[1] / 16 * errR); chanG[XX + 1 + (Y + 1) * width] = (chanG[XX + 1 + (Y + 1) * width] + m[1] / 16 * errG); chanB[XX + 1 + (Y + 1) * width] = (chanB[XX + 1 + (Y + 1) * width] + m[1] / 16 * errB); } } else { if (XX < width - 1) { chanR[XX + 1 + Y * width] = (chanR[XX + 1 + Y * width] + m[0] / 16 * errR); chanG[XX + 1 + Y * width] = (chanG[XX + 1 + Y * width] + m[0] / 16 * errG); chanB[XX + 1 + Y * width] = (chanB[XX + 1 + Y * width] + m[0] / 16 * errB); } if (Y < height - 1) { chanR[XX - 1 + (Y + 1) * width] = (chanR[XX - 1 + (Y + 1) * width] + m[1] / 16 * errR); chanG[XX - 1 + (Y + 1) * width] = (chanG[XX - 1 + (Y + 1) * width] + m[1] / 16 * errG); chanB[XX - 1 + (Y + 1) * width] = (chanB[XX - 1 + (Y + 1) * width] + m[1] / 16 * errB); } if (Y < height - 1) { chanR[XX + (Y + 1) * width] = (chanR[XX + (Y + 1) * width] + m[2] / 16 * errR); chanG[XX + (Y + 1) * width] = (chanG[XX + (Y + 1) * width] + m[2] / 16 * errG); chanB[XX + (Y + 1) * width] = (chanB[XX + (Y + 1) * width] + m[2] / 16 * errB); } if ((XX < width - 1) && (Y < height - 1)) { chanR[XX + 1 + (Y + 1) * width] = (chanR[XX + 1 + (Y + 1) * width] + m[3] / 16 * errR); chanG[XX + 1 + (Y + 1) * width] = (chanG[XX + 1 + (Y + 1) * width] + m[3] / 16 * errG); chanB[XX + 1 + (Y + 1) * width] = (chanB[XX + 1 + (Y + 1) * width] + m[3] / 16 * errB); } } } } } static void rgb_to_gray_red_inplace(int16_t *chanR, int16_t *chanG, int16_t *chanB, uint16_t width, uint16_t height) { for (uint16_t Y = 0; Y < height; Y++) { for (uint16_t X = 0; X < width; X++) { float Clinear = 0.2126 * chanR[X + Y * width] + 0.7152 * chanG[X + Y * width] + 0.0722 * chanB[X + Y * width]; if ((chanR[X + Y * width] < chanG[X + Y * width] && chanR[X + Y * width] < chanB[X + Y * width])) { chanR[X + Y * width] = Clinear; chanG[X + Y * width] = Clinear; chanB[X + Y * width] = Clinear; } } } } static void threshold_chan(int16_t *colorchan, uint16_t width, uint16_t height, uint8_t threshold, uint8_t *colormap) { for (uint16_t Y = 0; Y < height; Y++) { for (uint16_t X = 0; X < width; X++) { colormap[X + Y * width] = colorchan[X + Y * width] < threshold; } } } 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) { for (uint16_t Y = 0; Y < height; Y++) { for (uint16_t X = 0; X < width; X++) { if ((chanR[X + Y * width] < threshold_black) && (chanG[X + Y * width] < threshold_black) && (chanB[X + Y * width] < threshold_black)) { blackmap[X + Y * width] = 1; redmap[X + Y * width] = 0; } else if ((chanR[X + Y * width] > threshold_red) && (chanG[X + Y * width] < threshold_black) && (chanB[X + Y * width] < threshold_black)) { blackmap[X + Y * width] = 0; redmap[X + Y * width] = 1; } else { blackmap[X + Y * width] = 0; redmap[X + Y * width] = 0; } } } } static void map8to1(uint8_t *colormap, uint16_t width, uint16_t height, uint8_t *colormap8) { uint16_t width8; if (width % 8 == 0) { width8 = width / 8; } else { width8 = width / 8 + 1; } uint8_t data = 0; uint8_t count = 0; for (uint16_t Y = 0; Y < height; Y++) { for (uint16_t X = 0; X < width; X++) { data = data | colormap[X + Y * width]; count += 1; if ((count >= 8) || (X == width - 1)) { colormap8[X / 8 + Y * width8] = (~data) & 0xFF; count = 0; data = 0; } data = (data << 1) & 0xFF; } } } 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) { BMP_HEADER *pbmpheader = (BMP_HEADER *)bmp; // check file is full color if (pbmpheader->bpp != 24) { return PM3_ESOFT; } if (pbmpheader->B == 'M' || pbmpheader->M == 'B') { //0x4d42 PrintAndLogEx(WARNING, "The file is not a BMP!"); return PM3_ESOFT; } PrintAndLogEx(DEBUG, "file size = %d", pbmpheader->fsize); PrintAndLogEx(DEBUG, "file offset = %d", pbmpheader->offset); if (pbmpheader->fsize > bmpsize) { PrintAndLogEx(WARNING, "The file is truncated!"); return PM3_ESOFT; } // Get BMP file data pointer uint32_t offset = pbmpheader->offset; uint16_t width = pbmpheader->BMP_Width; uint16_t height = pbmpheader->BMP_Height; int16_t *chanR = calloc(width * height, sizeof(int16_t)); if (chanR == NULL) { return PM3_EMALLOC; } int16_t *chanG = calloc(width * height, sizeof(int16_t)); if (chanG == NULL) { free(chanR); return PM3_EMALLOC; } int16_t *chanB = calloc(width * height, sizeof(int16_t)); if (chanB == NULL) { free(chanR); free(chanG); return PM3_EMALLOC; } // Extracting BMP chans for (uint16_t Y = 0; Y < height; Y++) { for (uint16_t X = 0; X < width; X++) { chanB[X + (height - Y - 1) * width] = bmp[offset++]; chanG[X + (height - Y - 1) * width] = bmp[offset++]; chanR[X + (height - Y - 1) * width] = bmp[offset++]; } // Skip line padding offset += width % 4; } if ((model_nr == M1in54B) || (model_nr == M2in13B)) { // for BW+Red screens: uint8_t *mapBlack = calloc(width * height, sizeof(uint8_t)); if (mapBlack == NULL) { free(chanR); free(chanG); free(chanB); return PM3_EMALLOC; } uint8_t *mapRed = calloc(width * height, sizeof(uint8_t)); if (mapRed == NULL) { free(chanR); free(chanG); free(chanB); free(mapBlack); return PM3_EMALLOC; } rgb_to_gray_red_inplace(chanR, chanG, chanB, width, height); uint8_t palette[] = {0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00}; // black, white, red dither_rgb_inplace(chanR, chanG, chanB, width, height, palette, sizeof(palette) / 3); threshold_rgb_black_red(chanR, chanG, chanB, width, height, 128, 128, mapBlack, mapRed); if (save_conversions) { // fill BMP chans offset = pbmpheader->offset; for (uint16_t Y = 0; Y < height; Y++) { for (uint16_t X = 0; X < width; X++) { bmp[offset++] = chanB[X + (height - Y - 1) * width] & 0xFF; bmp[offset++] = chanG[X + (height - Y - 1) * width] & 0xFF; bmp[offset++] = chanR[X + (height - Y - 1) * width] & 0xFF; } // Skip line padding offset += width % 4; } PrintAndLogEx(INFO, "Saving red+black dithered version..."); if (saveFile(filename, ".bmp", bmp, offset) != PM3_SUCCESS) { PrintAndLogEx(WARNING, "Could not save file " _YELLOW_("%s"), filename); free(chanR); free(chanG); free(chanB); free(mapBlack); free(mapRed); return PM3_EIO; } } free(chanR); free(chanG); free(chanB); *black = calloc(WSMAPSIZE, sizeof(uint8_t)); if (*black == NULL) { free(mapBlack); free(mapRed); return PM3_EMALLOC; } map8to1(mapBlack, width, height, *black); free(mapBlack); *red = calloc(WSMAPSIZE, sizeof(uint8_t)); if (*red == NULL) { free(mapRed); free(*black); return PM3_EMALLOC; } map8to1(mapRed, width, height, *red); free(mapRed); } else { // for BW-only screens: int16_t *chanGrey = calloc(width * height, sizeof(int16_t)); if (chanGrey == NULL) { free(chanR); free(chanG); free(chanB); return PM3_EMALLOC; } rgb_to_gray(chanR, chanG, chanB, width, height, chanGrey); dither_chan_inplace(chanGrey, width, height); uint8_t *mapBlack = calloc(width * height, sizeof(uint8_t)); if (mapBlack == NULL) { free(chanR); free(chanG); free(chanB); free(chanGrey); return PM3_EMALLOC; } threshold_chan(chanGrey, width, height, 128, mapBlack); if (save_conversions) { // fill BMP chans offset = pbmpheader->offset; for (uint16_t Y = 0; Y < height; Y++) { for (uint16_t X = 0; X < width; X++) { bmp[offset++] = chanGrey[X + (height - Y - 1) * width] & 0xFF; bmp[offset++] = chanGrey[X + (height - Y - 1) * width] & 0xFF; bmp[offset++] = chanGrey[X + (height - Y - 1) * width] & 0xFF; } // Skip line padding offset += width % 4; } PrintAndLogEx(INFO, "Saving black dithered version..."); if (saveFile(filename, ".bmp", bmp, offset) != PM3_SUCCESS) { PrintAndLogEx(WARNING, "Could not save file " _YELLOW_("%s"), filename); free(chanGrey); free(chanR); free(chanG); free(chanB); free(mapBlack); return PM3_EIO; } } free(chanGrey); free(chanR); free(chanG); free(chanB); *black = calloc(WSMAPSIZE, sizeof(uint8_t)); if (*black == NULL) { free(mapBlack); return PM3_EMALLOC; } map8to1(mapBlack, width, height, *black); free(mapBlack); } return PM3_SUCCESS; } static void read_black(uint32_t i, uint8_t *l, uint8_t model_nr, uint8_t *black) { for (uint8_t j = 0; j < models[model_nr].len; j++) { l[3 + j] = black[i * models[model_nr].len + j]; } } static void read_red(uint32_t i, uint8_t *l, uint8_t model_nr, uint8_t *red) { for (uint8_t j = 0; j < models[model_nr].len; j++) { if (model_nr == M1in54B) { //1.54B needs to flip the red picture data, other screens do not need to flip data l[3 + j] = ~red[i * models[model_nr].len + j]; } else { l[3 + j] = red[i * models[model_nr].len + j]; } } } static int transceive_blocking(uint8_t *txBuf, uint16_t txBufLen, uint8_t *rxBuf, uint16_t rxBufLen, uint16_t *actLen, bool retransmit) { uint8_t fail_num = 0; if (rxBufLen < 2) { return PM3_EINVARG; } while (1) { PacketResponseNG resp; SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_DISCONNECT, txBufLen, 0, txBuf, txBufLen); rxBuf[0] = 1; if (WaitForResponseTimeout(CMD_ACK, &resp, 2000)) { if (resp.oldarg[0] > rxBufLen) { PrintAndLogEx(WARNING, "Received %"PRIu32 " bytes, rxBuf too small (%u)", resp.oldarg[0], rxBufLen); memcpy(rxBuf, resp.data.asBytes, rxBufLen); *actLen = rxBufLen; return PM3_ESOFT; } memcpy(rxBuf, resp.data.asBytes, resp.oldarg[0]); *actLen = resp.oldarg[0]; } if ((retransmit) && (rxBuf[0] != 0 || rxBuf[1] != 0)) { fail_num++; if (fail_num > 10) { PROMPT_CLEARLINE; PrintAndLogEx(WARNING, "Transmission failed, please try again."); DropField(); return PM3_ESOFT; } } else { break; } } return PM3_SUCCESS; } // 1.54B Keychain // 1.54B does not share the common base and requires specific handling static int start_drawing_1in54B(uint8_t model_nr, uint8_t *black, uint8_t *red) { int ret; uint8_t step_5[128] = {0xcd, 0x05, 100}; uint8_t step_4[2] = {0xcd, 0x04}; uint8_t step_6[2] = {0xcd, 0x06}; uint8_t rx[20] = {0}; uint16_t actrxlen[20], i = 0, progress = 0; if (model_nr == M1in54B) { step_5[2] = 100; } PrintAndLogEx(DEBUG, "1.54_Step9: e-paper config2 (black)"); if (model_nr == M1in54B) { //1.54inch B Keychain for (i = 0; i < 50; i++) { read_black(i, step_5, model_nr, black); ret = transceive_blocking(step_5, 103, rx, 20, actrxlen, true); // cd 05 if (ret != PM3_SUCCESS) { return ret; } progress = i * 100 / 100; PrintAndLogEx(INPLACE, "Progress: %d %%", progress); } } PROMPT_CLEARLINE; PrintAndLogEx(DEBUG, "1.54_Step6: e-paper power on"); ret = transceive_blocking(step_4, 2, rx, 20, actrxlen, true); //cd 04 if (ret != PM3_SUCCESS) { return ret; } PrintAndLogEx(DEBUG, "1.54_Step7: e-paper config2 (red)"); if (model_nr == M1in54B) { //1.54inch B Keychain for (i = 0; i < 50; i++) { read_red(i, step_5, model_nr, red); ret = transceive_blocking(step_5, 103, rx, 20, actrxlen, true); // cd 05 if (ret != PM3_SUCCESS) { return ret; } progress = i * 100 / 100 + 50; PrintAndLogEx(INPLACE, "Progress: %d %%", progress); } } PROMPT_CLEARLINE; // Send update instructions PrintAndLogEx(DEBUG, "1.54_Step8: EDP load to main"); ret = transceive_blocking(step_6, 2, rx, 20, actrxlen, true); //cd 06 if (ret != PM3_SUCCESS) { return ret; } PrintAndLogEx(DEBUG, "1.54_Step9"); return PM3_SUCCESS; } static int start_drawing(uint8_t model_nr, uint8_t *black, uint8_t *red) { uint8_t progress = 0; uint8_t step0[2] = {0xcd, 0x0d}; uint8_t step1[3] = {0xcd, 0x00, 10}; //select e-paper type and reset e-paper 4:2.13inch e-Paper 7:2.9inch e-Paper 10:4.2inch e-Paper 14:7.5inch e-Paper uint8_t step2[2] = {0xcd, 0x01}; //e-paper normal mode type: uint8_t step3[2] = {0xcd, 0x02}; //e-paper config1 uint8_t step4[2] = {0xcd, 0x03}; //e-paper power on uint8_t step5[2] = {0xcd, 0x05}; //e-paper config2 uint8_t step6[2] = {0xcd, 0x06}; //EDP load to main uint8_t step7[2] = {0xcd, 0x07}; //Data preparation uint8_t step8[123] = {0xcd, 0x08, 0x64}; //Data start command 2.13inch(0x10:Send 16 data at a time) 2.9inch(0x10:Send 16 data at a time) 4.2inch(0x64:Send 100 data at a time) 7.5inch(0x78:Send 120 data at a time) uint8_t step9[2] = {0xcd, 0x18}; //e-paper power on uint8_t step10[2] = {0xcd, 0x09}; //Refresh e-paper uint8_t step11[2] = {0xcd, 0x0a}; //wait for ready uint8_t step12[2] = {0xcd, 0x04}; //e-paper power off command uint8_t step13[124] = {0xcd, 0x19, 121}; // uint8_t step13[2]={0xcd,0x0b}; //Judge whether the power supply is turned off successfully // uint8_t step14[2]={0xcd,0x0c}; //The end of the transmission uint8_t rx[20]; uint16_t actrxlen[20], i = 0; clearCommandBuffer(); SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_NO_DISCONNECT, 0, 0, NULL, 0); PacketResponseNG resp; if (!WaitForResponseTimeout(CMD_ACK, &resp, 2500)) { PrintAndLogEx(ERR, "No tag found"); DropField(); return PM3_ETIMEOUT; } iso14a_card_select_t card; memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t)); uint64_t select_status = resp.oldarg[0]; if (select_status == 0) { PrintAndLogEx(ERR, "Tag select error"); DropField(); return PM3_ERFTRANS; } else if (select_status == 3) { PrintAndLogEx(WARNING, "Card doesn't support standard iso14443-3 anticollision, doesn't look like Waveshare tag"); DropField(); return PM3_ESOFT; } if ((card.uidlen != 7) || (memcmp(card.uid, "WSDZ10m", 7) != 0)) { PrintAndLogEx(WARNING, "Card doesn't look like Waveshare tag"); DropField(); return PM3_ESOFT; } PrintAndLogEx(DEBUG, "model_nr = %d", model_nr); int ret; PrintAndLogEx(DEBUG, "Step0"); ret = transceive_blocking(step0, 2, rx, 20, actrxlen, true); //cd 0d if (ret != PM3_SUCCESS) { return ret; } PrintAndLogEx(DEBUG, "Step1: e-paper config"); //step1[2] screen model //step8[2] nr of bytes sent at once //step13[2] nr of bytes sent for the second time // generally, step8 sends a black image, step13 sends a red image if (model_nr == M2in13) { //2.13inch step1[2] = EPD_2IN13V2; step8[2] = 16; step13[2] = 0; } else if (model_nr == M2in9) { //2.9inch step1[2] = EPD_2IN9; step8[2] = 16; step13[2] = 0; } else if (model_nr == M4in2) { //4.2inch step1[2] = EPD_4IN2; step8[2] = 100; step13[2] = 0; } else if (model_nr == M7in5) { //7.5inch step1[2] = EPD_7IN5V2; step8[2] = 120; step13[2] = 0; } else if (model_nr == M2in7) { //2.7inch step1[2] = EPD_2IN7; step8[2] = 121; // Send blank data for the first time, and send other data to 0xff without processing the bottom layer step13[2] = 121; //Sending the second data is the real image data. If the previous 0xff is not sent, the last output image is abnormally black } else if (model_nr == M2in13B) { //2.13inch B step1[2] = EPD_2IN13BC; step8[2] = 106; step13[2] = 106; } else if (model_nr == M7in5HD) { step1[2] = EPD_7IN5HD; step8[2] = 120; step13[2] = 0; } if (model_nr == M1in54B) { ret = transceive_blocking(step1, 2, rx, 20, actrxlen, true); //cd 00 } else { ret = transceive_blocking(step1, 3, rx, 20, actrxlen, true); } if (ret != PM3_SUCCESS) { return ret; } msleep(100); PrintAndLogEx(DEBUG, "Step2: e-paper normal mode type"); ret = transceive_blocking(step2, 2, rx, 20, actrxlen, true); //cd 01 if (ret != PM3_SUCCESS) { return ret; } msleep(100); PrintAndLogEx(DEBUG, "Step3: e-paper config1"); ret = transceive_blocking(step3, 2, rx, 20, actrxlen, true); //cd 02 if (ret != PM3_SUCCESS) { return ret; } msleep(200); PrintAndLogEx(DEBUG, "Step4: e-paper power on"); ret = transceive_blocking(step4, 2, rx, 20, actrxlen, true); //cd 03 if (ret != PM3_SUCCESS) { return ret; } if (model_nr == M1in54B) { // 1.54B Keychain handler PrintAndLogEx(DEBUG, "Start_Drawing_1in54B"); ret = start_drawing_1in54B(model_nr, black, red); if (ret != PM3_SUCCESS) { return ret; } //1.54B Data transfer is complete and wait for refresh } else { PrintAndLogEx(DEBUG, "Step5: e-paper config2"); ret = transceive_blocking(step5, 2, rx, 20, actrxlen, true); //cd 05 if (ret != PM3_SUCCESS) { return ret; } msleep(100); PrintAndLogEx(DEBUG, "Step6: EDP load to main") ; ret = transceive_blocking(step6, 2, rx, 20, actrxlen, true); //cd 06 if (ret != PM3_SUCCESS) { return ret; } msleep(100); PrintAndLogEx(DEBUG, "Step7: Data preparation"); ret = transceive_blocking(step7, 2, rx, 20, actrxlen, true); //cd 07 if (ret != PM3_SUCCESS) { return ret; } PrintAndLogEx(DEBUG, "Step8: Start data transfer"); if (model_nr == M2in13) { //2.13inch for (i = 0; i < 250; i++) { read_black(i, step8, model_nr, black); ret = transceive_blocking(step8, 19, rx, 20, actrxlen, true); // cd 08 if (ret != PM3_SUCCESS) { return ret; } progress = i * 100 / 250; PrintAndLogEx(INPLACE, "Progress: %d %%", progress); } } else if (model_nr == M2in9) { for (i = 0; i < 296; i++) { read_black(i, step8, model_nr, black); ret = transceive_blocking(step8, 19, rx, 20, actrxlen, true); // cd 08 if (ret != PM3_SUCCESS) { return ret; } progress = i * 100 / 296; PrintAndLogEx(INPLACE, "Progress: %d %%", progress); } } else if (model_nr == M4in2) { //4.2inch for (i = 0; i < 150; i++) { read_black(i, step8, model_nr, black); ret = transceive_blocking(step8, 103, rx, 20, actrxlen, true); // cd 08 if (ret != PM3_SUCCESS) { return ret; } progress = i * 100 / 150; PrintAndLogEx(INPLACE, "Progress: %d %%", progress); } } else if (model_nr == M7in5) { //7.5inch for (i = 0; i < 400; i++) { read_black(i, step8, model_nr, black); ret = transceive_blocking(step8, 123, rx, 20, actrxlen, true); // cd 08 if (ret != PM3_SUCCESS) { return ret; } progress = i * 100 / 400; PrintAndLogEx(INPLACE, "Progress: %d %%", progress); msleep(6); } } else if (model_nr == M2in13B) { //2.13inch B for (i = 0; i < 26; i++) { read_black(i, step8, model_nr, black); ret = transceive_blocking(step8, 109, rx, 20, actrxlen, true); // cd 08 if (ret != PM3_SUCCESS) { return ret; } progress = i * 50 / 26; PrintAndLogEx(INPLACE, "Progress: %d %%", progress); } } else if (model_nr == M7in5HD) { //7.5HD for (i = 0; i < 484; i++) { read_black(i, step8, model_nr, black); //memset(&step8[3], 0xf0, 120); ret = transceive_blocking(step8, 123, rx, 20, actrxlen, true); // cd 08 if (ret != PM3_SUCCESS) { return ret; } progress = i * 100 / 484; PrintAndLogEx(INPLACE, "Progress: %d %%", progress); } memset(&step8[3], 0xff, 120); ret = transceive_blocking(step8, 110 + 3, rx, 20, actrxlen, true); // cd 08 if (ret != PM3_SUCCESS) { return ret; } } else if (model_nr == M2in7) { //2.7inch for (i = 0; i < 48; i++) { //read_black(i,step8, model_nr, black); memset(&step8[3], 0xFF, sizeof(step8) - 3); ret = transceive_blocking(step8, 124, rx, 20, actrxlen, true); // cd 08 if (ret != PM3_SUCCESS) { return ret; } progress = i * 50 / 48; PrintAndLogEx(INPLACE, "Progress: %d %%", progress); } } PROMPT_CLEARLINE; PrintAndLogEx(DEBUG, "Step9: e-paper power on"); if (model_nr == M2in13 || model_nr == M2in9 || model_nr == M4in2 || model_nr == M7in5 || model_nr == M7in5HD) { ret = transceive_blocking(step9, 2, rx, 20, actrxlen, true); //cd 18 // The black-and-white screen sending backplane is also shielded, with no effect. Except 2.7 if (ret != PM3_SUCCESS) { return ret; } } else if (model_nr == M2in13B || model_nr == M2in7) { ret = transceive_blocking(step9, 2, rx, 20, actrxlen, true); //cd 18 if (ret != PM3_SUCCESS) { return ret; } PrintAndLogEx(DEBUG, "Step9b"); if (model_nr == M2in7) { for (i = 0; i < 48; i++) { read_black(i, step13, model_nr, black); ret = transceive_blocking(step13, 124, rx, 20, actrxlen, true); //CD 19 if (ret != PM3_SUCCESS) { return ret; } progress = i * 50 / 48 + 50; PrintAndLogEx(INPLACE, "Progress: %d %%", progress); } } else if (model_nr == M2in13B) { for (i = 0; i < 26; i++) { read_red(i, step13, model_nr, red); //memset(&step13[3], 0xfE, 106); ret = transceive_blocking(step13, 109, rx, 20, actrxlen, true); if (ret != PM3_SUCCESS) { return ret; } progress = i * 50 / 26 + 50; PrintAndLogEx(INPLACE, "Progress: %d %%", progress); } } PROMPT_CLEARLINE; } PrintAndLogEx(DEBUG, "Step10: Refresh e-paper"); ret = transceive_blocking(step10, 2, rx, 20, actrxlen, true); //cd 09 refresh command if (ret != PM3_SUCCESS) { return ret; } msleep(200); } PrintAndLogEx(DEBUG, "Step11: Wait tag to be ready"); if (model_nr == M2in13B || model_nr == M1in54B) { // Black, white and red screen refresh time is longer, wait first msleep(9000); } else if (model_nr == M7in5HD) { msleep(1000); } uint8_t fail_num = 0; while (1) { if (model_nr == M1in54B) { // send 0xcd 0x08 with 1.54B ret = transceive_blocking(step8, 2, rx, 20, actrxlen, false); //cd 08 } else { ret = transceive_blocking(step11, 2, rx, 20, actrxlen, false); //cd 0a } if (ret != PM3_SUCCESS) { return ret; } if (rx[0] == 0xff && rx[1] == 0) { PrintAndLogEx(NORMAL, ""); PrintAndLogEx(SUCCESS, "E-paper Reflash OK"); msleep(200); break; } else { if (fail_num > 50) { PrintAndLogEx(WARNING, "Update failed, please try again."); DropField(); return PM3_ESOFT; } else { fail_num++; PrintAndLogEx(INPLACE, "E-paper Reflashing, Waiting"); msleep(100); } } } PrintAndLogEx(DEBUG, "Step12: e-paper power off command"); ret = transceive_blocking(step12, 2, rx, 20, actrxlen, true); //cd 04 if (ret != PM3_SUCCESS) { return ret; } msleep(200); PrintAndLogEx(SUCCESS, "E-paper Update OK"); msleep(200); DropField(); return PM3_SUCCESS; } static int CmdHF14AWSLoadBmp(const char *Cmd) { char filename[FILE_PATH_SIZE] = {0}; uint8_t cmdp = 0; bool errors = false; size_t filenamelen = 0; uint8_t model_nr = 0xff; bool save_conversions = false; while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { switch (tolower(param_getchar(Cmd, cmdp))) { case 'h': return usage_hf_waveshare_loadbmp(); case 'f': filenamelen = param_getstr(Cmd, cmdp + 1, filename, FILE_PATH_SIZE); if (filenamelen > FILE_PATH_SIZE - 5) filenamelen = FILE_PATH_SIZE - 5; cmdp += 2; break; case 'm': model_nr = param_get8(Cmd, cmdp + 1); cmdp += 2; break; case 's': save_conversions = true; cmdp += 1; break; default: PrintAndLogEx(WARNING, "Unknown parameter: " _RED_("'%c'"), param_getchar(Cmd, cmdp)); errors = true; break; } } //Validations if (filenamelen < 1) { PrintAndLogEx(WARNING, "Missing filename"); errors = true; } if (model_nr == 0xff) { PrintAndLogEx(WARNING, "Missing model"); errors = true; } else if (model_nr >= MEND) { PrintAndLogEx(WARNING, "Unknown model"); errors = true; } if (errors || cmdp == 0) return usage_hf_waveshare_loadbmp(); uint8_t *bmp = NULL; uint8_t *black = NULL; uint8_t *red = NULL; size_t bytes_read = 0; if (loadFile_safe(filename, ".bmp", (void **)&bmp, &bytes_read) != PM3_SUCCESS) { PrintAndLogEx(WARNING, "Could not find file " _YELLOW_("%s"), filename); return PM3_EIO; } int depth = picture_bit_depth(bmp, bytes_read, model_nr); if (depth == PM3_ESOFT) { PrintAndLogEx(ERR, "Error, BMP file is too small"); free(bmp); return PM3_ESOFT; } else if (depth == 1) { PrintAndLogEx(DEBUG, "BMP file is a bitmap"); if (read_bmp_bitmap(bmp, bytes_read, model_nr, &black, &red) != PM3_SUCCESS) { free(bmp); return PM3_ESOFT; } } else if (depth == 24) { PrintAndLogEx(DEBUG, "BMP file is a RGB"); if (read_bmp_rgb(bmp, bytes_read, model_nr, &black, &red, filename, save_conversions) != PM3_SUCCESS) { free(bmp); return PM3_ESOFT; } } else if (depth == 32) { PrintAndLogEx(ERR, "Error, BMP color depth %i not supported. Remove alpha channel.", depth); free(bmp); return PM3_ESOFT; } else { PrintAndLogEx(ERR, "Error, BMP color depth %i not supported", depth); free(bmp); return PM3_ESOFT; } free(bmp); start_drawing(model_nr, black, red); free(black); if ((model_nr == M1in54B) || (model_nr == M2in13B)) { free(red); } return PM3_SUCCESS; } static command_t CommandTable[] = { {"help", CmdHelp, AlwaysAvailable, "This help"}, {"loadbmp", CmdHF14AWSLoadBmp, IfPm3Iso14443a, "Load BMP file to Waveshare NFC ePaper"}, {NULL, NULL, NULL, NULL} }; static int CmdHelp(const char *Cmd) { (void)Cmd; // Cmd is not used so far CmdsHelp(CommandTable); return PM3_SUCCESS; } int CmdHFWaveshare(const char *Cmd) { clearCommandBuffer(); return CmdsParse(CommandTable, Cmd); }