diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/ESP32_Pico_GxEPD2_BLE/ESP32_Pico_GxEPD2_BLE.ino b/E-Paper_Projects/01_Emoji2MiniE-Paper/ESP32_Pico_GxEPD2_BLE/ESP32_Pico_GxEPD2_BLE.ino new file mode 100644 index 0000000..f325990 --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/ESP32_Pico_GxEPD2_BLE/ESP32_Pico_GxEPD2_BLE.ino @@ -0,0 +1,368 @@ +///////////////////////////////////////////////////////////////// +/* + Mini E-Paper | Ep1. Uploading Emoji to E-Paper from Flutter App(iOS & Android) + Video Tutorial: https://youtu.be/-RhEiKldfDc + Created by Eric N. (ThatProject) +*/ +///////////////////////////////////////////////////////////////// + +#include +#include "GxEPD2_display_selection_new_style.h" +#include +#include +#include +#include +#include +#include "SPIFFS.h" +#define FileClass fs::File + +BLEServer *pServer = NULL; +BLECharacteristic *pTxCharacteristic; +#define SERVICE_UUID "713d0001-503e-4c75-ba94-3148f18d941e" +#define CHARACTERISTIC_UUID_RX "713d0002-503e-4c75-ba94-3148f18d941e" + +const char temp_filename[] = "/new_image.bmp"; +const char BLE_Device_Name[] = "MINI E-PAPER"; +class BLEServerCB : public BLEServerCallbacks { + void onConnect(BLEServer *pServer){}; + + void onDisconnect(BLEServer *pServer) { + pServer->startAdvertising(); + } +}; + + +class BLERxCallback : public BLECharacteristicCallbacks { + + uint16_t read16(fs::File &f) { + // BMP data is stored little-endian, same as Arduino. + uint16_t result; + ((uint8_t *)&result)[0] = f.read(); // LSB + ((uint8_t *)&result)[1] = f.read(); // MSB + return result; + } + + uint32_t read32(fs::File &f) { + // BMP data is stored little-endian, same as Arduino. + uint32_t result; + ((uint8_t *)&result)[0] = f.read(); // LSB + ((uint8_t *)&result)[1] = f.read(); + ((uint8_t *)&result)[2] = f.read(); + ((uint8_t *)&result)[3] = f.read(); // MSB + return result; + } + + static const uint16_t input_buffer_pixels = 800; // may affect performance + static const uint16_t max_row_width = 80; // for up to 6" display 1448x1072 + static const uint16_t max_palette_pixels = 256; // for depth <= 8 + + uint8_t input_buffer[3 * input_buffer_pixels]; // up to depth 24 + uint8_t output_row_mono_buffer[max_row_width / 8]; // buffer for at least one row of b/w bits + uint8_t output_row_color_buffer[max_row_width / 8]; // buffer for at least one row of color bits + uint8_t mono_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 b/w + uint8_t color_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 c/w + uint16_t rgb_palette_buffer[max_palette_pixels]; // palette buffer for depth <= 8 for buffered graphics, needed for 7-color display + + bool isEnd = true; + File file; + + void onWrite(BLECharacteristic *pCharacteristic) { + std::string rxValue = pCharacteristic->getValue(); + + Serial.printf("RX length: %d\n", rxValue.length()); + if (rxValue.length() > 0) { + file.write((const byte *)pCharacteristic->getData(), rxValue.length()); + } else { + Serial.println("***GET START/END TRIGGER****"); + isEnd = !isEnd; + + if (!isEnd) { + file = SPIFFS.open(temp_filename, FILE_WRITE); + if (!file) { + Serial.println("File is not available!"); + }else{ + Serial.println("File is ready to write!"); + } + + } else { + file.close(); + Serial.println("File Closed!"); + drawBitmapFromSpiffs_Buffered(temp_filename, 0, 0, false, false, false); + } + } + } + + void drawBitmapFromSpiffs_Buffered(const char *filename, int16_t x, int16_t y, bool with_color, bool partial_update, bool overwrite) { + fs::File file; + bool valid = false; // valid format to be handled + bool flip = true; // bitmap is stored bottom-to-top + bool has_multicolors = display.epd2.panel == GxEPD2::ACeP565; + uint32_t startTime = millis(); + if ((x >= display.width()) || (y >= display.height())) return; + Serial.println(); + Serial.print("Loading image '"); + Serial.print(filename); + Serial.println('\''); + file = SPIFFS.open(filename, "r"); + if (!file) { + Serial.print("File not found"); + return; + } + // Parse BMP header + if (read16(file) == 0x4D42) // BMP signature + { + uint32_t fileSize = read32(file); + uint32_t creatorBytes = read32(file); + uint32_t imageOffset = read32(file); // Start of image data + uint32_t headerSize = read32(file); + uint32_t width = read32(file); + uint32_t height = read32(file); + uint16_t planes = read16(file); + uint16_t depth = read16(file); // bits per pixel + uint32_t format = read32(file); + if ((planes == 1) && ((format == 0) || (format == 3))) // uncompressed is handled, 565 also + { + Serial.print("File size: "); + Serial.println(fileSize); + Serial.print("Image Offset: "); + Serial.println(imageOffset); + Serial.print("Header size: "); + Serial.println(headerSize); + Serial.print("Bit Depth: "); + Serial.println(depth); + Serial.print("Image size: "); + Serial.print(width); + Serial.print('x'); + Serial.println(height); + // BMP rows are padded (if needed) to 4-byte boundary + uint32_t rowSize = (width * depth / 8 + 3) & ~3; + if (depth < 8) rowSize = ((width * depth + 8 - depth) / 8 + 3) & ~3; + if (height < 0) { + height = -height; + flip = false; + } + uint16_t w = width; + uint16_t h = height; + if ((x + w - 1) >= display.width()) w = display.width() - x; + if ((y + h - 1) >= display.height()) h = display.height() - y; + //if (w <= max_row_width) // handle with direct drawing + { + valid = true; + uint8_t bitmask = 0xFF; + uint8_t bitshift = 8 - depth; + uint16_t red, green, blue; + bool whitish, colored; + if (depth == 1) with_color = false; + if (depth <= 8) { + if (depth < 8) bitmask >>= depth; + //file.seek(54); //palette is always @ 54 + file.seek(imageOffset - (4 << depth)); // 54 for regular, diff for colorsimportant + for (uint16_t pn = 0; pn < (1 << depth); pn++) { + blue = file.read(); + green = file.read(); + red = file.read(); + file.read(); + whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish + colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish? + if (0 == pn % 8) mono_palette_buffer[pn / 8] = 0; + mono_palette_buffer[pn / 8] |= whitish << pn % 8; + if (0 == pn % 8) color_palette_buffer[pn / 8] = 0; + color_palette_buffer[pn / 8] |= colored << pn % 8; + rgb_palette_buffer[pn] = ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | ((blue & 0xF8) >> 3); + } + } + if (partial_update) display.setPartialWindow(x, y, w, h); + else + display.setFullWindow(); + display.firstPage(); + do { + if (!overwrite) display.fillScreen(GxEPD_WHITE); + uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset; + for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line + { + uint32_t in_remain = rowSize; + uint32_t in_idx = 0; + uint32_t in_bytes = 0; + uint8_t in_byte = 0; // for depth <= 8 + uint8_t in_bits = 0; // for depth <= 8 + uint16_t color = GxEPD_WHITE; + file.seek(rowPosition); + for (uint16_t col = 0; col < w; col++) // for each pixel + { + // Time to read more pixel data? + if (in_idx >= in_bytes) // ok, exact match for 24bit also (size IS multiple of 3) + { + in_bytes = file.read(input_buffer, in_remain > sizeof(input_buffer) ? sizeof(input_buffer) : in_remain); + in_remain -= in_bytes; + in_idx = 0; + } + switch (depth) { + case 24: + blue = input_buffer[in_idx++]; + green = input_buffer[in_idx++]; + red = input_buffer[in_idx++]; + whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish + colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish? + color = ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | ((blue & 0xF8) >> 3); + break; + case 16: + { + uint8_t lsb = input_buffer[in_idx++]; + uint8_t msb = input_buffer[in_idx++]; + if (format == 0) // 555 + { + blue = (lsb & 0x1F) << 3; + green = ((msb & 0x03) << 6) | ((lsb & 0xE0) >> 2); + red = (msb & 0x7C) << 1; + color = ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | ((blue & 0xF8) >> 3); + } else // 565 + { + blue = (lsb & 0x1F) << 3; + green = ((msb & 0x07) << 5) | ((lsb & 0xE0) >> 3); + red = (msb & 0xF8); + color = (msb << 8) | lsb; + } + whitish = with_color ? ((red > 0x80) && (green > 0x80) && (blue > 0x80)) : ((red + green + blue) > 3 * 0x80); // whitish + colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish? + } + break; + case 1: + case 4: + case 8: + { + if (0 == in_bits) { + in_byte = input_buffer[in_idx++]; + in_bits = 8; + } + uint16_t pn = (in_byte >> bitshift) & bitmask; + whitish = mono_palette_buffer[pn / 8] & (0x1 << pn % 8); + colored = color_palette_buffer[pn / 8] & (0x1 << pn % 8); + in_byte <<= depth; + in_bits -= depth; + color = rgb_palette_buffer[pn]; + } + break; + } + if (with_color && has_multicolors) { + // keep color + } else if (whitish) { + color = GxEPD_WHITE; + } else if (colored && with_color) { + color = GxEPD_COLORED; + } else { + color = GxEPD_BLACK; + } + uint16_t yrow = y + (flip ? h - row - 1 : row); + display.drawPixel(x + col, yrow, color); + } // end pixel + } // end line + } while (display.nextPage()); + Serial.print("loaded in "); + Serial.print(millis() - startTime); + Serial.println(" ms"); + } + } + } + file.close(); + if (!valid) { + Serial.println("bitmap format not handled."); + } + } +}; + +void setup() { + Serial.begin(115200); + Serial.println(); + initDisplay(); + initSPIFFS(); + initBLE(); +} + +void loop(void) {} + +void initDisplay() { + SPI.begin(EPD_SCLK, EPD_MISO, EPD_MOSI); + display.init(115200); +} + +void initSPIFFS() { + + if (!SPIFFS.begin()) { + Serial.println("SPIFFS initialisation failed!"); + while (1) yield(); + } + + Serial.println("SPIFFS started"); + listSPIFFS(); +} + +void listSPIFFS(void) { + Serial.println(F("\r\nListing SPIFFS files:")); + static const char line[] PROGMEM = "================================================="; + + Serial.println(FPSTR(line)); + Serial.println(F(" File name Size")); + Serial.println(FPSTR(line)); + + fs::File root = SPIFFS.open("/"); + if (!root) { + Serial.println(F("Failed to open directory")); + return; + } + if (!root.isDirectory()) { + Serial.println(F("Not a directory")); + return; + } + + fs::File file = root.openNextFile(); + while (file) { + + if (file.isDirectory()) { + Serial.print("DIR : "); + String fileName = file.name(); + Serial.print(fileName); + } else { + String fileName = file.name(); + Serial.print(" " + fileName); + // File path can be 31 characters maximum in SPIFFS + int spaces = 33 - fileName.length(); // Tabulate nicely + if (spaces < 1) spaces = 1; + while (spaces--) Serial.print(" "); + String fileSize = (String) file.size(); + spaces = 10 - fileSize.length(); // Tabulate nicely + if (spaces < 1) spaces = 1; + while (spaces--) Serial.print(" "); + Serial.println(fileSize + " bytes"); + } + + file = root.openNextFile(); + } + + Serial.println(FPSTR(line)); + Serial.println(); + delay(1000); +} + +void initBLE() { + Serial.println("**** INIT BLE ****"); + // Create the BLE Device + + BLEDevice::init(BLE_Device_Name); + // Create the BLE Server + pServer = BLEDevice::createServer(); + pServer->setCallbacks(new BLEServerCB()); + + // Create the BLE Service + BLEService *pService = pServer->createService(SERVICE_UUID); + BLECharacteristic *pRxCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID_RX, + BLECharacteristic::PROPERTY_WRITE); + pRxCharacteristic->setCallbacks(new BLERxCallback()); + + // Start the service + pService->start(); + + // Start advertising + pServer->getAdvertising()->start(); + Serial.println("Start advertising"); +} diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/ESP32_Pico_GxEPD2_BLE/GxEPD2_display_selection_new_style.h b/E-Paper_Projects/01_Emoji2MiniE-Paper/ESP32_Pico_GxEPD2_BLE/GxEPD2_display_selection_new_style.h new file mode 100644 index 0000000..9a7957e --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/ESP32_Pico_GxEPD2_BLE/GxEPD2_display_selection_new_style.h @@ -0,0 +1,222 @@ +// Display Library example for SPI e-paper panels from Dalian Good Display and boards from Waveshare. +// Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines! +// +// Display Library based on Demo Example from Good Display: http://www.e-paper-display.com/download_list/downloadcategoryid=34&isMode=false.html +// +// Author: Jean-Marc Zingg +// +// Version: see library.properties +// +// Library: https://github.com/ZinggJM/GxEPD2 + +// Supporting Arduino Forum Topics: +// Waveshare e-paper displays with SPI: http://forum.arduino.cc/index.php?topic=487007.0 +// Good Display ePaper for Arduino: https://forum.arduino.cc/index.php?topic=436411.0 + +// select the display class (only one), matching the kind of display panel + +#define EPD_MOSI (21) +#define EPD_MISO (-1) +#define EPD_SCLK (22) +#define EPD_CS (5) + +#define EPD_BUSY (34) +#define EPD_RSET (4) +#define EPD_DC (19) + +#define GxEPD2_DISPLAY_CLASS GxEPD2_BW +//#define GxEPD2_DISPLAY_CLASS GxEPD2_3C +//#define GxEPD2_DISPLAY_CLASS GxEPD2_7C + +// select the display driver class (only one) for your panel +#define GxEPD2_DRIVER_CLASS GxEPD2_102 // GxEPD2_102 80x128, UC8175 +//#define GxEPD2_DRIVER_CLASS GxEPD2_154 // GDEP015OC1 200x200, IL3829, no longer available +//#define GxEPD2_DRIVER_CLASS GxEPD2_154_D67 // GDEH0154D67 200x200, SSD1681 +//#define GxEPD2_DRIVER_CLASS GxEPD2_154_T8 // GDEW0154T8 152x152, UC8151 (IL0373) +//#define GxEPD2_DRIVER_CLASS GxEPD2_154_M09 // GDEW0154M09 200x200, JD79653A +//#define GxEPD2_DRIVER_CLASS GxEPD2_154_M10 // GDEW0154M10 152x152, UC8151D +//#define GxEPD2_DRIVER_CLASS GxEPD2_213 // GDE0213B1 128x250, IL3895, phased out +//#define GxEPD2_DRIVER_CLASS GxEPD2_213_B72 // GDEH0213B72 128x250, SSD1675A (IL3897) +//#define GxEPD2_DRIVER_CLASS GxEPD2_213_B73 // GDEH0213B73 128x250, SSD1675B +//#define GxEPD2_DRIVER_CLASS GxEPD2_213_B74 // GDEM0213B74 128x250, SSD1680 +//#define GxEPD2_DRIVER_CLASS GxEPD2_213_flex // GDEW0213I5F 104x212, UC8151 (IL0373) +//#define GxEPD2_DRIVER_CLASS GxEPD2_213_M21 // GDEW0213M21 104x212, UC8151 (IL0373) +//#define GxEPD2_DRIVER_CLASS GxEPD2_213_T5D // GDEW0213T5D 104x212, UC8151D +//#define GxEPD2_DRIVER_CLASS GxEPD2_290 // GDEH029A1 128x296, SSD1608 (IL3820) +//#define GxEPD2_DRIVER_CLASS GxEPD2_290_T5 // GDEW029T5 128x296, UC8151 (IL0373) +//#define GxEPD2_DRIVER_CLASS GxEPD2_290_T5D // GDEW029T5D 128x296, UC8151D +//#define GxEPD2_DRIVER_CLASS GxEPD2_290_T94 // GDEM029T94 128x296, SSD1680 +//#define GxEPD2_DRIVER_CLASS GxEPD2_290_T94_V2 // GDEM029T94 128x296, SSD1680, Waveshare 2.9" V2 variant +//#define GxEPD2_DRIVER_CLASS GxEPD2_290_M06 // GDEW029M06 128x296, UC8151 (IL0373) +//#define GxEPD2_DRIVER_CLASS GxEPD2_260 // GDEW026T0 152x296, UC8151 (IL0373) +//#define GxEPD2_DRIVER_CLASS GxEPD2_260_M01 // GDEW026M01 152x296, UC8151 (IL0373) +//#define GxEPD2_DRIVER_CLASS GxEPD2_270 // GDEW027W3 176x264, EK79652 (IL91874) +//#define GxEPD2_DRIVER_CLASS GxEPD2_371 // GDEW0371W7 240x416, UC8171 (IL0324) +//#define GxEPD2_DRIVER_CLASS GxEPD2_420 // GDEW042T2 400x300, UC8176 (IL0398) +//#define GxEPD2_DRIVER_CLASS GxEPD2_420_M01 // GDEW042M01 400x300, UC8176 (IL0398) +//#define GxEPD2_DRIVER_CLASS GxEPD2_583 // GDEW0583T7 600x448, UC8179 (IL0371) +//#define GxEPD2_DRIVER_CLASS GxEPD2_583_T8 // GDEW0583T8 648x480, GD7965 +//#define GxEPD2_DRIVER_CLASS GxEPD2_750 // GDEW075T8 640x384, UC8179 (IL0371) +//#define GxEPD2_DRIVER_CLASS GxEPD2_750_T7 // GDEW075T7 800x480, GD7965 +//#define GxEPD2_DRIVER_CLASS GxEPD2_1160_T91 // GDEH116T91 960x640, SSD1677 +// 3-color e-papers +//#define GxEPD2_DRIVER_CLASS GxEPD2_154c // GDEW0154Z04 200x200, IL0376F, no longer available +//#define GxEPD2_DRIVER_CLASS GxEPD2_154_Z90c // GDEH0154Z90 200x200, SSD1682 +//#define GxEPD2_DRIVER_CLASS GxEPD2_213c // GDEW0213Z16 104x212, UC8151 (IL0373) +//#define GxEPD2_DRIVER_CLASS GxEPD2_213_Z19c // GDEW0213Z19 104x212, UC8151D +//#define GxEPD2_DRIVER_CLASS GxEPD2_290c // GDEW029Z10 128x296, UC8151 (IL0373) +//#define GxEPD2_DRIVER_CLASS GxEPD2_290_Z13c // GDEH029Z13 128x296, UC8151D +//#define GxEPD2_DRIVER_CLASS GxEPD2_290_C90c // GDEM029C90 128x296, SSD1680 +//#define GxEPD2_DRIVER_CLASS GxEPD2_270c // GDEW027C44 176x264, IL91874 +//#define GxEPD2_DRIVER_CLASS GxEPD2_420c // GDEW042Z15 400x300, UC8176 (IL0398) +//#define GxEPD2_DRIVER_CLASS GxEPD2_583c // GDEW0583Z21 600x448, UC8179 (IL0371) +//#define GxEPD2_DRIVER_CLASS GxEPD2_750c // GDEW075Z09 640x384, UC8179 (IL0371) +//#define GxEPD2_DRIVER_CLASS GxEPD2_750c_Z08 // GDEW075Z08 800x480, GD7965 +//#define GxEPD2_DRIVER_CLASS GxEPD2_750c_Z90 // GDEH075Z90 880x528, SSD1677 +//#define GxEPD2_DRIVER_CLASS GxEPD2_1248 // GDEW1248T3 1303x984, UC8179 +// 7-color e-paper +//#define GxEPD2_DRIVER_CLASS GxEPD2_565c // Waveshare 5.65" 7-color (3C graphics) +// grey levels parallel IF e-papers on Waveshare e-Paper IT8951 Driver HAT +//#define GxEPD2_DRIVER_CLASS GxEPD2_it60 // ED060SCT 800x600 +//#define GxEPD2_DRIVER_CLASS GxEPD2_it60_1448x1072 // ED060KC1 1448x1072 +//#define GxEPD2_DRIVER_CLASS GxEPD2_it78_1872x1404 // ED078KC2 1872x1404 + +// SS is usually used for CS. define here for easy change +#ifndef EPD_CS +#define EPD_CS SS +#endif + +#if defined(GxEPD2_DISPLAY_CLASS) && defined(GxEPD2_DRIVER_CLASS) + +// somehow there should be an easier way to do this +#define GxEPD2_BW_IS_GxEPD2_BW true +#define GxEPD2_3C_IS_GxEPD2_3C true +#define GxEPD2_7C_IS_GxEPD2_7C true +#define GxEPD2_1248_IS_GxEPD2_1248 true +#define IS_GxEPD(c, x) (c##x) +#define IS_GxEPD2_BW(x) IS_GxEPD(GxEPD2_BW_IS_, x) +#define IS_GxEPD2_3C(x) IS_GxEPD(GxEPD2_3C_IS_, x) +#define IS_GxEPD2_7C(x) IS_GxEPD(GxEPD2_7C_IS_, x) +#define IS_GxEPD2_1248(x) IS_GxEPD(GxEPD2_1248_IS_, x) + +#include "GxEPD2_selection_check.h" + +#if defined (ESP8266) +#define MAX_DISPLAY_BUFFER_SIZE (81920ul-34000ul-5000ul) // ~34000 base use, change 5000 to your application use +#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8)) +#elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8)) +#elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) +#endif +// adapt the constructor parameters to your wiring +GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=D8*/ EPD_CS, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4)); +// mapping of Waveshare e-Paper ESP8266 Driver Board, new version +//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=15*/ EPD_CS, /*DC=4*/ 4, /*RST=2*/ 2, /*BUSY=5*/ 5)); +// mapping of Waveshare e-Paper ESP8266 Driver Board, old version +//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=15*/ EPD_CS, /*DC=4*/ 4, /*RST=5*/ 5, /*BUSY=16*/ 16)); +#endif + +#if defined(ESP32) +#define MAX_DISPLAY_BUFFER_SIZE 65536ul // e.g. +#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8)) +#elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8)) +#elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) +#endif +// adapt the constructor parameters to your wiring +#if !IS_GxEPD2_1248(GxEPD2_DRIVER_CLASS) +#if defined(ARDUINO_LOLIN_D32_PRO) +GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 0, /*RST=*/ 2, /*BUSY=*/ 15)); +#elif defined(ARDUINO_ESP32_DEV) // e.g. TTGO T8 ESP32-WROVER +GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 2, /*RST=*/ 0, /*BUSY=*/ 4)); +#else +//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); +GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ EPD_DC, /*RST=*/ EPD_RSET, /*BUSY=*/ EPD_BUSY)); +#endif +#else // GxEPD2_1248 +// Waveshare 12.48 b/w SPI display board and frame or Good Display 12.48 b/w panel GDEW1248T3 +// general constructor for use with all parameters, e.g. for Waveshare ESP32 driver board mounted on connection board +GxEPD2_BW < GxEPD2_1248, GxEPD2_1248::HEIGHT / 4 > display(GxEPD2_1248(/*sck=*/ 13, /*miso=*/ 12, /*mosi=*/ 14, + /*cs_m1=*/ 23, /*cs_s1=*/ 22, /*cs_m2=*/ 16, /*cs_s2=*/ 19, + /*dc1=*/ 25, /*dc2=*/ 17, /*rst1=*/ 33, /*rst2=*/ 5, + /*busy_m1=*/ 32, /*busy_s1=*/ 26, /*busy_m2=*/ 18, /*busy_s2=*/ 4)); +#endif +#endif + +// can't use package "STMF1 Boards (STM32Duino.com)" (Roger Clark) anymore with Adafruit_GFX, use "STM32 Boards (selected from submenu)" (STMicroelectronics) +#if defined(ARDUINO_ARCH_STM32) +#define MAX_DISPLAY_BUFFER_SIZE 15000ul // ~15k is a good compromise +#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8)) +#elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8)) +#elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) +#endif +// adapt the constructor parameters to your wiring +GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=4*/ EPD_CS, /*DC=*/ 3, /*RST=*/ 2, /*BUSY=*/ 1)); +#endif + +#if defined(__AVR) +#if defined (ARDUINO_AVR_MEGA2560) // Note: SS is on 53 on MEGA +#define MAX_DISPLAY_BUFFER_SIZE 5000 // e.g. full height for 200x200 +#else // Note: SS is on 10 on UNO, NANO +#define MAX_DISPLAY_BUFFER_SIZE 800 // +#endif +#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8)) +#elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8)) +#elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) +#endif +// adapt the constructor parameters to your wiring +GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); +#endif + +#if defined(ARDUINO_ARCH_SAM) +#define MAX_DISPLAY_BUFFER_SIZE 32768ul // e.g., up to 96k +#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8)) +#elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8)) +#elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) +#endif +// adapt the constructor parameters to your wiring +GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=77*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); +#endif + +#if defined(ARDUINO_ARCH_SAMD) +#define MAX_DISPLAY_BUFFER_SIZE 15000ul // ~15k is a good compromise +#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8)) +#elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8)) +#elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) +#endif +// adapt the constructor parameters to your wiring +GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 4, /*DC=*/ 7, /*RST=*/ 6, /*BUSY=*/ 5)); +//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 4, /*DC=*/ 3, /*RST=*/ 2, /*BUSY=*/ 1)); // my Seed XIOA0 +//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 3, /*DC=*/ 2, /*RST=*/ 1, /*BUSY=*/ 0)); // my other Seed XIOA0 +#endif + +#if defined(ARDUINO_ARCH_RP2040) +#define MAX_DISPLAY_BUFFER_SIZE 131072ul // e.g. half of available ram +#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8)) +#elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8)) +#elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) +#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2)) +#endif +// adapt the constructor parameters to your wiring +GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=4*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); +#endif + +#endif diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/ESP32_Pico_GxEPD2_BLE/GxEPD2_selection_check.h b/E-Paper_Projects/01_Emoji2MiniE-Paper/ESP32_Pico_GxEPD2_BLE/GxEPD2_selection_check.h new file mode 100644 index 0000000..3a499bc --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/ESP32_Pico_GxEPD2_BLE/GxEPD2_selection_check.h @@ -0,0 +1,75 @@ +// Display Library example for SPI e-paper panels from Dalian Good Display and boards from Waveshare. +// Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines! +// +// Display Library based on Demo Example from Good Display: http://www.e-paper-display.com/download_list/downloadcategoryid=34&isMode=false.html +// +// Author: Jean-Marc Zingg +// +// Version: see library.properties +// +// Library: https://github.com/ZinggJM/GxEPD2 + +// Supporting Arduino Forum Topics: +// Waveshare e-paper displays with SPI: http://forum.arduino.cc/index.php?topic=487007.0 +// Good Display ePaper for Arduino: https://forum.arduino.cc/index.php?topic=436411.0 + +#define GxEPD2_102_IS_BW true +#define GxEPD2_154_IS_BW true +#define GxEPD2_154_D67_IS_BW true +#define GxEPD2_154_T8_IS_BW true +#define GxEPD2_154_M09_IS_BW true +#define GxEPD2_154_M10_IS_BW true +#define GxEPD2_213_IS_BW true +#define GxEPD2_213_B72_IS_BW true +#define GxEPD2_213_B73_IS_BW true +#define GxEPD2_213_B74_IS_BW true +#define GxEPD2_213_flex_IS_BW true +#define GxEPD2_213_M21_IS_BW true +#define GxEPD2_213_T5D_IS_BW true +#define GxEPD2_290_IS_BW true +#define GxEPD2_290_T5_IS_BW true +#define GxEPD2_290_T5D_IS_BW true +#define GxEPD2_290_T94_IS_BW true +#define GxEPD2_290_T94_V2_IS_BW true +#define GxEPD2_290_M06_IS_BW true +#define GxEPD2_260_IS_BW true +#define GxEPD2_260_M01_IS_BW true +#define GxEPD2_270_IS_BW true +#define GxEPD2_371_IS_BW true +#define GxEPD2_420_IS_BW true +#define GxEPD2_420_M01_IS_BW true +#define GxEPD2_583_IS_BW true +#define GxEPD2_583_T8_IS_BW true +#define GxEPD2_750_IS_BW true +#define GxEPD2_750_T7_IS_BW true +#define GxEPD2_1160_T91_IS_BW true +// 3-color e-papers +#define GxEPD2_154c_IS_3C true +#define GxEPD2_154_Z90c_IS_3C true +#define GxEPD2_213c_IS_3C true +#define GxEPD2_213_Z19c_IS_3C true +#define GxEPD2_290c_IS_3C true +#define GxEPD2_290_Z13c_IS_3C true +#define GxEPD2_290_C90c_IS_3C true +#define GxEPD2_270c_IS_3C true +#define GxEPD2_420c_IS_3C true +#define GxEPD2_583c_IS_3C true +#define GxEPD2_750c_IS_3C true +#define GxEPD2_750c_Z08_IS_3C true +#define GxEPD2_750c_Z90_IS_3C true +#define GxEPD2_1248_IS_3C true +// 7-color e-paper +#define GxEPD2_565c_IS_7C true + +#if defined(GxEPD2_DISPLAY_CLASS) && defined(GxEPD2_DRIVER_CLASS) +#define IS_GxEPD2_DRIVER(c, x) (c##x) +#define IS_GxEPD2_DRIVER_BW(x) IS_GxEPD2_DRIVER(x, _IS_BW) +#define IS_GxEPD2_DRIVER_3C(x) IS_GxEPD2_DRIVER(x, _IS_3C) +#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_3C(GxEPD2_DRIVER_CLASS) +#error "GxEPD2_BW used with 3-color driver class" +#endif +#if IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_BW(GxEPD2_DRIVER_CLASS) +#error "GxEPD2_3C used with b/w driver class" +#endif + +#endif diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/.gitignore b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/.gitignore new file mode 100644 index 0000000..0fa6b67 --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/.metadata b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/.metadata new file mode 100644 index 0000000..be0f63d --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 4cc385b4b84ac2f816d939a49ea1f328c4e0b48e + channel: stable + +project_type: app diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/README.md b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/README.md new file mode 100644 index 0000000..d2a5d02 --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/README.md @@ -0,0 +1,5 @@ +# Mini E-Paper | Ep1. Uploading Emoji to E-Paper from Flutter App(iOS & Android) +https://youtu.be/-RhEiKldfDc + +# My Channel +www.that-project.com \ No newline at end of file diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/analysis_options.yaml b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/assets/images/iot_image.png b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/assets/images/iot_image.png new file mode 100644 index 0000000..d002fc4 Binary files /dev/null and b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/assets/images/iot_image.png differ diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/const/custom_colors.dart b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/const/custom_colors.dart new file mode 100644 index 0000000..aa34624 --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/const/custom_colors.dart @@ -0,0 +1,6 @@ +import 'package:flutter/material.dart'; + +// Colors +const kMainBG = Color(0xff191720); +const kTextFieldFill = Color(0xff1E1C24); +const kCardBG = Color(0xffffffff); diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/const/custom_styles.dart b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/const/custom_styles.dart new file mode 100644 index 0000000..31a8841 --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/const/custom_styles.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +const kHeadline = TextStyle( + color: Colors.white, + fontSize: 34, + fontWeight: FontWeight.bold, +); + +const kBodyText = TextStyle( + color: Colors.grey, + fontSize: 15, +); + +const kButtonText = TextStyle( + color: Colors.black87, + fontSize: 16, + fontWeight: FontWeight.bold, +); + +const kBodyText2 = + TextStyle(fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white); + +const kSubtitleText = TextStyle( + color: Colors.grey, + fontSize: 12, +); diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/const/emoji_data.dart b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/const/emoji_data.dart new file mode 100644 index 0000000..dcb4f77 --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/const/emoji_data.dart @@ -0,0 +1,848 @@ +const emojiArray = [ + "๐Ÿ˜„", + "๐Ÿ˜†", + "๐Ÿ˜Š", + "๐Ÿ˜ƒ", + "๐Ÿ˜", + "๐Ÿ˜", + "๐Ÿ˜˜", + "๐Ÿ˜š", + "๐Ÿ˜ณ", + "๐Ÿ˜Œ", + "๐Ÿ˜†", + "๐Ÿ˜", + "๐Ÿ˜‰", + "๐Ÿ˜œ", + "๐Ÿ˜", + "๐Ÿ˜€", + "๐Ÿ˜—", + "๐Ÿ˜™", + "๐Ÿ˜›", + "๐Ÿ˜ด", + "๐Ÿ˜Ÿ", + "๐Ÿ˜ฆ", + "๐Ÿ˜ง", + "๐Ÿ˜ฎ", + "๐Ÿ˜ฌ", + "๐Ÿ˜•", + "๐Ÿ˜ฏ", + "๐Ÿ˜‘", + "๐Ÿ˜’", + "๐Ÿ˜…", + "๐Ÿ˜“", + "๐Ÿ˜ฅ", + "๐Ÿ˜ฉ", + "๐Ÿ˜”", + "๐Ÿ˜ž", + "๐Ÿ˜–", + "๐Ÿ˜จ", + "๐Ÿ˜ฐ", + "๐Ÿ˜ฃ", + "๐Ÿ˜ข", + "๐Ÿ˜ญ", + "๐Ÿ˜‚", + "๐Ÿ˜ฒ", + "๐Ÿ˜ฑ", + "๐Ÿ˜ซ", + "๐Ÿ˜ ", + "๐Ÿ˜ก", + "๐Ÿ˜ค", + "๐Ÿ˜ช", + "๐Ÿ˜‹", + "๐Ÿ˜ท", + "๐Ÿ˜Ž", + "๐Ÿ˜ต", + "๐Ÿ‘ฟ", + "๐Ÿ˜ˆ", + "๐Ÿ˜", + "๐Ÿ˜ถ", + "๐Ÿ˜‡", + "๐Ÿ‘ฝ", + "๐Ÿ’›", + "๐Ÿ’™", + "๐Ÿ’œ", + "๐Ÿ’š", + "๐Ÿ’”", + "๐Ÿ’“", + "๐Ÿ’—", + "๐Ÿ’•", + "๐Ÿ’ž", + "๐Ÿ’˜", + "๐Ÿ’–", + "โœจ", + "โญ", + "๐ŸŒŸ", + "๐Ÿ’ซ", + "๐Ÿ’ฅ", + "๐Ÿ’ข", + "โ—", + "โ“", + "โ•", + "โ”", + "๐Ÿ’ค", + "๐Ÿ’จ", + "๐Ÿ’ฆ", + "๐ŸŽถ", + "๐ŸŽต", + "๐Ÿ”ฅ", + "๐Ÿ’ฉ", + "๐Ÿ‘", + "๐Ÿ‘Ž", + "๐Ÿ‘Œ", + "๐Ÿ‘Š", + "โœŠ", + "โœŒ๏ธ", + "๐Ÿ‘‹", + "โœ‹", + "๐Ÿ‘", + "โ˜๏ธ", + "๐Ÿ‘‡", + "๐Ÿ‘ˆ", + "๐Ÿ‘‰", + "๐Ÿ™Œ", + "๐Ÿ™", + "๐Ÿ‘†", + "๐Ÿ‘", + "๐Ÿ’ช", + "๐Ÿค˜", + "๐Ÿ–•", + "๐Ÿšถ", + "๐Ÿƒ", + "๐Ÿ‘ซ", + "๐Ÿ‘ช", + "๐Ÿ‘ฌ", + "๐Ÿ‘ญ", + "๐Ÿ’ƒ", + "๐Ÿ‘ฏ", + "๐Ÿ™†", + "๐Ÿ™…", + "๐Ÿ’", + "๐Ÿ™‹", + "๐Ÿ‘ฐ", + "๐Ÿ™‡", + "๐Ÿ’", + "๐Ÿ’‘", + "๐Ÿ’†", + "๐Ÿ’‡", + "๐Ÿ’…", + "๐Ÿ‘ฆ", + "๐Ÿ‘ง", + "๐Ÿ‘ฉ", + "๐Ÿ‘จ", + "๐Ÿ‘ถ", + "๐Ÿ‘ต", + "๐Ÿ‘ด", + "๐Ÿ‘ฒ", + "๐Ÿ‘ณ", + "๐Ÿ‘ท", + "๐Ÿ‘ฎ", + "๐Ÿ‘ผ", + "๐Ÿ‘ธ", + "๐Ÿ˜บ", + "๐Ÿ˜ธ", + "๐Ÿ˜ป", + "๐Ÿ˜ฝ", + "๐Ÿ˜ผ", + "๐Ÿ™€", + "๐Ÿ˜ฟ", + "๐Ÿ˜น", + "๐Ÿ˜พ", + "๐Ÿ‘น", + "๐Ÿ‘บ", + "๐Ÿ™ˆ", + "๐Ÿ™‰", + "๐Ÿ™Š", + "๐Ÿ’‚", + "๐Ÿ’€", + "๐Ÿพ", + "๐Ÿ‘„", + "๐Ÿ’‹", + "๐Ÿ’ง", + "๐Ÿ‘‚", + "๐Ÿ‘€", + "๐Ÿ‘ƒ", + "๐Ÿ‘…", + "๐Ÿ’Œ", + "๐Ÿ‘ค", + "๐Ÿ‘ฅ", + "๐Ÿ’ฌ", + "๐Ÿ’ญ", + "โ˜€๏ธ", + "โ˜๏ธ", + "โ„๏ธ", + "โ›„", + "โšก", + "๐ŸŒ€", + "๐ŸŒ", + "๐ŸŒŠ", + "๐Ÿฑ", + "๐Ÿถ", + "๐Ÿญ", + "๐Ÿน", + "๐Ÿฐ", + "๐Ÿบ", + "๐Ÿธ", + "๐Ÿฏ", + "๐Ÿจ", + "๐Ÿป", + "๐Ÿท", + "๐Ÿฝ", + "๐Ÿฎ", + "๐Ÿ—", + "๐Ÿต", + "๐Ÿ’", + "๐Ÿด", + "๐ŸŽ", + "๐Ÿซ", + "๐Ÿ‘", + "๐Ÿ˜", + "๐Ÿผ", + "๐Ÿ", + "๐Ÿฆ", + "๐Ÿค", + "๐Ÿฅ", + "๐Ÿฃ", + "๐Ÿ”", + "๐Ÿง", + "๐Ÿข", + "๐Ÿ›", + "๐Ÿ", + "๐Ÿœ", + "๐Ÿž", + "๐ŸŒ", + "๐Ÿ™", + "๐Ÿ ", + "๐ŸŸ", + "๐Ÿณ", + "๐Ÿ‹", + "๐Ÿฌ", + "๐Ÿ„", + "๐Ÿ", + "๐Ÿ€", + "๐Ÿƒ", + "๐Ÿ…", + "๐Ÿ‡", + "๐Ÿ‰", + "๐Ÿ", + "๐Ÿ“", + "๐Ÿ•", + "๐Ÿ–", + "๐Ÿ", + "๐Ÿ‚", + "๐Ÿฒ", + "๐Ÿก", + "๐ŸŠ", + "๐Ÿช", + "๐Ÿ†", + "๐Ÿˆ", + "๐Ÿฉ", + "๐Ÿพ", + "๐Ÿ’", + "๐ŸŒธ", + "๐ŸŒท", + "๐Ÿ€", + "๐ŸŒน", + "๐ŸŒป", + "๐ŸŒบ", + "๐Ÿ", + "๐Ÿƒ", + "๐Ÿ‚", + "๐ŸŒฟ", + "๐Ÿ„", + "๐ŸŒต", + "๐ŸŒด", + "๐ŸŒฒ", + "๐ŸŒณ", + "๐ŸŒฐ", + "๐ŸŒฑ", + "๐ŸŒผ", + "๐ŸŒพ", + "๐Ÿš", + "๐ŸŒ", + "๐ŸŒž", + "๐ŸŒ", + "๐ŸŒš", + "๐ŸŒ‘", + "๐ŸŒ’", + "๐ŸŒ“", + "๐ŸŒ”", + "๐ŸŒ•", + "๐ŸŒ–", + "๐ŸŒ—", + "๐ŸŒ˜", + "๐ŸŒœ", + "๐ŸŒ›", + "๐ŸŒ”", + "๐ŸŒ", + "๐ŸŒŽ", + "๐ŸŒ", + "๐ŸŒ‹", + "๐ŸŒŒ", + "โ›…", + "๐ŸŽ", + "๐Ÿ’", + "๐ŸŽŽ", + "๐ŸŽ’", + "๐ŸŽ“", + "๐ŸŽ", + "๐ŸŽ†", + "๐ŸŽ‡", + "๐ŸŽ", + "๐ŸŽ‘", + "๐ŸŽƒ", + "๐Ÿ‘ป", + "๐ŸŽ…", + "๐ŸŽ„", + "๐ŸŽ", + "๐Ÿ””", + "๐Ÿ”•", + "๐ŸŽ‹", + "๐ŸŽ‰", + "๐ŸŽŠ", + "๐ŸŽˆ", + "๐Ÿ”ฎ", + "๐Ÿ’ฟ", + "๐Ÿ“€", + "๐Ÿ’พ", + "๐Ÿ“ท", + "๐Ÿ“น", + "๐ŸŽฅ", + "๐Ÿ’ป", + "๐Ÿ“บ", + "๐Ÿ“ฑ", + "โ˜Ž๏ธ", + "โ˜Ž๏ธ", + "๐Ÿ“ž", + "๐Ÿ“Ÿ", + "๐Ÿ“ ", + "๐Ÿ’ฝ", + "๐Ÿ“ผ", + "๐Ÿ”‰", + "๐Ÿ”ˆ", + "๐Ÿ”‡", + "๐Ÿ“ข", + "๐Ÿ“ฃ", + "โŒ›", + "โณ", + "โฐ", + "โŒš", + "๐Ÿ“ป", + "๐Ÿ“ก", + "โžฟ", + "๐Ÿ”", + "๐Ÿ”Ž", + "๐Ÿ”“", + "๐Ÿ”’", + "๐Ÿ”", + "๐Ÿ”", + "๐Ÿ”‘", + "๐Ÿ’ก", + "๐Ÿ”ฆ", + "๐Ÿ”†", + "๐Ÿ”…", + "๐Ÿ”Œ", + "๐Ÿ”‹", + "๐Ÿ“ฒ", + "โœ‰๏ธ", + "๐Ÿ“ซ", + "๐Ÿ“ฎ", + "๐Ÿ›€", + "๐Ÿ›", + "๐Ÿšฟ", + "๐Ÿšฝ", + "๐Ÿ”ง", + "๐Ÿ”ฉ", + "๐Ÿ”จ", + "๐Ÿ’บ", + "๐Ÿ’ฐ", + "๐Ÿ’ด", + "๐Ÿ’ต", + "๐Ÿ’ท", + "๐Ÿ’ถ", + "๐Ÿ’ณ", + "๐Ÿ’ธ", + "๐Ÿ“ง", + "๐Ÿ“ฅ", + "๐Ÿ“ค", + "โœ‰๏ธ", + "๐Ÿ“จ", + "๐Ÿ“ฏ", + "๐Ÿ“ช", + "๐Ÿ“ฌ", + "๐Ÿ“ญ", + "๐Ÿ“ฆ", + "๐Ÿšช", + "๐Ÿšฌ", + "๐Ÿ’ฃ", + "๐Ÿ”ซ", + "๐Ÿ”ช", + "๐Ÿ’Š", + "๐Ÿ’‰", + "๐Ÿ“„", + "๐Ÿ“ƒ", + "๐Ÿ“‘", + "๐Ÿ“Š", + "๐Ÿ“ˆ", + "๐Ÿ“‰", + "๐Ÿ“œ", + "๐Ÿ“‹", + "๐Ÿ“†", + "๐Ÿ“…", + "๐Ÿ“‡", + "๐Ÿ“", + "๐Ÿ“‚", + "โœ‚๏ธ", + "๐Ÿ“Œ", + "๐Ÿ“Ž", + "โœ’๏ธ", + "โœ๏ธ", + "๐Ÿ“", + "๐Ÿ“", + "๐Ÿ“•", + "๐Ÿ“—", + "๐Ÿ“˜", + "๐Ÿ“™", + "๐Ÿ““", + "๐Ÿ“”", + "๐Ÿ“’", + "๐Ÿ“š", + "๐Ÿ”–", + "๐Ÿ“›", + "๐Ÿ”ฌ", + "๐Ÿ”ญ", + "๐Ÿ“ฐ", + "๐Ÿˆ", + "๐Ÿ€", + "โšฝ", + "โšพ", + "๐ŸŽพ", + "๐ŸŽฑ", + "๐Ÿ‰", + "๐ŸŽณ", + "โ›ณ", + "๐Ÿšต", + "๐Ÿšด", + "๐Ÿ‡", + "๐Ÿ‚", + "๐ŸŠ", + "๐Ÿ„", + "๐ŸŽฟ", + "โ™ ๏ธ", + "โ™ฅ๏ธ", + "โ™ฃ๏ธ", + "โ™ฆ๏ธ", + "๐Ÿ’Ž", + "๐Ÿ’", + "๐Ÿ†", + "๐ŸŽผ", + "๐ŸŽน", + "๐ŸŽป", + "๐Ÿ‘พ", + "๐ŸŽฎ", + "๐Ÿƒ", + "๐ŸŽด", + "๐ŸŽฒ", + "๐ŸŽฏ", + "๐Ÿ€„", + "๐ŸŽฌ", + "๐Ÿ“", + "๐Ÿ“", + "๐Ÿ“–", + "๐ŸŽจ", + "๐ŸŽค", + "๐ŸŽง", + "๐ŸŽบ", + "๐ŸŽท", + "๐ŸŽธ", + "๐Ÿ‘ž", + "๐Ÿ‘ก", + "๐Ÿ‘ ", + "๐Ÿ’„", + "๐Ÿ‘ข", + "๐Ÿ‘•", + "๐Ÿ‘•", + "๐Ÿ‘”", + "๐Ÿ‘š", + "๐Ÿ‘—", + "๐ŸŽฝ", + "๐Ÿ‘–", + "๐Ÿ‘˜", + "๐Ÿ‘™", + "๐ŸŽ€", + "๐ŸŽฉ", + "๐Ÿ‘‘", + "๐Ÿ‘’", + "๐Ÿ‘ž", + "๐ŸŒ‚", + "๐Ÿ’ผ", + "๐Ÿ‘œ", + "๐Ÿ‘", + "๐Ÿ‘›", + "๐Ÿ‘“", + "๐ŸŽฃ", + "โ˜•", + "๐Ÿต", + "๐Ÿถ", + "๐Ÿผ", + "๐Ÿบ", + "๐Ÿป", + "๐Ÿธ", + "๐Ÿน", + "๐Ÿท", + "๐Ÿด", + "๐Ÿ•", + "๐Ÿ”", + "๐ŸŸ", + "๐Ÿ—", + "๐Ÿ–", + "๐Ÿ", + "๐Ÿ›", + "๐Ÿค", + "๐Ÿฑ", + "๐Ÿฃ", + "๐Ÿฅ", + "๐Ÿ™", + "๐Ÿ˜", + "๐Ÿš", + "๐Ÿœ", + "๐Ÿฒ", + "๐Ÿข", + "๐Ÿก", + "๐Ÿฅš", + "๐Ÿž", + "๐Ÿฉ", + "๐Ÿฎ", + "๐Ÿฆ", + "๐Ÿจ", + "๐Ÿง", + "๐ŸŽ‚", + "๐Ÿฐ", + "๐Ÿช", + "๐Ÿซ", + "๐Ÿฌ", + "๐Ÿญ", + "๐Ÿฏ", + "๐ŸŽ", + "๐Ÿ", + "๐ŸŠ", + "๐Ÿ‹", + "๐Ÿ’", + "๐Ÿ‡", + "๐Ÿ‰", + "๐Ÿ“", + "๐Ÿ‘", + "๐Ÿˆ", + "๐ŸŒ", + "๐Ÿ", + "๐Ÿ", + "๐Ÿ ", + "๐Ÿ†", + "๐Ÿ…", + "๐ŸŒฝ", + "๐Ÿ ", + "๐Ÿก", + "๐Ÿซ", + "๐Ÿข", + "๐Ÿฃ", + "๐Ÿฅ", + "๐Ÿฆ", + "๐Ÿช", + "๐Ÿฉ", + "๐Ÿจ", + "๐Ÿ’’", + "โ›ช", + "๐Ÿฌ", + "๐Ÿค", + "๐ŸŒ‡", + "๐ŸŒ†", + "๐Ÿฏ", + "๐Ÿฐ", + "โ›บ", + "๐Ÿญ", + "๐Ÿ—ผ", + "๐Ÿ—พ", + "๐Ÿ—ป", + "๐ŸŒ„", + "๐ŸŒ…", + "๐ŸŒ ", + "๐Ÿ—ฝ", + "๐ŸŒ‰", + "๐ŸŽ ", + "๐ŸŒˆ", + "๐ŸŽก", + "โ›ฒ", + "๐ŸŽข", + "๐Ÿšข", + "๐Ÿšค", + "โ›ต", + "โ›ต", + "๐Ÿšฃ", + "โš“", + "๐Ÿš€", + "โœˆ๏ธ", + "๐Ÿš", + "๐Ÿš‚", + "๐ŸšŠ", + "๐Ÿšž", + "๐Ÿšฒ", + "๐Ÿšก", + "๐ŸšŸ", + "๐Ÿš ", + "๐Ÿšœ", + "๐Ÿš™", + "๐Ÿš˜", + "๐Ÿš—", + "๐Ÿš—", + "๐Ÿš•", + "๐Ÿš–", + "๐Ÿš›", + "๐ŸšŒ", + "๐Ÿš", + "๐Ÿšจ", + "๐Ÿš“", + "๐Ÿš”", + "๐Ÿš’", + "๐Ÿš‘", + "๐Ÿš", + "๐Ÿšš", + "๐Ÿš‹", + "๐Ÿš‰", + "๐Ÿš†", + "๐Ÿš…", + "๐Ÿš„", + "๐Ÿšˆ", + "๐Ÿš", + "๐Ÿšƒ", + "๐ŸšŽ", + "๐ŸŽซ", + "โ›ฝ", + "๐Ÿšฆ", + "๐Ÿšฅ", + "โš ๏ธ", + "๐Ÿšง", + "๐Ÿ”ฐ", + "๐Ÿง", + "๐ŸŽฐ", + "๐Ÿš", + "๐Ÿ’ˆ", + "โ™จ๏ธ", + "๐Ÿ", + "๐ŸŽŒ", + "๐Ÿฎ", + "๐Ÿ—ฟ", + "๐ŸŽช", + "๐ŸŽญ", + "๐Ÿ“", + "๐Ÿšฉ", + "๐Ÿ‡ฏ๐Ÿ‡ต", + "๐Ÿ‡ฐ๐Ÿ‡ท", + "๐Ÿ‡จ๐Ÿ‡ณ", + "๐Ÿ‡บ๐Ÿ‡ธ", + "๐Ÿ‡ซ๐Ÿ‡ท", + "๐Ÿ‡ช๐Ÿ‡ธ", + "๐Ÿ‡ฎ๐Ÿ‡น", + "๐Ÿ‡ท๐Ÿ‡บ", + "๐Ÿ‡ฌ๐Ÿ‡ง", + "๐Ÿ‡ฉ๐Ÿ‡ช", + "1๏ธโƒฃ", + "2๏ธโƒฃ", + "3๏ธโƒฃ", + "4๏ธโƒฃ", + "5๏ธโƒฃ", + "6๏ธโƒฃ", + "7๏ธโƒฃ", + "8๏ธโƒฃ", + "9๏ธโƒฃ", + "๐Ÿ”Ÿ", + "๐Ÿ”ข", + "0๏ธโƒฃ", + "#๏ธโƒฃ", + "๐Ÿ”ฃ", + "โ—€๏ธ", + "โฌ‡๏ธ", + "โ–ถ๏ธ", + "โฌ…๏ธ", + "๐Ÿ” ", + "๐Ÿ”ก", + "๐Ÿ”ค", + "โ†™๏ธ", + "โ†˜๏ธ", + "โžก๏ธ", + "โฌ†๏ธ", + "โ†–๏ธ", + "โ†—๏ธ", + "โฌ", + "โซ", + "๐Ÿ”ฝ", + "โคต๏ธ", + "โคด๏ธ", + "โ†ฉ๏ธ", + "โ†ช๏ธ", + "โ†”๏ธ", + "โ†•๏ธ", + "๐Ÿ”ผ", + "๐Ÿ”ƒ", + "๐Ÿ”„", + "โช", + "โฉ", + "โ„น๏ธ", + "๐Ÿ†—", + "๐Ÿ”€", + "๐Ÿ”", + "๐Ÿ”‚", + "๐Ÿ†•", + "๐Ÿ”", + "๐Ÿ†™", + "๐Ÿ†’", + "๐Ÿ†“", + "๐Ÿ†–", + "๐ŸŽฆ", + "๐Ÿˆ", + "๐Ÿ“ถ", + "๐Ÿˆน", + "๐Ÿˆด", + "๐Ÿˆบ", + "๐Ÿˆฏ", + "๐Ÿˆท๏ธ", + "๐Ÿˆถ", + "๐Ÿˆต", + "๐Ÿˆš", + "๐Ÿˆธ", + "๐Ÿˆณ", + "๐Ÿˆฒ", + "๐Ÿˆ‚๏ธ", + "๐Ÿšป", + "๐Ÿšน", + "๐Ÿšบ", + "๐Ÿšผ", + "๐Ÿšญ", + "๐Ÿ…ฟ๏ธ", + "โ™ฟ", + "๐Ÿš‡", + "๐Ÿ›„", + "๐Ÿ‰‘", + "๐Ÿšพ", + "๐Ÿšฐ", + "๐Ÿšฎ", + "ใŠ™๏ธ", + "ใŠ—๏ธ", + "โ“‚๏ธ", + "๐Ÿ›‚", + "๐Ÿ›…", + "๐Ÿ›ƒ", + "๐Ÿ‰", + "๐Ÿ†‘", + "๐Ÿ†˜", + "๐Ÿ†”", + "๐Ÿšซ", + "๐Ÿ”ž", + "๐Ÿ“ต", + "๐Ÿšฏ", + "๐Ÿšฑ", + "๐Ÿšณ", + "๐Ÿšท", + "๐Ÿšธ", + "โ›”", + "โœณ๏ธ", + "โ‡๏ธ", + "โœด๏ธ", + "๐Ÿ’Ÿ", + "๐Ÿ†š", + "๐Ÿ“ณ", + "๐Ÿ“ด", + "๐Ÿ’น", + "๐Ÿ’ฑ", + "โ™ˆ", + "โ™‰", + "โ™Š", + "โ™‹", + "โ™Œ", + "โ™", + "โ™Ž", + "โ™", + "โ™", + "โ™‘", + "โ™’", + "โ™“", + "โ›Ž", + "๐Ÿ”ฏ", + "โŽ", + "๐Ÿ…ฐ๏ธ", + "๐Ÿ…ฑ๏ธ", + "๐Ÿ†Ž", + "๐Ÿ…พ๏ธ", + "๐Ÿ’ ", + "โ™ป๏ธ", + "๐Ÿ”š", + "๐Ÿ”™", + "๐Ÿ”›", + "๐Ÿ”œ", + "๐Ÿ•", + "๐Ÿ•œ", + "๐Ÿ•™", + "๐Ÿ•ฅ", + "๐Ÿ•š", + "๐Ÿ•ฆ", + "๐Ÿ•›", + "๐Ÿ•ง", + "๐Ÿ•‘", + "๐Ÿ•", + "๐Ÿ•’", + "๐Ÿ•ž", + "๐Ÿ•“", + "๐Ÿ•Ÿ", + "๐Ÿ•”", + "๐Ÿ• ", + "๐Ÿ••", + "๐Ÿ•ก", + "๐Ÿ•–", + "๐Ÿ•ข", + "๐Ÿ•—", + "๐Ÿ•ฃ", + "๐Ÿ•˜", + "๐Ÿ•ค", + "๐Ÿ’ฒ", + "ยฉ๏ธ", + "ยฎ๏ธ", + "โ„ข๏ธ", + "โŒ", + "โ—", + "โ€ผ๏ธ", + "โ‰๏ธ", + "โญ•", + "โœ–๏ธ", + "โž•", + "โž–", + "โž—", + "๐Ÿ’ฎ", + "๐Ÿ’ฏ", + "โœ”๏ธ", + "โ˜‘๏ธ", + "๐Ÿ”˜", + "๐Ÿ”—", + "โžฐ", + "ใ€ฐ๏ธ", + "ใ€ฝ๏ธ", + "๐Ÿ”ฑ", + "โ–ช๏ธ", + "โ–ซ๏ธ", + "โ—พ", + "โ—ฝ", + "โ—ผ๏ธ", + "โ—ป๏ธ", + "โฌ›", + "โฌœ", + "โœ…", + "๐Ÿ”ฒ", + "๐Ÿ”ณ", + "โšซ", + "โšช", + "๐Ÿ”ด", + "๐Ÿ”ต", + "๐Ÿ”ท", + "๐Ÿ”ถ", + "๐Ÿ”น", + "๐Ÿ”ธ", + "๐Ÿ”บ", + "๐Ÿ”ป" +]; diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/main.dart b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/main.dart new file mode 100644 index 0000000..1ae4c32 --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/main.dart @@ -0,0 +1,36 @@ +///////////////////////////////////////////////////////////////// +/* + Mini E-Paper | Ep1. Uploading Emoji to E-Paper from Flutter App(iOS & Android) + Video Tutorial: https://youtu.be/-RhEiKldfDc + Created by Eric N. (ThatProject) +*/ +///////////////////////////////////////////////////////////////// + +import 'package:epaper_emoji_ble/route/router.dart' as router; +import 'package:epaper_emoji_ble/route/routing_constants.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import 'const/custom_colors.dart'; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + title: 'Emoji -> E-Paper App', + theme: ThemeData( + textTheme: GoogleFonts.poppinsTextTheme(Theme.of(context).textTheme), + scaffoldBackgroundColor: kMainBG, + primarySwatch: Colors.amber, + visualDensity: VisualDensity.adaptivePlatformDensity, + ), + onGenerateRoute: router.generateRoute, + initialRoute: SplashScreenRoute, + ); + } +} diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/route/router.dart b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/route/router.dart new file mode 100644 index 0000000..d5a816a --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/route/router.dart @@ -0,0 +1,35 @@ +import 'dart:typed_data'; + +import 'package:epaper_emoji_ble/route/routing_constants.dart'; +import 'package:epaper_emoji_ble/screens/ble_list_screen.dart'; +import 'package:epaper_emoji_ble/screens/emji_converter_screen.dart'; +import 'package:epaper_emoji_ble/screens/emoji_screen.dart'; +import 'package:epaper_emoji_ble/screens/splash_screen.dart'; +import 'package:epaper_emoji_ble/screens/undefined_screen.dart'; +import 'package:flutter/material.dart'; + +Route generateRoute(RouteSettings settings) { + switch (settings.name) { + case SplashScreenRoute: + return MaterialPageRoute(builder: (context) => const SplashScreen()); + + case EmojiScreenRoute: + return MaterialPageRoute(builder: (context) => const EmojiScreen()); + + case EmojiConverterScreenRoute: + return MaterialPageRoute( + builder: (context) => EmojiConverterScreen( + selectedEmoji: settings.arguments as String)); + + case BLEScreenRoute: + return MaterialPageRoute( + builder: (context) => + BleListScreen(headedBitmap: settings.arguments as Uint8List)); + + default: + return MaterialPageRoute( + builder: (context) => UndefinedView( + name: settings.name!, + )); + } +} diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/route/routing_constants.dart b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/route/routing_constants.dart new file mode 100644 index 0000000..ea9ce76 --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/route/routing_constants.dart @@ -0,0 +1,4 @@ +const String SplashScreenRoute = '/'; +const String EmojiScreenRoute = '/Emoji'; +const String EmojiConverterScreenRoute = '/EmojiConverter'; +const String BLEScreenRoute = '/BLEList'; diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/ble_list_screen.dart b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/ble_list_screen.dart new file mode 100644 index 0000000..78de532 --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/ble_list_screen.dart @@ -0,0 +1,235 @@ +import 'dart:typed_data'; + +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:epaper_emoji_ble/const/custom_styles.dart'; +import 'package:epaper_emoji_ble/widgets/ble_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_blue/flutter_blue.dart'; +import 'package:quiver/iterables.dart'; +import 'package:sn_progress_dialog/progress_dialog.dart'; +import 'package:wakelock/wakelock.dart'; + +class BleListScreen extends StatelessWidget { + final Uint8List headedBitmap; + + const BleListScreen({Key? key, required this.headedBitmap}) : super(key: key); + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: FlutterBlue.instance.state, + initialData: BluetoothState.unknown, + builder: (c, snapshot) { + final state = snapshot.data; + if (state == BluetoothState.on) { + return FindDevicesScreen( + headedBitmap: headedBitmap, + ); + } + return BluetoothOffScreen(state: state); + }); + } +} + +class BluetoothOffScreen extends StatelessWidget { + const BluetoothOffScreen({Key? key, this.state}) : super(key: key); + + final BluetoothState? state; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.lightBlue, + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.bluetooth_disabled, + size: 200.0, + color: Colors.white54, + ), + Text( + 'Bluetooth Adapter is ${state != null ? state.toString().substring(15) : 'not available'}.', + style: Theme.of(context) + .primaryTextTheme + .headline1 + ?.copyWith(color: Colors.white), + ), + ], + ), + ), + ); + } +} + +class FindDevicesScreen extends StatelessWidget { + final Uint8List headedBitmap; + + const FindDevicesScreen({Key? key, required this.headedBitmap}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Find Devices'), + ), + body: RefreshIndicator( + onRefresh: () => + FlutterBlue.instance.startScan(timeout: const Duration(seconds: 4)), + child: SingleChildScrollView( + child: Column( + children: [ + StreamBuilder>( + stream: Stream.periodic(const Duration(seconds: 2)) + .asyncMap((_) => FlutterBlue.instance.connectedDevices), + initialData: [], + builder: (c, snapshot) => Column( + children: snapshot.data! + .map((d) => ListTile( + title: Text( + d.name, + style: kButtonText.copyWith(color: Colors.white), + ), + subtitle: Text( + d.id.toString(), + style: + kSubtitleText.copyWith(color: Colors.white), + ), + trailing: StreamBuilder( + stream: d.state, + initialData: BluetoothDeviceState.disconnected, + builder: (c, snapshot) { + if (snapshot.data == + BluetoothDeviceState.connected) { + return TextButton( + child: Text( + 'Send', + style: kButtonText.copyWith( + color: Colors.white), + ), + onPressed: () { + _findTargetUUID(context, d); + }); + } + return Text(snapshot.data.toString()); + }, + ), + )) + .toList(), + ), + ), + StreamBuilder>( + stream: FlutterBlue.instance.scanResults, + initialData: [], + builder: (c, snapshot) => Column( + children: snapshot.data! + .map( + (r) => ScanResultTile( + result: r, + onTap: () { + r.device.connect(); + }, + ), + ) + .toList(), + ), + ), + ], + ), + ), + ), + floatingActionButton: StreamBuilder( + stream: FlutterBlue.instance.isScanning, + initialData: false, + builder: (c, snapshot) { + if (snapshot.data!) { + return FloatingActionButton( + child: const Icon(Icons.stop), + onPressed: () => FlutterBlue.instance.stopScan(), + backgroundColor: Colors.red, + ); + } else { + return FloatingActionButton( + child: const Icon(Icons.search), + onPressed: () => FlutterBlue.instance + .startScan(timeout: const Duration(seconds: 4))); + } + }, + ), + ); + } + + final String SERVICE_UUID = "713d0001-503e-4c75-ba94-3148f18d941e"; + final String CHARACTERISTIC_UUID = "713d0002-503e-4c75-ba94-3148f18d941e"; + + _findTargetUUID(BuildContext context, BluetoothDevice device) async { + print("device :${device.id}"); + try { + List services = await device.discoverServices(); + + for (var service in services) { + print('service!!! : ${service}'); + print('service last uuid!!! : ${service.uuid.toString()}'); + + if (service.uuid.toString() == SERVICE_UUID) { + for (var characteristic in service.characteristics) { + if (characteristic.uuid.toString() == CHARACTERISTIC_UUID) { + print( + 'characteristic uuid!!! : ${characteristic.uuid.toString()}'); + + _sendData(context, characteristic); + // for (var pair in pairs) { + // print(pair); + // await characteristic.write(pair, withoutResponse: false); + // + // print('SENT!'); + // } + } + } + } + } + } catch (e) { + await showOkAlertDialog( + context: context, + title: 'Error', + message: e.toString(), + ); + } + } + + _sendData( + BuildContext context, BluetoothCharacteristic characteristic) async { + Wakelock.enable(); + print('headedBitmap length: ${headedBitmap.length}'); + var pairs = partition(headedBitmap, 20); + + var startSignal = await characteristic.write([], withoutResponse: false); + print('startSignal: $startSignal'); + ProgressDialog pd = ProgressDialog(context: context); + + pd.show( + max: pairs.length, + msg: 'Preparing Upload...', + progressType: ProgressType.valuable, + backgroundColor: const Color(0xff212121), + progressValueColor: Colors.amberAccent, + progressBgColor: Colors.white70, + msgColor: Colors.white, + valueColor: Colors.white); + + int i = 0; + for (var pair in pairs) { + ++i; + print(pair); + pd.update(value: i, msg: 'Bitmap Uploading...'); + + await characteristic.write(pair, withoutResponse: false); + } + + var endSignal = await characteristic.write([], withoutResponse: false); + print('endSignal: $endSignal'); + Wakelock.disable(); + } +} diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/emji_converter_screen.dart b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/emji_converter_screen.dart new file mode 100644 index 0000000..a601f0b --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/emji_converter_screen.dart @@ -0,0 +1,166 @@ +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:bitmap/bitmap.dart'; +import 'package:epaper_emoji_ble/const/custom_styles.dart'; +import 'package:epaper_emoji_ble/route/routing_constants.dart'; +import 'package:epaper_emoji_ble/util/bitmap24.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:image/image.dart' as img; + +class EmojiConverterScreen extends StatefulWidget { + final String selectedEmoji; + + const EmojiConverterScreen({Key? key, required this.selectedEmoji}) + : super(key: key); + + @override + _EmojiConverterScreenState createState() => _EmojiConverterScreenState(); +} + +class _EmojiConverterScreenState extends State { + GlobalKey globalKey = GlobalKey(); + Uint8List? bitmap24WithHeader; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Emoji Converter'), + ), + body: ListView(children: [ + Center( + child: Column( + children: [ + const SizedBox( + height: 40, + ), + RepaintBoundary( + key: globalKey, + child: Container( + color: Colors.transparent, + width: 200, + height: 200, + child: Center( + child: Text( + widget.selectedEmoji, + style: const TextStyle(fontSize: 160), + )), + ), + ), + const SizedBox( + height: 40, + ), + bitmap24WithHeader == null + ? Container( + height: 60, + width: MediaQuery.of(context).size.width * 0.8, + decoration: BoxDecoration( + color: Colors.grey[850], + borderRadius: BorderRadius.circular(18), + ), + child: TextButton( + style: ButtonStyle( + overlayColor: MaterialStateProperty.resolveWith( + (states) => Colors.black12, + ), + ), + onPressed: _convertImage, + child: Text( + 'Convert to Bitmap', + style: kButtonText.copyWith(color: Colors.white), + ), + )) + : Container( + height: 100, + width: 100, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(18), + ), + child: Image.memory( + bitmap24WithHeader!, + )), + const SizedBox( + height: 40, + ), + bitmap24WithHeader != null + ? Container( + height: 60, + width: MediaQuery.of(context).size.width * 0.8, + decoration: BoxDecoration( + color: Colors.grey[850], + borderRadius: BorderRadius.circular(18), + ), + child: TextButton( + style: ButtonStyle( + overlayColor: MaterialStateProperty.resolveWith( + (states) => Colors.black12, + ), + ), + onPressed: _findBLEDevice, + child: Text( + 'Find & Select Your Mini E-Paper', + style: kButtonText.copyWith(color: Colors.white), + ), + )) + : Container() + ], + ), + ), + ]), + ); + } + + _convertImage() async { + final RenderRepaintBoundary boundary = + globalKey.currentContext!.findRenderObject()! as RenderRepaintBoundary; + final ui.Image image = await boundary.toImage(); + final ByteData? byteData = + await image.toByteData(format: ui.ImageByteFormat.png); + final Uint8List pngBytes = byteData!.buffer.asUint8List(); + + var pngImage; + try { + pngImage = img.decodePng(pngBytes); + } catch (e) { + await showOkAlertDialog( + context: context, + title: 'Error', + message: e.toString(), + ); + return; + } + + var resizedImage = img.copyResize(pngImage, width: 80, height: 80); + var grayImage = img.grayscale(resizedImage); + var imgThreeColor = img.quantize(grayImage, numberOfColors: 3); + var flipImage = img.flip(imgThreeColor, img.Flip.vertical); + + Bitmap bitmap = Bitmap.fromHeadless( + resizedImage.width, resizedImage.height, flipImage.getBytes()); + + Uint8List bitmap24Data = Bitmap24.fromBitMap32( + resizedImage.width, resizedImage.height, bitmap.content); + Bitmap24 bitmap24 = Bitmap24.fromHeadless( + resizedImage.width, resizedImage.height, bitmap24Data); + + setState(() { + bitmap24WithHeader = bitmap24.buildHeaded(); + }); + } + + _findBLEDevice() async { + if (bitmap24WithHeader == null) { + await showOkAlertDialog( + context: context, + title: 'Error', + message: 'Check the Bitmap Emoji', + ); + return; + } + Navigator.pushNamed(context, BLEScreenRoute, arguments: bitmap24WithHeader); + } +} diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/emoji_screen.dart b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/emoji_screen.dart new file mode 100644 index 0000000..a2685aa --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/emoji_screen.dart @@ -0,0 +1,51 @@ +import 'package:epaper_emoji_ble/const/emoji_data.dart'; +import 'package:epaper_emoji_ble/route/routing_constants.dart'; +import 'package:flutter/material.dart'; + +class EmojiScreen extends StatelessWidget { + const EmojiScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Padding( + padding: const EdgeInsets.only(top: 40, bottom: 10), + child: Center(child: emojiGridView(context)), + ), + ); + } + + Widget emojiGridView(BuildContext context) { + return MediaQuery.removePadding( + context: context, + removeTop: true, + child: GridView.builder( + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.all(8), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: MediaQuery.of(context).size.width > + MediaQuery.of(context).size.height + ? 10 + : 5, + ), + itemCount: emojiArray.length, + itemBuilder: (BuildContext context, int index) { + return Card( + color: Colors.amber, + child: InkWell( + onTap: () { + Navigator.pushNamed(context, EmojiConverterScreenRoute, + arguments: emojiArray[index]); + }, + child: Center( + child: Text( + emojiArray[index], + style: const TextStyle(fontSize: 30), + ), + ), + ), + ); + }), + ); + } +} diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/splash_screen.dart b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/splash_screen.dart new file mode 100644 index 0000000..d63a748 --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/splash_screen.dart @@ -0,0 +1,87 @@ +import 'package:epaper_emoji_ble/const/custom_styles.dart'; +import 'package:epaper_emoji_ble/route/routing_constants.dart'; +import 'package:flutter/material.dart'; + +class SplashScreen extends StatelessWidget { + const SplashScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ListView( + children: [ + _getScreen(context), + ], + ), + ); + } + + _getScreen(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 20, right: 20, top: 40, bottom: 10), + child: Column( + children: [ + Column( + children: [ + Center( + child: Container( + child: const Image( + image: AssetImage( + 'assets/images/iot_image.png', + ), + height: 200, + color: Colors.white, + ), + ), + ), + const SizedBox( + height: 20, + ), + const Text( + "E-Paper\n^\n|\nBLE\n|\nEmoji๐Ÿ˜ƒ", + style: kHeadline, + textAlign: TextAlign.center, + ), + const SizedBox( + height: 20, + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.8, + child: const Text( + "It's a project that allows you to send emojis to Mini E-Paper. For detail, check out my channel. www.that-project.com", + style: kBodyText, + textAlign: TextAlign.center, + ), + ) + ], + ), + const SizedBox( + height: 50, + ), + Container( + height: 60, + width: MediaQuery.of(context).size.width * 0.8, + decoration: BoxDecoration( + color: Colors.grey[850], + borderRadius: BorderRadius.circular(18), + ), + child: TextButton( + style: ButtonStyle( + overlayColor: MaterialStateProperty.resolveWith( + (states) => Colors.black12, + ), + ), + onPressed: () { + Navigator.pushNamedAndRemoveUntil(context, EmojiScreenRoute, + (Route route) => false); + }, + child: Text( + 'GET STARTED', + style: kButtonText.copyWith(color: Colors.white), + ), + )) + ], + ), + ); + } +} diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/undefined_screen.dart b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/undefined_screen.dart new file mode 100644 index 0000000..2aafa49 --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/undefined_screen.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +class UndefinedView extends StatelessWidget { + final String name; + + const UndefinedView({Key? key, required this.name}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Text('Route for $name is not defined'), + ), + ); + } +} diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/util/bitmap24.dart b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/util/bitmap24.dart new file mode 100644 index 0000000..658a18b --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/util/bitmap24.dart @@ -0,0 +1,88 @@ +import 'dart:typed_data'; + +class Bitmap24 { + /// The width in pixels of the image. + final int width; + + /// The width in pixels of the image. + final int height; + + /// A [Uint8List] of bytes in a RGB format. + final Uint8List content; + + int get size => (width * height) * RGB24BitmapHeader.pixelLength; + + Bitmap24.fromHeadless(this.width, this.height, this.content); + + Bitmap24.fromHeadful(this.width, this.height, Uint8List headedIntList) + : content = headedIntList.sublist( + RGB24BitmapHeader.RGB24HeaderSize, + headedIntList.length, + ); + + Bitmap24.blank( + this.width, + this.height, + ) : content = Uint8List.fromList( + List.filled(width * height * RGB24BitmapHeader.pixelLength, 0), + ); + + Uint8List buildHeaded() { + final header = RGB24BitmapHeader(size, width, height) + ..applyContent(content); + return header.headerIntList; + } + + static Uint8List fromBitMap32(int width, int height, Uint8List bitmap32) { + Uint8List R8G8B8 = Uint8List(width * height * 3); + int j = 0; + for (int i = 0; i < (bitmap32.length - 3); i = i + 4) { + var Red = bitmap32[i + 2]; + var Green = bitmap32[i + 1]; + var Blue = bitmap32[i]; + + R8G8B8[j] = Red; + R8G8B8[j + 1] = Green; + R8G8B8[j + 2] = Blue; + j = j + 3; + } + + return R8G8B8; + } +} + +class RGB24BitmapHeader { + static const int pixelLength = 3; + static const int RGB24HeaderSize = 54; + + RGB24BitmapHeader(this.contentSize, int width, int height) { + headerIntList = Uint8List(fileLength); + + final ByteData bd = headerIntList.buffer.asByteData(); + bd.setUint8(0x0, 0x42); + bd.setUint8(0x1, 0x4d); + bd.setInt32(0x2, fileLength, Endian.little); + bd.setInt32(0xa, RGB24HeaderSize, Endian.little); + bd.setUint32(0xe, 28, Endian.little); + bd.setUint32(0x12, width, Endian.little); + bd.setUint32(0x16, height, Endian.little); + bd.setUint16(0x1a, 1, Endian.little); + bd.setUint32(0x1c, 24, Endian.little); // pixel size + bd.setUint32(0x1e, 0, Endian.little); //BI_BITFIELDS + bd.setUint32(0x22, contentSize, Endian.little); + } + + int contentSize; + + void applyContent(Uint8List contentIntList) { + headerIntList.setRange( + RGB24HeaderSize, + fileLength, + contentIntList, + ); + } + + late Uint8List headerIntList; + + int get fileLength => contentSize + RGB24HeaderSize; +} diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/widgets/ble_widget.dart b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/widgets/ble_widget.dart new file mode 100644 index 0000000..28374c5 --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/widgets/ble_widget.dart @@ -0,0 +1,314 @@ +// Copyright 2017, Paul DeMarco. +// All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:epaper_emoji_ble/const/custom_styles.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_blue/flutter_blue.dart'; + +class ScanResultTile extends StatelessWidget { + const ScanResultTile({Key? key, required this.result, this.onTap}) + : super(key: key); + + final ScanResult result; + final VoidCallback? onTap; + + Widget _buildTitle(BuildContext context) { + if (result.device.name.isNotEmpty) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + result.device.name, + overflow: TextOverflow.ellipsis, + style: kButtonText.copyWith(color: Colors.white), + ), + Text( + result.device.id.toString(), + style: kSubtitleText.copyWith(color: Colors.white), + ) + ], + ); + } else { + return Text(result.device.id.toString(), + style: kSubtitleText.copyWith(color: Colors.white)); + } + } + + Widget _buildAdvRow(BuildContext context, String title, String value) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: kSubtitleText.copyWith(color: Colors.white), + ), + const SizedBox( + width: 12.0, + ), + Expanded( + child: Text( + value, + style: kSubtitleText.copyWith(color: Colors.white), + softWrap: true, + ), + ), + ], + ), + ); + } + + String getNiceHexArray(List bytes) { + return '[${bytes.map((i) => i.toRadixString(16).padLeft(2, '0')).join(', ')}]' + .toUpperCase(); + } + + String getNiceManufacturerData(Map> data) { + if (data.isEmpty) { + return 'N/A'; + } + List res = []; + data.forEach((id, bytes) { + res.add( + '${id.toRadixString(16).toUpperCase()}: ${getNiceHexArray(bytes)}'); + }); + return res.join(', '); + } + + String getNiceServiceData(Map> data) { + if (data.isEmpty) { + return 'N/A'; + } + List res = []; + data.forEach((id, bytes) { + res.add('${id.toUpperCase()}: ${getNiceHexArray(bytes)}'); + }); + return res.join(', '); + } + + @override + Widget build(BuildContext context) { + return ExpansionTile( + title: _buildTitle(context), + leading: Text( + result.rssi.toString(), + style: kButtonText.copyWith(color: Colors.white), + ), + trailing: TextButton( + style: ButtonStyle( + overlayColor: MaterialStateProperty.resolveWith( + (states) => Colors.black12, + ), + ), + child: Text( + 'CONNECT', + style: kButtonText.copyWith(color: Colors.white), + ), + onPressed: (result.advertisementData.connectable) ? onTap : null, + ), + children: [ + _buildAdvRow( + context, 'Complete Local Name', result.advertisementData.localName), + _buildAdvRow(context, 'Tx Power Level', + '${result.advertisementData.txPowerLevel ?? 'N/A'}'), + _buildAdvRow(context, 'Manufacturer Data', + getNiceManufacturerData(result.advertisementData.manufacturerData)), + _buildAdvRow( + context, + 'Service UUIDs', + (result.advertisementData.serviceUuids.isNotEmpty) + ? result.advertisementData.serviceUuids.join(', ').toUpperCase() + : 'N/A'), + _buildAdvRow(context, 'Service Data', + getNiceServiceData(result.advertisementData.serviceData)), + ], + ); + } +} + +class ServiceTile extends StatelessWidget { + final BluetoothService service; + final List characteristicTiles; + + const ServiceTile( + {Key? key, required this.service, required this.characteristicTiles}) + : super(key: key); + + @override + Widget build(BuildContext context) { + if (characteristicTiles.isNotEmpty) { + return ExpansionTile( + title: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Service'), + Text( + '0x${service.uuid.toString().toUpperCase().substring(4, 8)}', + style: kSubtitleText.copyWith(color: Colors.white), + ) + ], + ), + children: characteristicTiles, + ); + } else { + return ListTile( + title: const Text('Service'), + subtitle: + Text('0x${service.uuid.toString().toUpperCase().substring(4, 8)}'), + ); + } + } +} + +class CharacteristicTile extends StatelessWidget { + final BluetoothCharacteristic characteristic; + final List descriptorTiles; + final VoidCallback? onReadPressed; + final VoidCallback? onWritePressed; + final VoidCallback? onNotificationPressed; + + const CharacteristicTile( + {Key? key, + required this.characteristic, + required this.descriptorTiles, + this.onReadPressed, + this.onWritePressed, + this.onNotificationPressed}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return StreamBuilder>( + stream: characteristic.value, + initialData: characteristic.lastValue, + builder: (c, snapshot) { + final value = snapshot.data; + return ExpansionTile( + title: ListTile( + title: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Characteristic'), + Text( + '0x${characteristic.uuid.toString().toUpperCase().substring(4, 8)}', + style: Theme.of(context).textTheme.bodyText1?.copyWith( + color: Theme.of(context).textTheme.caption?.color)) + ], + ), + subtitle: Text(value.toString()), + contentPadding: const EdgeInsets.all(0.0), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon( + Icons.file_download, + color: Theme.of(context).iconTheme.color?.withOpacity(0.5), + ), + onPressed: onReadPressed, + ), + IconButton( + icon: Icon(Icons.file_upload, + color: Theme.of(context).iconTheme.color?.withOpacity(0.5)), + onPressed: onWritePressed, + ), + IconButton( + icon: Icon( + characteristic.isNotifying + ? Icons.sync_disabled + : Icons.sync, + color: Theme.of(context).iconTheme.color?.withOpacity(0.5)), + onPressed: onNotificationPressed, + ) + ], + ), + children: descriptorTiles, + ); + }, + ); + } +} + +class DescriptorTile extends StatelessWidget { + final BluetoothDescriptor descriptor; + final VoidCallback? onReadPressed; + final VoidCallback? onWritePressed; + + const DescriptorTile( + {Key? key, + required this.descriptor, + this.onReadPressed, + this.onWritePressed}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Descriptor'), + Text('0x${descriptor.uuid.toString().toUpperCase().substring(4, 8)}', + style: Theme.of(context) + .textTheme + .bodyText1 + ?.copyWith(color: Theme.of(context).textTheme.caption?.color)) + ], + ), + subtitle: StreamBuilder>( + stream: descriptor.value, + initialData: descriptor.lastValue, + builder: (c, snapshot) => Text(snapshot.data.toString()), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon( + Icons.file_download, + color: Theme.of(context).iconTheme.color?.withOpacity(0.5), + ), + onPressed: onReadPressed, + ), + IconButton( + icon: Icon( + Icons.file_upload, + color: Theme.of(context).iconTheme.color?.withOpacity(0.5), + ), + onPressed: onWritePressed, + ) + ], + ), + ); + } +} + +class AdapterStateTile extends StatelessWidget { + const AdapterStateTile({Key? key, required this.state}) : super(key: key); + + final BluetoothState state; + + @override + Widget build(BuildContext context) { + return Container( + color: Colors.redAccent, + child: ListTile( + title: Text( + 'Bluetooth adapter is ${state.toString().substring(15)}', + style: Theme.of(context).primaryTextTheme.headline1, + ), + trailing: Icon( + Icons.error, + color: Theme.of(context).primaryTextTheme.subtitle1?.color, + ), + ), + ); + } +} diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/pubspec.lock b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/pubspec.lock new file mode 100644 index 0000000..dab1a66 --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/pubspec.lock @@ -0,0 +1,439 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + adaptive_dialog: + dependency: "direct main" + description: + name: adaptive_dialog + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + animations: + dependency: transitive + description: + name: animations + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.2" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.8.1" + bitmap: + dependency: "direct main" + description: + name: bitmap + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.2" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_blue: + dependency: "direct main" + description: + name: flutter_blue + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + font_awesome_flutter: + dependency: "direct main" + description: + name: font_awesome_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "9.1.0" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.3" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + image: + dependency: "direct main" + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.10" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.7.0" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + path_provider: + dependency: transitive + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.1" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.3" + protobuf: + dependency: transitive + description: + name: protobuf + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + quiver: + dependency: "direct main" + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.26.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + sn_progress_dialog: + dependency: "direct main" + description: + name: sn_progress_dialog + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.2" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + wakelock: + dependency: "direct main" + description: + name: wakelock + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.3+3" + wakelock_macos: + dependency: transitive + description: + name: wakelock_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0+2" + wakelock_platform_interface: + dependency: transitive + description: + name: wakelock_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.1+2" + wakelock_web: + dependency: transitive + description: + name: wakelock_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0+2" + wakelock_windows: + dependency: transitive + description: + name: wakelock_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0+1" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.9" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "5.2.0" +sdks: + dart: ">=2.13.0 <3.0.0" + flutter: ">=2.0.0" diff --git a/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/pubspec.yaml b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/pubspec.yaml new file mode 100644 index 0000000..5e24320 --- /dev/null +++ b/E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/pubspec.yaml @@ -0,0 +1,101 @@ +name: epaper_emoji_ble +description: App allows to send the emoji you choose to your e-paper via ble + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.12.0 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + google_fonts: ^2.1.0 + adaptive_dialog: ^1.1.0 + font_awesome_flutter: ^9.1.0 + bitmap: ^0.1.2 + image: ^3.0.2 + flutter_blue: ^0.8.0 + quiver: ^3.0.1 + sn_progress_dialog: ^1.0.3 + wakelock: ^0.5.3+3 + +dev_dependencies: + flutter_test: + sdk: flutter + + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^1.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + assets: + - assets/images/ + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages