diff --git a/.gitignore b/.gitignore index a14eddf..efcf066 100644 --- a/.gitignore +++ b/.gitignore @@ -18,8 +18,6 @@ dist/ downloads/ eggs/ .eggs/ -lib/ -lib64/ parts/ sdist/ var/ diff --git a/inkplate10/inkplate10.ino b/inkplate10/inkplate10.ino index 87bb02c..b4b3344 100644 --- a/inkplate10/inkplate10.ino +++ b/inkplate10/inkplate10.ino @@ -1,8 +1,8 @@ /* - Adapted from the Inkplate10 examples - https://github.com/SolderedElectronics/Inkplate-Arduino-library/blob/2dd263f2f9548aadac8638413a143beddf068a64/examples/Inkplate10/Projects/Inkplate10_Image_Frame_From_Web/Inkplate10_Image_Frame_From_Web.ino and - https://github.com/SolderedElectronics/Inkplate-Arduino-library/blob/2dd263f2f9548aadac8638413a143beddf068a64/examples/Inkplate10/Advanced/Other/Inkplate10_Read_Battery_Voltage/Inkplate10_Read_Battery_Voltage.ino and - https://github.com/SolderedElectronics/Inkplate-Arduino-library/blob/2dd263f2f9548aadac8638413a143beddf068a64/examples/Inkplate10/Advanced/DeepSleep/Inkplate10_Wake_Up_Button/Inkplate10_Wake_Up_Button.ino + Adapted from the Inkplate10 examples: + * https://github.com/SolderedElectronics/Inkplate-Arduino-library/blob/2dd263f2f9548aadac8638413a143beddf068a64/examples/Inkplate10/Advanced/WEB_WiFi/Inkplate10_Show_JPG_With_HTTPClient/Inkplate10_Show_JPG_With_HTTPClient.ino + * https://github.com/SolderedElectronics/Inkplate-Arduino-library/blob/2dd263f2f9548aadac8638413a143beddf068a64/examples/Inkplate10/Advanced/Other/Inkplate10_Read_Battery_Voltage/Inkplate10_Read_Battery_Voltage.ino + * https://github.com/SolderedElectronics/Inkplate-Arduino-library/blob/2dd263f2f9548aadac8638413a143beddf068a64/examples/Inkplate10/Advanced/DeepSleep/Inkplate10_Wake_Up_Button/Inkplate10_Wake_Up_Button.ino What this code does: 1. Connect to a WiFi access point @@ -12,32 +12,137 @@ 5. Set a sleep timer for 60 minutes, and allow the Inkplate to go into deep sleep to conserve battery */ -// Next 3 lines are a precaution, you can ignore those, and the example would also work without them #if !defined(ARDUINO_INKPLATE10) && !defined(ARDUINO_INKPLATE10V2) -#error "Wrong board selection for this example, please select e-radionica Inkplate10 or Soldered Inkplate10 in the boards menu." +#error \ + "Wrong board selection for this example, please select e-radionica Inkplate10 or Soldered Inkplate10 in the boards menu." #endif +#include "HTTPClient.h" #include "Inkplate.h" +#include "WiFi.h" -Inkplate display(INKPLATE_3BIT); +#include "lib/png_image_helper.cpp" -const char ssid[] = "YOUR WIFI SSID"; // Your WiFi SSID -const char *password = "YOUR WIFI PASSWORD"; // Your WiFi password -const char *imgurl = "http://url.to.your.server/image"; // Your dashboard image web address +char *ssid = "YOUR WIFI SSID"; // Your WiFi SSID +char *pass = "YOUR WIFI PASSWORD"; // Your WiFi password +String imgurl = "https://url-to-your-server/image"; // Your dashboard image web address #define BATTV_MAX 4.1 // maximum voltage of battery #define BATTV_MIN 3.2 // what we regard as an empty battery #define BATTV_LOW 3.4 // voltage considered to be low battery +Inkplate display(INKPLATE_1BIT); + void setup() { Serial.begin(115200); - display.begin(); + display.begin(); // Init Inkplate library + display.clearDisplay(); // Clear frame buffer of display + display.setTextSize(2); // Set text size to be 2 times bigger than original (5x7 px) + display.setTextColor(BLACK); // Set text color to black + display.setCursor(25, 25); + + // Show a connection message + display.print("Connecting to WiFi"); + display.partialUpdate(); + + // Actually connect to the WiFi network + WiFi.mode(WIFI_MODE_STA); + WiFi.begin(ssid, pass); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + display.print("."); + display.partialUpdate(); + } + display.print(" Successful."); + display.partialUpdate(); + display.setCursor(25, 50); + display.print("Downloading image..."); + display.partialUpdate(); + + // Switch to 3-bit mode so the image will be of better quality + display.setDisplayMode(INKPLATE_3BIT); + + // Make an object for the HTTP client + HTTPClient http; + http.setTimeout(10000); + http.begin(imgurl); + + int httpCode = http.GET(); + + if (httpCode == HTTP_CODE_OK) + { + // Get the size of the image + int32_t size = http.getSize(); + int32_t len = size; + + if (size > 0) + { + // Allocate the memory for the image + uint8_t *buffer = (uint8_t *)ps_malloc(size); + uint8_t *buffPtr = buffer; // Copy of the buffer pointer so that the original one is not lost + + // Temporary buffer for retrieving parts of the image and storing them in the real buffer + uint8_t buff[512] = {0}; + + // Let's fetch the data + WiFiClient *stream = http.getStreamPtr(); // We need a stream pointer to know how much data is available + + // Repeat as long as we have a connection and while there is data to read + while (http.connected() && (len > 0 || len == -1)) + { + // Get the number of available bytes + size_t size = stream->available(); + + // If there are available bytes, read them + if (size) + { + // Read available bytes from the stream and store them in the buffer + int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); + memcpy(buffPtr, buff, c); + + // As we read the data, we subtract the length we read and the remaining length is in the variable + // len + if (len > 0) + len -= c; + + // Likewise for the buffer pointer + buffPtr += c; + } + else if (len == -1) + { + len = 0; + } + } + + // Draw image into the frame buffer of Inkplate + drawPngFromBuffer(buffer, size, 0, 0, true, false); + } + else + { + // Show an error message + display.setCursor(25, 25); + display.print("Invalid response length " + String(size) + " (HTTP " + String(httpCode) + ")"); + } + } + else + { + // Show an error message + display.setCursor(25, 25); + display.print("HTTP error " + String(httpCode)); + } - // Join wifi - display.connectWiFi(ssid, password); + // Show battery percentage if low + double battvoltage = display.readBattery(); + int battpc = calc_battery_percentage(battvoltage); + if (battvoltage < BATTV_LOW) { + display.setCursor(1025, 800); // Inkplate 10 has a 9.7 inch, 1,200 x 825 pixel display + display.println("Battery: " + String(battpc) + "%"); + } - displayInfo(); + // Draw image on the screen + display.display(); // Go to sleep for 60min (60min * 60s * 1000ms * 1000us) esp_sleep_enable_timer_wakeup(60ll * 60 * 1000 * 1000); @@ -55,33 +160,6 @@ void loop() // time. loop() must be empty! } -void displayInfo() -{ - // First, lets delete everything from frame buffer - display.clearDisplay(); - - // Display image from API - if (!display.drawImage(imgurl, display.PNG, 0, 0)) - { - // If is something failed (wrong filename or format), write error message on the screen. - display.println("Image open error"); - } - - double battvoltage = display.readBattery(); - int battpc = calc_battery_percentage(battvoltage); - if (battvoltage < BATTV_LOW) { - char msg [20]; - sprintf (msg, "Battery: %d%%", battpc); - display.setCursor(1100, 800); // Inkplate 10 has a 9.7 inch, 1,200 x 825 pixel display - display.setTextSize(2); - display.setTextColor(BLACK); - display.print(msg); - } - - display.display(); // Send everything to display (refresh the screen) -} - - int calc_battery_percentage(double battv) { int battery_percentage = (uint8_t)(((battv - BATTV_MIN) / (BATTV_MAX - BATTV_MIN)) * 100); diff --git a/inkplate10/lib/png_image_helper.cpp b/inkplate10/lib/png_image_helper.cpp new file mode 100644 index 0000000..ef2e6e7 --- /dev/null +++ b/inkplate10/lib/png_image_helper.cpp @@ -0,0 +1,127 @@ +/** + ************************************************** + * @file png_image_helper.cpp + * @brief Needed for drawPngFromBuffer function as a workaround until + * https://github.com/SolderedElectronics/Inkplate-Arduino-library/pull/210 is merged + * + * This code is released under the GNU Lesser General Public + *License v3.0: https://www.gnu.org/licenses/lgpl-3.0.en.html Please review the + *LICENSE file included with this example. If you have any questions about + *licensing, please contact techsupport@e-radionica.com Distributed as-is; no + *warranty is given. + * + * @authors Soldered.com, Chris Twomey + ***************************************************/ + +#include "libs/pngle/pngle.h" + +extern Image *_imagePtrPng; + +static bool _pngInvert = 0; +static bool _pngDither = 0; +static int16_t lastY = -1; +static uint16_t _pngX = 0; +static uint16_t _pngY = 0; +static Image::Position _pngPosition = Image::_npos; + +uint8_t ditherBuffer[2][E_INK_WIDTH + 20]; + +/** + * @brief pngle_on_buffer_draw + * + * @param pngle_t *pngle + * pointer to image + * @param uint32_t x + * x plane position + * @param uint32_t y + * y plane position + * @param uint32_t w + * image width + * @param uint32_t h + * image height + * @param uint8_t rgba[4] + * color + */ +void pngle_on_buffer_draw(pngle_t *pngle, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint8_t rgba[4]) +{ + if (_pngPosition != Image::_npos) + { + _imagePtrPng->getPointsForPosition(_pngPosition, pngle_get_width(pngle), pngle_get_height(pngle), E_INK_WIDTH, + E_INK_HEIGHT, &_pngX, &_pngY); + lastY = _pngY; + _pngPosition = Image::_npos; + } + if (rgba[3]) + for (int j = 0; j < h; ++j) + for (int i = 0; i < w; ++i) + { + uint8_t r = rgba[0]; + uint8_t g = rgba[1]; + uint8_t b = rgba[2]; + + pngle_ihdr_t *ihdr = pngle_get_ihdr(pngle); + + if (ihdr->depth == 1) + r = g = b = (b ? 0xFF : 0); + + uint8_t px = RGB3BIT(r, g, b); + + if (_pngDither) + { + px = _imagePtrPng->ditherGetPixelBmp(RGB8BIT(r, g, b), x + i, y + j, _imagePtrPng->width(), 0); + if (_pngInvert) + px = 7 - px; + if (_imagePtrPng->getDisplayMode() == INKPLATE_1BIT) + px = (~px >> 2) & 1; + } + _imagePtrPng->drawPixel(_pngX + x + i, _pngY + y + j, px); + } + if (lastY != y) + { + lastY = y; + _imagePtrPng->ditherSwap(_imagePtrPng->width()); + } +} + +/** + * @brief drawPngFromBuffer function draws png image from buffer + * + * @param int32_t len + * size of buffer + * @param int x + * x position for top left image corner + * @param int y + * y position for top left image corner + * @param bool dither + * 1 if using dither, 0 if not + * @param bool invert + * 1 if using invert, 0 if not + * + * @return 1 if drawn successfully, 0 if not + */ +bool drawPngFromBuffer(uint8_t *buf, int32_t len, int x, int y, bool dither, bool invert) +{ + _pngDither = dither; + _pngInvert = invert; + lastY = y; + + bool ret = 1; + + if (dither) + memset(ditherBuffer, 0, sizeof ditherBuffer); + + pngle_t *pngle = pngle_new(); + _pngX = x; + _pngY = y; + pngle_set_draw_callback(pngle, pngle_on_buffer_draw); + + if (!buf) + return 0; + + if (pngle_feed(pngle, buf, len) < 0) + ret = 0; + pngle_destroy(pngle); + + free(buf); + return ret; +} diff --git a/pyproject.toml b/pyproject.toml index 34e0864..725aa89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "mag-ink-dash-plus" -version = "0.7.1" +version = "0.8.0" description = "E-Ink Magic Dashboard that runs off a battery powered Inkplate 10; displaying content from an ICS calendar feed and OpenWeatherMap that are retrieved and rendered by a Docker container." authors = ["speedyg0nz", "stefanthoss"] license = "Apache License 2.0" diff --git a/src/main.py b/src/main.py index d482d9f..5eb7451 100644 --- a/src/main.py +++ b/src/main.py @@ -24,7 +24,7 @@ cfg = MagInkDashConfig.get_config() -app = FastAPI(title="MagInkDashPlus Server", version="0.7.1") +app = FastAPI(title="MagInkDashPlus Server", version="0.8.0") logger = structlog.get_logger()