#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <Adafruit_GFX.h>
#if defined(EPD)
#include <GxEPD2.h>
#else
#include <Adafruit_ILI9341.h>
#define TFT_DC 15
#define TFT_CS 22
#define BLACK ILI9341_BLACK
#define WHITE ILI9341_WHITE
Adafruit_ILI9341 display = Adafruit_ILI9341(TFT_CS, TFT_DC);
#endif
// Conversion factor for seconds to microseconds
#define S_TO_uS_FACTOR (1000000)
#define M_TO_uS_FACTOR (6e7)
#define H_TO_uS_FACTOR (3.6e9)
#define PCF_INT 35
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const String url = "https://storage.googleapis.com/test-bucket-a83n/bar.bmp";
const String timeUrl = "http://worldtimeapi.org/api/timezone/Pacific/Auckland";
// Number of milliseconds to wait without receiving any data before we give up
const int kNetworkTimeout = 30*1000;
// Number of milliseconds to wait if no data is available before trying again
const int kNetworkDelay = 1000;
struct imageData {
uint8_t* img;
int len;
int index;
};
uint16_t img_read16(struct imageData &image) {
uint16_t result;
uint8_t* img = image.img;
((uint8_t *)&result)[0] = *(img + image.index); // LSB
((uint8_t *)&result)[1] = *(img + image.index + 1); // MSB
image.index += 2;
return result;
}
uint32_t img_read32(struct imageData &image) {
uint32_t result;
uint8_t* img = image.img;
((uint8_t *)&result)[0] = *(img + image.index); // LSB
((uint8_t *)&result)[1] = *(img + image.index + 1);
((uint8_t *)&result)[2] = *(img + image.index + 2);
((uint8_t *)&result)[3] = *(img + image.index + 3); // MSB
image.index += 4;
return result;
}
uint8_t img_read(struct imageData &image) {
uint8_t result;
uint8_t* img = image.img;
result = *(img + image.index);
image.index += 1;
return result;
}
static const uint16_t input_buffer_pixels = 20;
static uint8_t input_buffer[3 * input_buffer_pixels];
static const uint16_t max_palette_pixels = 256; // for depth <= 8
static uint8_t mono_palette_buffer[max_palette_pixels / 8]; // palette buffer for depth <= 8 b/w
void display_bitmap(struct imageData &image)
{
bool valid = false; // valid format to be handled
bool flip = true; // bitmap is stored bottom-to-top
uint32_t startTime = millis();
// Parse BMP header
if (img_read16(image) == 0x4D42) { // BMP signature
uint32_t fileSize = img_read32(image);
uint32_t creatorBytes = img_read32(image);
uint32_t imageOffset = img_read32(image); // Start of image data
uint32_t headerSize = img_read32(image);
uint32_t width = img_read32(image);
uint32_t height = img_read32(image);
uint16_t planes = img_read16(image);
uint16_t depth = img_read16(image); // bits per pixel
uint32_t format = img_read32(image);
if ((planes == 1) && ((format == 0) || (format == 3))) { // uncompressed is handled, 565 also
// 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;
valid = true;
uint8_t bitmask = 0xFF;
uint8_t bitshift = 8 - depth;
uint16_t red, green, blue;
bool whitish, colored;
if (depth <= 8) {
if (depth < 8) bitmask >>= depth;
image.index = 54; //palette is always @ 54
for (uint16_t pn = 0; pn < (1 << depth); pn++) {
blue = img_read(image);
green = img_read(image);
red = img_read(image);
img_read(image);
whitish = ((red + green + blue) > 3 * 0x80); // whitish
if (0 == pn % 8) mono_palette_buffer[pn / 8] = 0;
mono_palette_buffer[pn / 8] |= whitish << pn % 8;
}
}
//display.fillScreen(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 = BLACK;
image.index = 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 = in_remain > sizeof(input_buffer) ? sizeof(input_buffer) : in_remain;
for (int i = 0; i < in_bytes; i++) {
input_buffer[i] = img_read(image);
}
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 = ((red + green + blue) > 3 * 0x80); // whitish
colored = (red > 0xF0) || ((green > 0xF0) && (blue > 0xF0)); // reddish or yellowish?
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;
} else {// 565
blue = (lsb & 0x1F) << 3;
green = ((msb & 0x07) << 5) | ((lsb & 0xE0) >> 3);
red = (msb & 0xF8);
}
whitish = ((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);
in_byte <<= depth;
in_bits -= depth;
}
break;
}
if (whitish)
color = WHITE;
else
color = BLACK;
uint16_t yrow = (flip ? h - row - 1 : row);
if (col < display.width() && yrow < display.height()) {
display.drawPixel(col, yrow, color);
}
} // end pixel
} // end line
}
}
}
String get_time() {
HTTPClient http;
http.useHTTP10(true);
http.begin(timeUrl);
http.GET();
String result = http.getString();
DynamicJsonDocument doc(2048);
DeserializationError error = deserializeJson(doc, result);
// Test if parsing succeeds.
if (error) {
Serial.print("deserializeJson() failed: ");
Serial.println(error.c_str());
return "<error>";
}
String datetime = doc["datetime"].as<String>();
http.end();
return datetime;
}
imageData download_image() {
String dataStr = "";
HTTPClient http;
http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
// configure server and url
http.begin(url);
// start connection and send HTTP header
int httpCode = http.GET();
if(httpCode < 1) {
// TODO: Better error display
dataStr = "HTTP GET failed with error code " + String(http.errorToString(httpCode).c_str()) + " \n";
while(1);
}
if(httpCode != HTTP_CODE_OK) {
// TODO: Better error display
dataStr = "Server returned " + String(httpCode) + " error code\n";
while(1);
}
// get length of document (is -1 when Server sends no Content-Length header)
int len = http.getSize();
int orig = len;
// create buffer for read
uint8_t buff[128] = { 0 };
uint8_t * img = static_cast<uint8_t *>(malloc(len));
// get tcp stream
WiFiClient * stream = http.getStreamPtr();
// read all data from server
while(http.connected() && (len > 0 || len == -1)) {
// get available data size
size_t size = stream->available();
if(size) {
// read up to 128 byte
int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
if(len > 0) {
for (int i = 0; i < c; i++) {
img[i + (orig - len)] = buff[i];
}
len -= c;
}
}
delay(1);
}
imageData image;
image.img = img;
image.len = orig;
image.index = 0;
return image;
}
int8_t deep_sleep_timer_wakeup(uint64_t sleep_time_us)
{
// Turn off everything
// disable_everything();
esp_sleep_enable_timer_wakeup(sleep_time_us);
// Go to sleep
esp_deep_sleep_start();
return 0;
}
void setup() {
pinMode(PCF_INT, INPUT);
WiFi.begin(ssid, password, 6);
#if !defined(EPD)
display.begin();
#endif
display.setRotation(1);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
}
imageData image = download_image();
display_bitmap(image);
free(image.img);
display.setCursor(10, 50);
display.setTextColor(BLACK);
display.println(get_time());
deep_sleep_timer_wakeup(20 * M_TO_uS_FACTOR);
}
void loop() {
}