#include #include std::string base64_encode(char* buf, unsigned int bufLen); HalDisplay::HalDisplay(int8_t sclk, int8_t mosi, int8_t cs, int8_t dc, int8_t rst, int8_t busy) : einkDisplay(sclk, mosi, cs, dc, rst, busy) { if (is_emulated) { emuFramebuffer0 = new uint8_t[BUFFER_SIZE]; } } HalDisplay::~HalDisplay() { if (emuFramebuffer0) { delete[] emuFramebuffer0; emuFramebuffer0 = nullptr; } } void HalDisplay::begin() { if (!is_emulated) { einkDisplay.begin(); } else { Serial.printf("[%lu] [ ] Emulated display initialized\n", millis()); // no-op } } void HalDisplay::clearScreen(uint8_t color) const { if (!is_emulated) { einkDisplay.clearScreen(color); } else { Serial.printf("[%lu] [ ] Emulated clear screen with color 0x%02X\n", millis(), color); memset(emuFramebuffer0, color, BUFFER_SIZE); } } void HalDisplay::drawImage(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h, bool fromProgmem) const { if (!is_emulated) { einkDisplay.drawImage(imageData, x, y, w, h, fromProgmem); } else { Serial.printf("[%lu] [ ] Emulated draw image at (%u, %u) with size %ux%u\n", millis(), x, y, w, h); // Calculate bytes per line for the image const uint16_t imageWidthBytes = w / 8; // Copy image data to frame buffer for (uint16_t row = 0; row < h; row++) { const uint16_t destY = y + row; if (destY >= DISPLAY_HEIGHT) break; const uint16_t destOffset = destY * DISPLAY_WIDTH_BYTES + (x / 8); const uint16_t srcOffset = row * imageWidthBytes; for (uint16_t col = 0; col < imageWidthBytes; col++) { if ((x / 8 + col) >= DISPLAY_WIDTH_BYTES) break; if (fromProgmem) { emuFramebuffer0[destOffset + col] = pgm_read_byte(&imageData[srcOffset + col]); } else { emuFramebuffer0[destOffset + col] = imageData[srcOffset + col]; } } } } } void HalDisplay::displayBuffer(RefreshMode mode) { if (!is_emulated) { einkDisplay.displayBuffer(mode); } else { Serial.printf("[%lu] [ ] Emulated display buffer with mode %d\n", millis(), static_cast(mode)); std::string b64 = base64_encode(reinterpret_cast(emuFramebuffer0), BUFFER_SIZE); Serial.printf("$$DATA:DISPLAY:{\"mode\":%d,\"buffer\":\"", static_cast(mode)); Serial.print(b64.c_str()); Serial.print("\"}$$\n"); } } void HalDisplay::refreshDisplay(RefreshMode mode, bool turnOffScreen) { if (!is_emulated) { einkDisplay.refreshDisplay(mode, turnOffScreen); } else { Serial.printf("[%lu] [ ] Emulated refresh display with mode %d, turnOffScreen %d\n", millis(), static_cast(mode), turnOffScreen); // emulated delay if (mode == RefreshMode::FAST_REFRESH) { delay(50); } else if (mode == RefreshMode::HALF_REFRESH) { delay(800); } else if (mode == RefreshMode::FULL_REFRESH) { delay(1500); } } } void HalDisplay::deepSleep() { if (!is_emulated) { einkDisplay.deepSleep(); } else { Serial.printf("[%lu] [ ] Emulated deep sleep\n", millis()); // no-op } } uint8_t* HalDisplay::getFrameBuffer() const { if (!is_emulated) { return einkDisplay.getFrameBuffer(); } else { return emuFramebuffer0; } } void HalDisplay::copyGrayscaleBuffers(const uint8_t* lsbBuffer, const uint8_t* msbBuffer) { if (!is_emulated) { einkDisplay.copyGrayscaleBuffers(lsbBuffer, msbBuffer); } else { Serial.printf("[%lu] [ ] Emulated copy grayscale buffers\n", millis()); // TODO: not sure what this does } } void HalDisplay::copyGrayscaleLsbBuffers(const uint8_t* lsbBuffer) { if (!is_emulated) { einkDisplay.copyGrayscaleLsbBuffers(lsbBuffer); } else { Serial.printf("[%lu] [ ] Emulated copy grayscale LSB buffers\n", millis()); // TODO: not sure what this does } } void HalDisplay::copyGrayscaleMsbBuffers(const uint8_t* msbBuffer) { if (!is_emulated) { einkDisplay.copyGrayscaleMsbBuffers(msbBuffer); } else { Serial.printf("[%lu] [ ] Emulated copy grayscale MSB buffers\n", millis()); // TODO: not sure what this does } } void HalDisplay::cleanupGrayscaleBuffers(const uint8_t* bwBuffer) { if (!is_emulated) { einkDisplay.cleanupGrayscaleBuffers(bwBuffer); } else { Serial.printf("[%lu] [ ] Emulated cleanup grayscale buffers\n", millis()); // TODO: not sure what this does } } void HalDisplay::displayGrayBuffer() { if (!is_emulated) { einkDisplay.displayGrayBuffer(); } else { Serial.printf("[%lu] [ ] Emulated display gray buffer\n", millis()); // TODO: not sure what this does } } // // Base64 utilities // static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c == '/')); } std::string base64_encode(char* buf, unsigned int bufLen) { std::string ret; ret.reserve(bufLen * 4 / 3 + 4); // reserve enough space int i = 0; int j = 0; char char_array_3[3]; char char_array_4[4]; while (bufLen--) { char_array_3[i++] = *(buf++); if (i == 3) { char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; for(i = 0; (i < 4) ; i++) ret += base64_chars[char_array_4[i]]; i = 0; } } if (i) { for(j = i; j < 3; j++) { char_array_3[j] = '\0'; } char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]]; while((i++ < 3)) ret += '='; } return ret; }