From ce83662ededa2e9b4e6b6fa50c6e686bc07d118e Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 12 Sep 2021 22:11:12 -0700 Subject: [PATCH] Mini E-Paper | Ep1. Uploading Emoji to E-Paper from Flutter App(iOS & Android) --- .../ESP32_Pico_GxEPD2_BLE.ino | 368 ++++++++ .../GxEPD2_display_selection_new_style.h | 222 +++++ .../GxEPD2_selection_check.h | 75 ++ .../FlutterApp_epaper_emoji_ble/.gitignore | 46 + .../FlutterApp_epaper_emoji_ble/.metadata | 10 + .../FlutterApp_epaper_emoji_ble/README.md | 5 + .../analysis_options.yaml | 29 + .../assets/images/iot_image.png | Bin 0 -> 30451 bytes .../lib/const/custom_colors.dart | 6 + .../lib/const/custom_styles.dart | 26 + .../lib/const/emoji_data.dart | 848 ++++++++++++++++++ .../FlutterApp_epaper_emoji_ble/lib/main.dart | 36 + .../lib/route/router.dart | 35 + .../lib/route/routing_constants.dart | 4 + .../lib/screens/ble_list_screen.dart | 235 +++++ .../lib/screens/emji_converter_screen.dart | 166 ++++ .../lib/screens/emoji_screen.dart | 51 ++ .../lib/screens/splash_screen.dart | 87 ++ .../lib/screens/undefined_screen.dart | 16 + .../lib/util/bitmap24.dart | 88 ++ .../lib/widgets/ble_widget.dart | 314 +++++++ .../FlutterApp_epaper_emoji_ble/pubspec.lock | 439 +++++++++ .../FlutterApp_epaper_emoji_ble/pubspec.yaml | 101 +++ 23 files changed, 3207 insertions(+) create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/ESP32_Pico_GxEPD2_BLE/ESP32_Pico_GxEPD2_BLE.ino create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/ESP32_Pico_GxEPD2_BLE/GxEPD2_display_selection_new_style.h create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/ESP32_Pico_GxEPD2_BLE/GxEPD2_selection_check.h create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/.gitignore create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/.metadata create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/README.md create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/analysis_options.yaml create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/assets/images/iot_image.png create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/const/custom_colors.dart create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/const/custom_styles.dart create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/const/emoji_data.dart create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/main.dart create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/route/router.dart create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/route/routing_constants.dart create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/ble_list_screen.dart create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/emji_converter_screen.dart create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/emoji_screen.dart create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/splash_screen.dart create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/screens/undefined_screen.dart create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/util/bitmap24.dart create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/lib/widgets/ble_widget.dart create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/pubspec.lock create mode 100644 E-Paper_Projects/01_Emoji2MiniE-Paper/FlutterApp_epaper_emoji_ble/pubspec.yaml 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 0000000000000000000000000000000000000000..d002fc4c6b85e4b9e50ebf6d3bd4211837b1e00f GIT binary patch literal 30451 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 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