From 8102628f48508e59b522844aa90f3b9395e459ea Mon Sep 17 00:00:00 2001 From: ZinggJM Date: Mon, 11 Nov 2024 18:52:26 +0100 Subject: [PATCH 1/3] Create screenshot.bmp from z0gs, for test --- extras/bitmaps/z0gs/screenshot.bmp | Bin 0 -> 1843254 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 extras/bitmaps/z0gs/screenshot.bmp diff --git a/extras/bitmaps/z0gs/screenshot.bmp b/extras/bitmaps/z0gs/screenshot.bmp new file mode 100644 index 0000000000000000000000000000000000000000..44f4624cec86f7076d670c1b96cb28aaf729002b GIT binary patch literal 1843254 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%0DG*4R4`L@w6U@#ZrEyhS`Y8>uTkI;T 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 z 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 Date: Wed, 27 Nov 2024 11:28:04 +0100 Subject: [PATCH 2/3] Version 1.6.1 - fixed and updated GxEPD2_WiFi_Example --- README.md | 4 +- .../GxEPD2_Spiffs_Loader.ino | 10 +- .../GxEPD2_github_raw_certs.h | 208 +++++++++++------- .../GxEPD2_WiFi_CertStore_Example.ino | 138 ++++++------ .../GxEPD2_WiFi_Example.ino | 94 +++----- library.properties | 2 +- 6 files changed, 233 insertions(+), 223 deletions(-) diff --git a/README.md b/README.md index ee9edad0..999b7771 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,9 @@ - But only few panels at any time, and only panels from known sources. - Adding support will take as much time as needed. -### Version 1.6.0 +### Version 1.6.1 +- fixed and updated GxEPD2_WiFi_Example +#### Version 1.6.0 - updated support for GDEY029T94: fixed a partial refresh issue - updated support for GDEY042T81: fixed fast full refresh for 2024 panel version - updated support for GDEP073E01: fixed color mapping for its native color values diff --git a/examples/GxEPD2_Spiffs_Loader/GxEPD2_Spiffs_Loader.ino b/examples/GxEPD2_Spiffs_Loader/GxEPD2_Spiffs_Loader.ino index c4c6020c..07df4aa7 100644 --- a/examples/GxEPD2_Spiffs_Loader/GxEPD2_Spiffs_Loader.ino +++ b/examples/GxEPD2_Spiffs_Loader/GxEPD2_Spiffs_Loader.ino @@ -35,21 +35,20 @@ const char* ssid = "........"; const char* password = "........"; const int httpPort = 80; const int httpsPort = 443; -const char* fp_api_github_com = "df b2 29 c6 a6 38 1a 59 9d c9 ad 92 2d 26 f5 3c 83 8f a5 87"; // as of 25.11.2020 -const char* fp_github_com = "5f 3f 7a c2 56 9f 50 a4 66 76 47 c6 a1 8c a0 07 aa ed bb 8e"; // as of 25.11.2020 -const char* fp_rawcontent = "70 94 de dd e6 c4 69 48 3a 92 70 a1 48 56 78 2d 18 64 e0 b7"; // as of 25.11.2020 // note: the certificates have been moved to a separate header file, as R"CERT( destroys IDE Auto Format capability #include "GxEPD2_github_raw_certs.h" -const char* certificate_rawcontent = cert_DigiCert_TLS_RSA_SHA256_2020_CA1; // ok, should work until 2031-04-13 23:59:59 +const char* certificate_rawcontent = github_io_pem; // ok, should work until Fri, 14 Mar 2025 23:59:59 GMT const char* host_rawcontent = "raw.githubusercontent.com"; const char* path_rawcontent = "/ZinggJM/GxEPD2/master/extras/bitmaps/"; +const char* path_workcontent = "/ZinggJM/GxEPD2/work_in_progress/extras/bitmaps/"; const char* path_prenticedavid = "/prenticedavid/MCUFRIEND_kbv/master/extras/bitmaps/"; const char* path_waveshare_c = "/waveshare/e-Paper/master/RaspberryPi_JetsonNano/c/pic/"; const char* path_waveshare_py = "/waveshare/e-Paper/master/RaspberryPi_JetsonNano/python/pic/"; +const char* fp_rawcontent = "97:D8:C5:70:0F:12:24:6C:88:BC:FA:06:7E:8C:A7:4D:A8:62:67:28"; // SHA-1 as of 12.04.2024 void setup() { @@ -150,6 +149,7 @@ void downloadBitmaps_other() void downloadBitmaps_test() { + //downloadFile_HTTPS(host_rawcontent, path_workcontent, "z0gs/screenshot.bmp", fp_rawcontent, "screenshot.bmp"); return; downloadFile_HTTPS(host_rawcontent, path_rawcontent, "output5.bmp", fp_rawcontent, "output5.bmp"); downloadFile_HTTPS(host_rawcontent, path_rawcontent, "output6.bmp", fp_rawcontent, "output6.bmp"); downloadFile_HTTPS(host_rawcontent, path_rawcontent, "tractor_1.bmp", fp_rawcontent, "tractor_1.bmp"); @@ -289,6 +289,8 @@ void downloadFile_HTTPS(const char* host, const char* path, const char* filename Serial.println(); Serial.print("downloading file \""); Serial.print(filename); Serial.println("\""); Serial.print("connecting to "); Serial.println(host); #if defined (ESP8266) + client.setBufferSizes(4096, 4096); // required + //client.setBufferSizes(8192, 4096); // may help for some sites if (certificate) client.setTrustAnchors(&cert); else if (fingerprint) client.setFingerprint(fingerprint); else client.setInsecure(); diff --git a/examples/GxEPD2_Spiffs_Loader/GxEPD2_github_raw_certs.h b/examples/GxEPD2_Spiffs_Loader/GxEPD2_github_raw_certs.h index 26577192..75ad6981 100644 --- a/examples/GxEPD2_Spiffs_Loader/GxEPD2_github_raw_certs.h +++ b/examples/GxEPD2_Spiffs_Loader/GxEPD2_github_raw_certs.h @@ -1,3 +1,44 @@ +// how to find the certificate was not easy. finally I found it using Mozilla Firefox. +// opened one of the bitmaps, e.g. https://raw.githubusercontent.com/ZinggJM/GxEPD2/master/extras/bitmaps/logo200x200.bmp +// clicked the lock symbol, Connection secure clicked >, show connection details, clicked More Information, clicked View Certificate, clicked Download PEM (cert), +// Opened the .pem file and copied the pem into R"CERT(...)CERT"; strings. +// for https://raw.githubusercontent.com the pem works on ESP8266 and ESP32, if time is set from ntp. +// but using a root certificate would preferred, as its validity may be longer. But this failed now. + +const char github_io_pem [] PROGMEM = R"CERT( +-----BEGIN CERTIFICATE----- +MIIEyDCCA7CgAwIBAgIQDPW9BitWAvR6uFAsI8zwZjANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0yMTAzMzAwMDAwMDBaFw0zMTAzMjkyMzU5NTlaMFkxCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2Jh +bCBHMiBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAMz3EGJPprtjb+2QUlbFbSd7ehJWivH0+dbn4Y+9lavyYEEV +cNsSAPonCrVXOFt9slGTcZUOakGUWzUb+nv6u8W+JDD+Vu/E832X4xT1FE3LpxDy +FuqrIvAxIhFhaZAmunjZlx/jfWardUSVc8is/+9dCopZQ+GssjoP80j812s3wWPc +3kbW20X+fSP9kOhRBx5Ro1/tSUZUfyyIxfQTnJcVPAPooTncaQwywa8WV0yUR0J8 +osicfebUTVSvQpmowQTCd5zWSOTOEeAqgJnwQ3DPP3Zr0UxJqyRewg2C/Uaoq2yT +zGJSQnWS+Jr6Xl6ysGHlHx+5fwmY6D36g39HaaECAwEAAaOCAYIwggF+MBIGA1Ud +EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHSFgMBmx9833s+9KTeqAx2+7c0XMB8G +A1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYIKwYBBQUHAQEEajBoMCQG +CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQAYIKwYBBQUHMAKG +NGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RH +Mi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29t +L0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDA9BgNVHSAENjA0MAsGCWCGSAGG/WwC +ATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgGBmeBDAECAzANBgkqhkiG +9w0BAQsFAAOCAQEAkPFwyyiXaZd8dP3A+iZ7U6utzWX9upwGnIrXWkOH7U1MVl+t +wcW1BSAuWdH/SvWgKtiwla3JLko716f2b4gp/DA/JIS7w7d7kwcsr4drdjPtAFVS +slme5LnQ89/nD/7d+MS5EHKBCQRfz5eeLjJ1js+aWNJXMX43AYGyZm0pGrFmCW3R +bpD0ufovARTFXFZkAdl9h6g4U5+LXUZtXMYnhIHUfoyMo5tS58aI7Dd8KvvwVVo4 +chDYABPPTHPbqjc1qCmBaZx2vN4Ye5DUys/vZwP9BFohFrH/6j/f3IL16/RZkiMN +JCqVJUzKoZHm1Lesh3Sz8W2jmdv51b2EQJ8HmA== +-----END CERTIFICATE----- +)CERT"; + +// 12:04.2024: the following no longer works, needs further investigation +// ********************************************************************** + // http://cacerts.digicert.com/DigiCertTLSRSASHA2562020CA1-1.crt // CN: DigiCert TLS RSA SHA256 2020 CA1 => name: DigiCert_TLS_RSA_SHA256_2020_CA1 // not valid before: 2021-04-14 00:00:00 @@ -42,101 +83,102 @@ A7sKPPcw7+uvTPyLNhBzPvOk // https://raw.githubusercontent.com // issued by DigiCert Inc : DigiCert TLS RSA SHA256 2020 CA1 -// not valid before: Fri, 18 Mar 2022 00:00:00 GMT -// not valid after: Tue, 21 Mar 2023 23:59:59 GMT +// not valid before: Fri, 15 Mar 2024 00:00:00 GMT +// not valid after: Fri, 14 Mar 2025 23:59:59 GMT const char github_io_chain_pem_first [] PROGMEM = R"CERT( -----BEGIN CERTIFICATE----- -MIIHEzCCBfugAwIBAgIQC44ztdGGen6l0VAu1+MWiTANBgkqhkiG9w0BAQsFADBP -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMSkwJwYDVQQDEyBE -aWdpQ2VydCBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTAeFw0yMjAzMTgwMDAwMDBa -Fw0yMzAzMjEyMzU5NTlaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9y -bmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRUwEwYDVQQKEwxHaXRIdWIsIElu -Yy4xFDASBgNVBAMMCyouZ2l0aHViLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAxgDppBtMFl4zNvBbWAdr21IwcskxdMU/SkxYK/ZAXFrRIPcoZR15 -5DCrzTevHMUNLhCJqL8mMFidKOz4cZjhPn5sxjUCe/sPNvaiXm8cGUwzFlAYK1MY -dM+wepJKcT/qK8RegSIEbk/6qU5Bmh558RSCGuIJj3E85C0fRVdA+zXHP5GkuuB4 -VEXYzM2oz5KmgXIdBYFydjyK9LNO5nc/Z2Bc7JppCripRHfht3OR1Bf4JFYBuEJE -vE4E1FGxwhZzqA/F6ZTRNb+qhQgRfY3HNdbhv5/HRRQZY4H5V6F9MsO0RAHrz30A -QtB10paU9KOvxZqA/CEJTAdiYDQKAADitwIDAQABo4ID0TCCA80wHwYDVR0jBBgw -FoAUt2ui6qiqhIx56rTaD5iyxZV2ufQwHQYDVR0OBBYEFNPlHIi+YXl95r64oS2D -0v2+1mFkMHsGA1UdEQR0MHKCCyouZ2l0aHViLmlvggpnaXRodWIuY29tggwqLmdp -dGh1Yi5jb22CDnd3dy5naXRodWIuY29tgglnaXRodWIuaW+CFWdpdGh1YnVzZXJj -b250ZW50LmNvbYIXKi5naXRodWJ1c2VyY29udGVudC5jb20wDgYDVR0PAQH/BAQD -AgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjCBjwYDVR0fBIGHMIGE -MECgPqA8hjpodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUTFNSU0FT -SEEyNTYyMDIwQ0ExLTQuY3JsMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2VydC5j -b20vRGlnaUNlcnRUTFNSU0FTSEEyNTYyMDIwQ0ExLTQuY3JsMD4GA1UdIAQ3MDUw -MwYGZ4EMAQICMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29t -L0NQUzB/BggrBgEFBQcBAQRzMHEwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp -Z2ljZXJ0LmNvbTBJBggrBgEFBQcwAoY9aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu -Y29tL0RpZ2lDZXJ0VExTUlNBU0hBMjU2MjAyMENBMS0xLmNydDAJBgNVHRMEAjAA -MIIBfwYKKwYBBAHWeQIEAgSCAW8EggFrAWkAdgDoPtDaPvUGNTLnVyi8iWvJA9PL -0RFr7Otp4Xd9bQa9bgAAAX+dgO+dAAAEAwBHMEUCIQDOV8Qe7mebG+hhf+MfzEEF -2i0lNIO83vUTxkMREz/eMwIgKp3ZLFVMP2hz+1DAYPhmKvdWT3kTKWeeZxSHUEtm -f88AdgA1zxkbv7FsV78PrUxtQsu7ticgJlHqP+Eq76gDwzvWTAAAAX+dgO/rAAAE -AwBHMEUCIDAKos+w1Y1esfHWzcjREKA0m/fEoyMxA8Cj5EZETZziAiEAkxIcc6ZB -3d7pHTI2w1yuRp1s6uciDTU/ICZ5yEvuFtwAdwC3Pvsk35xNunXyOcW6WPRsXfxC -z3qfNcSeHQmBJe20mQAAAX+dgO/PAAAEAwBIMEYCIQDDYK04bMarexB4cqaAhnUF -FItaejcjp7CeW+YtF70CzAIhAK5Fy7ARpPdjNoaSWuG1NzNZbj6DPfwdsFOZnoBq -4GLhMA0GCSqGSIb3DQEBCwUAA4IBAQBokieSf5eLa7o62sPf9ihHuvIPoligvH1r -gxFf6+kgsz+EwtF80Tb0ZR2DC7O2HSi0JGiihPgKO/3bXHZUc9cc4NkL7J842yFI -rRwPHABMq2nLq7LCuGdJn+2tG76DAOXCtHMQ6XfuAq1FoPbtxXdWzynOJdDelPC6 -Qv3v3sMH6gJML1vW3OmeSUIncu686uDTs6E95BIuZ8eOjBjxZ7GfQv54RQU6oiMf -bYVRSYaNfnS0VkPlwcR9Ubhx9wEuG4GuDus7OWItwFqm2c3peKQQK8+2CDMUHDUC -QafDHvXcWeRdqQouCQC1tDlRAzPLKTcLD6EVltnPR5HQITRvHVgm +MIIHOTCCBiGgAwIBAgIQBj1JF0BNOeUTyz/uzRsuGzANBgkqhkiG9w0BAQsFADBZ +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMTMwMQYDVQQDEypE +aWdpQ2VydCBHbG9iYWwgRzIgVExTIFJTQSBTSEEyNTYgMjAyMCBDQTEwHhcNMjQw +MzE1MDAwMDAwWhcNMjUwMzE0MjM1OTU5WjBnMQswCQYDVQQGEwJVUzETMBEGA1UE +CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMM +R2l0SHViLCBJbmMuMRQwEgYDVQQDDAsqLmdpdGh1Yi5pbzCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAK0rFKU6TEGvuLCY3ZOuXlG+3jerD6EP1gc1qe35 +g68FqyGuVPOUddYNZiymjYMZxywoNp3qxlbFFBTf9etsayavT+uW+2UMjqCotAdK +KicBEspuExoACFuNgTi7sSUT7A55+k4/+5O+VtpaxQ5dmQk7HxcqvMYx5owBU+fB +wYDD+hXeg3YvxLZNeIlN8OlqWL8w9HbG+3ccegVEjOJQbkrcrW7IQMq2Uk92XjxI +PmMVIvaefqcC1poGYvS4VvEh3x64vJK1hEM4YLMKBaE/hqFtcMozi+H/8JqTCfzP +Qhnu21HIop9rSucxxnZbe9AeHz2LERpUTf3rjgOMg9PB1RUCAwEAAaOCA+0wggPp +MB8GA1UdIwQYMBaAFHSFgMBmx9833s+9KTeqAx2+7c0XMB0GA1UdDgQWBBTob1fr +hlGY65+lvlPa25SsKC777TB7BgNVHREEdDByggsqLmdpdGh1Yi5pb4IJZ2l0aHVi +LmlvghVnaXRodWJ1c2VyY29udGVudC5jb22CDnd3dy5naXRodWIuY29tggwqLmdp +dGh1Yi5jb22CFyouZ2l0aHVidXNlcmNvbnRlbnQuY29tggpnaXRodWIuY29tMD4G +A1UdIAQ3MDUwMwYGZ4EMAQICMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGln +aWNlcnQuY29tL0NQUzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUH +AwEGCCsGAQUFBwMCMIGfBgNVHR8EgZcwgZQwSKBGoESGQmh0dHA6Ly9jcmwzLmRp +Z2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbEcyVExTUlNBU0hBMjU2MjAyMENBMS0x +LmNybDBIoEagRIZCaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xv +YmFsRzJUTFNSU0FTSEEyNTYyMDIwQ0ExLTEuY3JsMIGHBggrBgEFBQcBAQR7MHkw +JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBRBggrBgEFBQcw +AoZFaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsRzJU +TFNSU0FTSEEyNTYyMDIwQ0ExLTEuY3J0MAwGA1UdEwEB/wQCMAAwggF/BgorBgEE +AdZ5AgQCBIIBbwSCAWsBaQB2AE51oydcmhDDOFts1N8/Uusd8OCOG41pwLH6ZLFi +mjnfAAABjkN89oAAAAQDAEcwRQIgU/M527Wcx0KQ3II7kCuG5WMuOHRSxKkf1xAj +JuSkyPACIQCVX0uurcIA2Ug7ipNN2S1ZygukWqJCh7hjIH0XsrXh8QB2AH1ZHhLh +eCp7HGFnfF79+NCHXBSgTpWeuQMv2Q6MLnm4AAABjkN89oEAAAQDAEcwRQIgCxpL +BDak+TWKarrCHlZn4DlqwEfAN3lvlgSo21HQuU8CIQDicrb72c0lA2suMWPWT92P +FLaRvFrFn9HVzI6Vh50YZgB3AObSMWNAd4zBEEEG13G5zsHSQPaWhIb7uocyHf0e +N45QAAABjkN89pQAAAQDAEgwRgIhAPJQX4QArFCjM0sKKzsWLmqmmU8lMhKEYR2T +ges1AQyQAiEA2Y3VhP5RG+dapcbwYgVbrTlgWzO7KE/lg1x11CVcz3QwDQYJKoZI +hvcNAQELBQADggEBAHKlvzObJBxxgyLaUNCEFf37mNFsUtXmaWvkmcfIt9V+TZ7Q +mtvjx5bsd5lqAflp/eqk4+JYpnYcKWrZfM/vMdxPQTeh/VQWewY/hYn6X/V1s2JI +MtjqEkW4aotVdWjHVvsx4rAjz5vtub/wVYgtrU8jusH3TVpT9/0AoFhKE5m2IS7M +Ig7wKR+DDxoNj4fFFluxteVNgbtwuJcb23NkBQqfHXCvQWqxXZZA4Nwl/WoGPoGG +dW5qVOc3BlhtITW53ASyhvKC7HArhj7LwQH8C/dRgn1agIHP9vVJ1NaZnPXhK98T +ohv++OO0E/F/bVGNWVnLBQ4v5PjQzRQUTGvM2mU= -----END CERTIFICATE----- )CERT"; const char github_io_chain_pem_second [] PROGMEM = R"CERT( -----BEGIN CERTIFICATE----- -MIIEvjCCA6agAwIBAgIQBtjZBNVYQ0b2ii+nVCJ+xDANBgkqhkiG9w0BAQsFADBh +MIIEyDCCA7CgAwIBAgIQDPW9BitWAvR6uFAsI8zwZjANBgkqhkiG9w0BAQsFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaME8xCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxKTAnBgNVBAMTIERpZ2lDZXJ0IFRMUyBS -U0EgU0hBMjU2IDIwMjAgQ0ExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAwUuzZUdwvN1PWNvsnO3DZuUfMRNUrUpmRh8sCuxkB+Uu3Ny5CiDt3+PE0J6a -qXodgojlEVbbHp9YwlHnLDQNLtKS4VbL8Xlfs7uHyiUDe5pSQWYQYE9XE0nw6Ddn -g9/n00tnTCJRpt8OmRDtV1F0JuJ9x8piLhMbfyOIJVNvwTRYAIuE//i+p1hJInuW -raKImxW8oHzf6VGo1bDtN+I2tIJLYrVJmuzHZ9bjPvXj1hJeRPG/cUJ9WIQDgLGB -Afr5yjK7tI4nhyfFK3TUqNaX3sNk+crOU6JWvHgXjkkDKa77SU+kFbnO8lwZV21r -eacroicgE7XQPUDTITAHk+qZ9QIDAQABo4IBgjCCAX4wEgYDVR0TAQH/BAgwBgEB -/wIBADAdBgNVHQ4EFgQUt2ui6qiqhIx56rTaD5iyxZV2ufQwHwYDVR0jBBgwFoAU -A95QNVbRTLtm8KPiGxvDl7I90VUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQG -CCsGAQUFBwMBBggrBgEFBQcDAjB2BggrBgEFBQcBAQRqMGgwJAYIKwYBBQUHMAGG -GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBABggrBgEFBQcwAoY0aHR0cDovL2Nh -Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNydDBCBgNV -HR8EOzA5MDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRH -bG9iYWxSb290Q0EuY3JsMD0GA1UdIAQ2MDQwCwYJYIZIAYb9bAIBMAcGBWeBDAEB -MAgGBmeBDAECATAIBgZngQwBAgIwCAYGZ4EMAQIDMA0GCSqGSIb3DQEBCwUAA4IB -AQCAMs5eC91uWg0Kr+HWhMvAjvqFcO3aXbMM9yt1QP6FCvrzMXi3cEsaiVi6gL3z -ax3pfs8LulicWdSQ0/1s/dCYbbdxglvPbQtaCdB73sRD2Cqk3p5BJl+7j5nL3a7h -qG+fh/50tx8bIKuxT8b1Z11dmzzp/2n3YWzW2fP9NsarA4h20ksudYbj/NhVfSbC -EXffPgK2fPOre3qGNm+499iTcc+G33Mw+nur7SpZyEKEOxEXGlLzyQ4UfaJbcme6 -ce1XR2bFuAJKZTRei9AqPCCcUZlM51Ke92sRKw2Sfh3oius2FkOH6ipjv3U/697E -A7sKPPcw7+uvTPyLNhBzPvOk +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0yMTAzMzAwMDAwMDBaFw0zMTAzMjkyMzU5NTlaMFkxCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2Jh +bCBHMiBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAMz3EGJPprtjb+2QUlbFbSd7ehJWivH0+dbn4Y+9lavyYEEV +cNsSAPonCrVXOFt9slGTcZUOakGUWzUb+nv6u8W+JDD+Vu/E832X4xT1FE3LpxDy +FuqrIvAxIhFhaZAmunjZlx/jfWardUSVc8is/+9dCopZQ+GssjoP80j812s3wWPc +3kbW20X+fSP9kOhRBx5Ro1/tSUZUfyyIxfQTnJcVPAPooTncaQwywa8WV0yUR0J8 +osicfebUTVSvQpmowQTCd5zWSOTOEeAqgJnwQ3DPP3Zr0UxJqyRewg2C/Uaoq2yT +zGJSQnWS+Jr6Xl6ysGHlHx+5fwmY6D36g39HaaECAwEAAaOCAYIwggF+MBIGA1Ud +EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHSFgMBmx9833s+9KTeqAx2+7c0XMB8G +A1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYIKwYBBQUHAQEEajBoMCQG +CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQAYIKwYBBQUHMAKG +NGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RH +Mi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29t +L0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDA9BgNVHSAENjA0MAsGCWCGSAGG/WwC +ATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgGBmeBDAECAzANBgkqhkiG +9w0BAQsFAAOCAQEAkPFwyyiXaZd8dP3A+iZ7U6utzWX9upwGnIrXWkOH7U1MVl+t +wcW1BSAuWdH/SvWgKtiwla3JLko716f2b4gp/DA/JIS7w7d7kwcsr4drdjPtAFVS +slme5LnQ89/nD/7d+MS5EHKBCQRfz5eeLjJ1js+aWNJXMX43AYGyZm0pGrFmCW3R +bpD0ufovARTFXFZkAdl9h6g4U5+LXUZtXMYnhIHUfoyMo5tS58aI7Dd8KvvwVVo4 +chDYABPPTHPbqjc1qCmBaZx2vN4Ye5DUys/vZwP9BFohFrH/6j/f3IL16/RZkiMN +JCqVJUzKoZHm1Lesh3Sz8W2jmdv51b2EQJ8HmA== -----END CERTIFICATE----- )CERT"; const char github_io_chain_pem_third [] PROGMEM = R"CERT( -----BEGIN CERTIFICATE----- -MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB -CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 -nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt -43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P -T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 -gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR -TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw -DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr -hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg -06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF -PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls -YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk -CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= -----END CERTIFICATE----- )CERT"; diff --git a/examples/GxEPD2_WiFi_CertStore_Example/GxEPD2_WiFi_CertStore_Example.ino b/examples/GxEPD2_WiFi_CertStore_Example/GxEPD2_WiFi_CertStore_Example.ino index 3fc6b5ae..1da12298 100644 --- a/examples/GxEPD2_WiFi_CertStore_Example/GxEPD2_WiFi_CertStore_Example.ino +++ b/examples/GxEPD2_WiFi_CertStore_Example/GxEPD2_WiFi_CertStore_Example.ino @@ -81,6 +81,7 @@ //#define GxEPD2_DRIVER_CLASS GxEPD2_290_BS // DEPG0290BS 128x296, SSD1680, (FPC-7519 rev.b) //#define GxEPD2_DRIVER_CLASS GxEPD2_290_M06 // GDEW029M06 128x296, UC8151D, (WFT0290CZ10) //#define GxEPD2_DRIVER_CLASS GxEPD2_290_GDEY029T94 // GDEY029T94 128x296, SSD1680, (FPC-A005 20.06.15) +//#define GxEPD2_DRIVER_CLASS GxEPD2_290_GDEY029T71H // GDEY029T71H 168x384, SSD1685, (FPC-H004 22.03.24) //#define GxEPD2_DRIVER_CLASS GxEPD2_310_GDEQ031T10 // GDEQ031T10 240x320, UC8253, (no inking, backside mark KEGMO 3100) //#define GxEPD2_DRIVER_CLASS GxEPD2_371 // GDEW0371W7 240x416, UC8171 (IL0324), (missing) //#define GxEPD2_DRIVER_CLASS GxEPD2_370_TC1 // ED037TC1 280x480, SSD1677, (ICA-FU-20 ichia 2029), Waveshare 3.7" @@ -90,6 +91,7 @@ //#define GxEPD2_DRIVER_CLASS GxEPD2_420_GYE042A87 // GYE042A87, 400x300, SSD1683 (HINK-E042-A07-FPC-A1) //#define GxEPD2_DRIVER_CLASS GxEPD2_420_SE0420NQ04 // SE0420NQ04, 400x300, UC8276C (OPM042A2_V1.0) //#define GxEPD2_DRIVER_CLASS GxEPD2_426_GDEQ0426T82 // GDEQ0426T82 480x800, SSD1677 (P426010-MF1-A) +//#define GxEPD2_DRIVER_CLASS GxEPD2_579_GDEY0579T93 // GDEY0579T93 792x272, SSD1683 (FPC-E004 22.04.13) //#define GxEPD2_DRIVER_CLASS GxEPD2_583 // GDEW0583T7 600x448, UC8159c (IL0371), (missing) //#define GxEPD2_DRIVER_CLASS GxEPD2_583_T8 // GDEW0583T8 648x480, EK79655 (GD7965), (WFT0583CZ61) //#define GxEPD2_DRIVER_CLASS GxEPD2_583_GDEQ0583T31 // GDEQ0583T31 648x480, UC8179, (P583010-MF1-B) @@ -97,6 +99,7 @@ //#define GxEPD2_DRIVER_CLASS GxEPD2_750_T7 // GDEW075T7 800x480, EK79655 (GD7965), (WFT0583CZ61) //#define GxEPD2_DRIVER_CLASS GxEPD2_750_GDEY075T7 // GDEY075T7 800x480, UC8179 (GD7965), (FPC-C001 20.08.20) //#define GxEPD2_DRIVER_CLASS GxEPD2_1020_GDEM102T91 // GDEM102T91 960x640, SSD1677, (FPC7705 REV.b) +//#define GxEPD2_DRIVER_CLASS GxEPD2_1085_GDEM1085T51 // GDEM1085T51 1360x480, JD79686AB, (FPC8617) *** needs CS2 *** //#define GxEPD2_DRIVER_CLASS GxEPD2_1160_T91 // GDEH116T91 960x640, SSD1677, (none or hidden) //#define GxEPD2_DRIVER_CLASS GxEPD2_1248 // GDEW1248T3 1304x984, UC8179, (WFT1248BZ23,WFT1248BZ24) //#define GxEPD2_DRIVER_CLASS GxEPD2_1330_GDEM133T91 // GDEM133T91 960x680, SSD1677, (FPC-7701 REV.B) @@ -113,6 +116,8 @@ //#define GxEPD2_DRIVER_CLASS GxEPD2_290_C90c // GDEM029C90 128x296, SSD1680, (FPC-7519 rev.b) //#define GxEPD2_DRIVER_CLASS GxEPD2_420c // GDEW042Z15 400x300, UC8176 (IL0398), (WFT0420CZ15) //#define GxEPD2_DRIVER_CLASS GxEPD2_420c_Z21 // GDEQ042Z21 400x300, UC8276, (hidden) +//#define GxEPD2_DRIVER_CLASS GxEPD2_420c_GDEY042Z98 // GDEY042Z98 400x300, SSD1683 (no inking) +//#define GxEPD2_DRIVER_CLASS GxEPD2_579c_GDEY0579Z93 // GDEY0579Z93 792x272, SSD1683 (FPC-E004 22.04.13) //#define GxEPD2_DRIVER_CLASS GxEPD2_583c // GDEW0583Z21 600x448, UC8159c (IL0371), (missing) //#define GxEPD2_DRIVER_CLASS GxEPD2_583c_Z83 // GDEW0583Z83 648x480, EK79655 (GD7965), (WFT0583CZ61) //#define GxEPD2_DRIVER_CLASS GxEPD2_583c_GDEQ0583Z31 // GDEQ0583Z31 648x480, UC8179C, @@ -123,15 +128,20 @@ //#define GxEPD2_DRIVER_CLASS GxEPD2_1248c // GDEY1248Z51 1304x984, UC8179, (WFT1248BZ23,WFT1248BZ24) //#define GxEPD2_DRIVER_CLASS GxEPD2_1330c_GDEM133Z91 // GDEM133Z91 960x680, SSD1677 (FPC-7701 REV.B) // 4-color e-paper +//#define GxEPD2_DRIVER_CLASS GxEPD2_213c_GDEY0213F51 // GDEY0213F51 122x250, JD79661 (FPC-A002 20.04.08) //#define GxEPD2_DRIVER_CLASS GxEPD2_266c_GDEY0266F51H // GDEY0266F51H 184x360, JD79667 (FPC-H006 22.04.02) //#define GxEPD2_DRIVER_CLASS GxEPD2_290c_GDEY029F51H // GDEY029F51H 168x384, JD79667 (FPC-H004 22.03.24) //#define GxEPD2_DRIVER_CLASS GxEPD2_300c // Waveshare 3.00" 4-color //#define GxEPD2_DRIVER_CLASS GxEPD2_420c_GDEY0420F51 // GDEY0420F51 400x300, HX8717 (no inking) //#define GxEPD2_DRIVER_CLASS GxEPD2_437c // Waveshare 4.37" 4-color +//#define GxEPD2_DRIVER_CLASS GxEPD2_0579c_GDEY0579F51 // GDEY0579F51 792x272, HX8717 (FPC-E009 22.09.25) +//#define GxEPD2_DRIVER_CLASS GxEPD2_1160c_GDEY116F51 // GDEY116F51 960x640, SSD2677, (FPC-K012 23.09.27) // 7-color e-paper //#define GxEPD2_DRIVER_CLASS GxEPD2_565c // Waveshare 5.65" 7-color +//#define GxEPD2_DRIVER_CLASS GxEPD2_565c_GDEP0565D90 // GDEP0565D90 600x448 7-color (E219454, AB1024-EGA AC0750TC1) //#define GxEPD2_DRIVER_CLASS GxEPD2_730c_GDEY073D46 // GDEY073D46 800x480 7-color, (N-FPC-001 2021.11.26) //#define GxEPD2_DRIVER_CLASS GxEPD2_730c_ACeP_730 // Waveshare 7.3" 7-color +//#define GxEPD2_DRIVER_CLASS GxEPD2_730c_GDEP073E01 // GDEP073E01 800x480 7-color, (E350911HF 94V-0 F-6 ROHS 24141) // 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 @@ -159,9 +169,7 @@ #define IS_GxEPD2_1248c(x) IS_GxEPD(GxEPD2_1248c_IS_, x) #if defined (ESP8266) -#define MAX_DISPLAY_BUFFER_SIZE (81920ul-34000ul-38000ul) // ~34000 base use, WiFiClientSecure seems to need about 38k more to work (with CertStore) -// for site people.math.sc.edu an increased RX buffer is required for timely decodes, client.setBufferSizes(8192, 4096); // may help. needs more space: -//#define MAX_DISPLAY_BUFFER_SIZE (81920ul-34000ul-38000ul) // ~34000 base use, WiFiClientSecure seems to need about 38k more to work (with CertStore) +#define MAX_DISPLAY_BUFFER_SIZE (81920ul-34000ul-40000ul) // ~34000 base use, WiFiClientSecure seems to need about 40k more to work (with CertStore) #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) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS) @@ -217,40 +225,44 @@ GxEPD2_DISPLAY_CLASS < GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS) > di #include #include #include +#else +#include #endif #include #include -#if defined (ESP8266) -// A single, global CertStore which can be used by all -// connections. Needs to stay live the entire time any of -// the WiFiClientBearSSLs are present. -BearSSL::CertStore certStore; -const char* certificate_rawcontent = 0; -#else -#include "GxEPD2_github_raw_certs.h" -const char* certificate_rawcontent = cert_DigiCert_TLS_RSA_SHA256_2020_CA1; -#pragma GCC warning "no CertStore for this target" -//#warning "no CertStore for this target" -#endif - const char* ssid = "........"; const char* password = "........"; const int httpPort = 80; const int httpsPort = 443; -const char* fp_rawcontent = "97:D8:C5:70:0F:12:24:6C:88:BC:FA:06:7E:8C:A7:4D:A8:62:67:28"; // SHA-1 as of 12.04.2024 +// note: the certificates have been moved to a separate header file, as R"CERT( destroys IDE Auto Format capability +#include "GxEPD2_github_raw_certs.h" +const char* certificate_rawcontent = github_io_pem; // ok, should work until Fri, 14 Mar 2025 23:59:59 GMT const char* host_rawcontent = "raw.githubusercontent.com"; const char* path_rawcontent = "/ZinggJM/GxEPD2/master/extras/bitmaps/"; +const char* path_workcontent = "/ZinggJM/GxEPD2/work_in_progress/extras/bitmaps/"; const char* path_prenticedavid = "/prenticedavid/MCUFRIEND_kbv/master/extras/bitmaps/"; const char* path_waveshare_c = "/waveshare/e-Paper/master/RaspberryPi_JetsonNano/c/pic/"; const char* path_waveshare_py = "/waveshare/e-Paper/master/RaspberryPi_JetsonNano/python/pic/"; -const char* path_Burkardt = "https://people.math.sc.edu/Burkardt/data/bmp/"; +const char* fp_rawcontent = "97:D8:C5:70:0F:12:24:6C:88:BC:FA:06:7E:8C:A7:4D:A8:62:67:28"; // SHA-1 as of 12.04.2024 + +//const char* path_Burkardt = "https://people.math.sc.edu/Burkardt/data/bmp/"; +const char* path_Burkardt = "/Burkardt/data/bmp/"; const char* fp_people_cas = "99:6C:B7:72:CC:ED:96:82:49:2D:B2:78:71:00:14:BA:02:8E:FF:BF"; const char* host_people_cas_sc_edu = "people.math.sc.edu"; +#if defined (ESP8266) +// A single, global CertStore which can be used by all +// connections. Needs to stay live the entire time any of +// the WiFiClientBearSSLs are present. +BearSSL::CertStore certStore; +#else +#pragma GCC warning "no CertStore for this target" +#endif + // note that BMP bitmaps are drawn at physical position in physical orientation of the screen void showBitmapFrom_HTTP(const char* host, const char* path, const char* filename, int16_t x, int16_t y, bool with_color = true); void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filename, const char* fingerprint, int16_t x, int16_t y, bool with_color = true, @@ -267,7 +279,8 @@ void setup() Serial.println(); Serial.println("GxEPD2_WiFi_Example"); - display.init(115200); + //display.init(115200); // default 10ms reset pulse, e.g. for bare panels with DESPI-C02 + display.init(115200, true, 2, false); // USE THIS for Waveshare boards with "clever" reset circuit, 2ms reset pulse #ifdef REMAP_SPI_FOR_WAVESHARE_ESP32_DRIVER_BOARD SPI.end(); // release standard SPI pins, e.g. SCK(18), MISO(19), MOSI(23), SS(5) @@ -283,26 +296,11 @@ void setup() delay(1000); } -#ifdef RE_INIT_NEEDED - WiFi.persistent(true); + Serial.println(); WiFi.mode(WIFI_STA); // switch off AP - WiFi.setAutoConnect(true); - WiFi.setAutoReconnect(true); - WiFi.disconnect(); -#endif - - if (!WiFi.getAutoConnect() || ( WiFi.getMode() != WIFI_STA) || ((WiFi.SSID() != ssid) && String(ssid) != "........")) - { - Serial.println(); - Serial.print("WiFi.getAutoConnect()="); - Serial.println(WiFi.getAutoConnect()); - Serial.print("WiFi.SSID()="); - Serial.println(WiFi.SSID()); - WiFi.mode(WIFI_STA); // switch off AP - Serial.print("Connecting to "); - Serial.println(ssid); - WiFi.begin(ssid, password); - } + Serial.print("Connecting to "); + Serial.println(ssid); + WiFi.begin(ssid, password); int ConnectTimeout = 60; // 30 seconds while (WiFi.status() != WL_CONNECTED) { @@ -388,8 +386,8 @@ void drawBitmaps_other() { int16_t w2 = display.width() / 2; int16_t h2 = display.height() / 2; - showBitmapFrom_HTTP("www.packescape.com", "/img/assets/", "IniciMenusTV2.bmp", w2 - 200, h2 - 150, false); - delay(2000); + //showBitmapFrom_HTTP("www.packescape.com", "/img/assets/", "IniciMenusTV2.bmp", w2 - 200, h2 - 150, false); + //delay(2000); showBitmapFrom_HTTP("www.squix.org", "/blog/wunderground/", "chanceflurries.bmp", w2 - 50, h2 - 50, false); delay(2000); showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "betty_1.bmp", fp_rawcontent, w2 - 100, h2 - 160); @@ -419,10 +417,11 @@ void drawBitmaps_other() void drawBitmaps_test() { #if defined (ESP8266) - showBitmapFrom_HTTPS_Buffered(host_people_cas_sc_edu, path_Burkardt, "lena.bmp", 0, 0, 0, true, 0); // connection ok with CertStore + //showBitmapFrom_HTTPS(host_people_cas_sc_edu, path_Burkardt, "lena.bmp", 0, 0, 0, true, 0); // connection not ok with CertStore, needs be updated #endif int16_t w2 = display.width() / 2; int16_t h2 = display.height() / 2; + //showBitmapFrom_HTTPS(host_rawcontent, path_workcontent, "z0gs/screenshot.bmp", fp_rawcontent, 0, 0); delay(2000); return; showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "betty_4.bmp", fp_rawcontent, w2 - 102, h2 - 126); delay(2000); showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "output5.bmp", fp_rawcontent, 0, 0); @@ -515,13 +514,18 @@ void drawBitmapsBuffered_7C() delay(2000); showBitmapFrom_HTTPS_Buffered(host_rawcontent, path_waveshare_py, "N-Color1.bmp", fp_rawcontent, 0, 0); delay(2000); + + showBitmapFrom_HTTPS_Buffered(host_rawcontent, path_rawcontent, "displayed_bmp_small_but_padded.bmp", fp_rawcontent, 0, 0); + delay(2000); + showBitmapFrom_HTTPS_Buffered(host_rawcontent, path_rawcontent, "displayed_bmp_large.bmp", fp_rawcontent, 0, 0); + delay(2000); } } void drawBitmapsBuffered_test() { #if defined (ESP8266) - showBitmapFrom_HTTPS_Buffered(host_people_cas_sc_edu, path_Burkardt, "lena.bmp", 0, 0, 0, true, 0); // connection ok with CertStore + //showBitmapFrom_HTTPS_Buffered(host_people_cas_sc_edu, path_Burkardt, "lena.bmp", 0, 0, 0, true, 0); // connection not ok with CertStore, needs be updated #endif int16_t w2 = display.width() / 2; int16_t h2 = display.height() / 2; @@ -530,6 +534,7 @@ void drawBitmapsBuffered_test() } static const uint16_t input_buffer_pixels = 800; // may affect performance +//static const uint16_t input_buffer_pixels = 960; // may affect performance static const uint16_t max_row_width = 1872; // for up to 7.8" display 1872x1404 static const uint16_t max_palette_pixels = 256; // for depth <= 8 @@ -570,10 +575,8 @@ void showBitmapFrom_HTTP(const char* host, const char* path, const char* filenam { connection_ok = line.startsWith("HTTP/1.1 200 OK"); if (connection_ok) Serial.println(line); - //if (!connection_ok) Serial.println(line); } if (!connection_ok) Serial.println(line); - //Serial.println(line); if (line == "\r") { Serial.println("headers received"); @@ -603,7 +606,7 @@ void showBitmapFrom_HTTP(const char* host, const char* path, const char* filenam Serial.print("Image size: "); Serial.print(width); Serial.print('x'); - Serial.println(height); + Serial.println(abs(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; @@ -628,7 +631,6 @@ void showBitmapFrom_HTTP(const char* host, const char* path, const char* filenam if (depth <= 8) { if (depth < 8) bitmask >>= depth; - //bytes_read += skip(client, 54 - bytes_read); //palette is always @ 54 bytes_read += skip(client, imageOffset - (4 << depth) - bytes_read); // 54 for regular, diff for colorsimportant for (uint16_t pn = 0; pn < (1 << depth); pn++) { @@ -647,7 +649,6 @@ void showBitmapFrom_HTTP(const char* host, const char* path, const char* filenam } display.clearScreen(); uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset; - //Serial.print("skip "); Serial.println(rowPosition - bytes_read); bytes_read += skip(client, rowPosition - bytes_read); for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line { @@ -680,6 +681,7 @@ void showBitmapFrom_HTTP(const char* host, const char* path, const char* filenam in_bytes = got; in_remain -= got; bytes_read += got; + in_idx = 0; } if (!connection_ok) { @@ -809,10 +811,8 @@ void drawBitmapFrom_HTTP_ToBuffer(const char* host, const char* path, const char { connection_ok = line.startsWith("HTTP/1.1 200 OK"); if (connection_ok) Serial.println(line); - //if (!connection_ok) Serial.println(line); } if (!connection_ok) Serial.println(line); - //Serial.println(line); if (line == "\r") { Serial.println("headers received"); @@ -842,7 +842,7 @@ void drawBitmapFrom_HTTP_ToBuffer(const char* host, const char* path, const char Serial.print("Image size: "); Serial.print(width); Serial.print('x'); - Serial.println(height); + Serial.println(abs(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; @@ -867,7 +867,6 @@ void drawBitmapFrom_HTTP_ToBuffer(const char* host, const char* path, const char if (depth <= 8) { if (depth < 8) bitmask >>= depth; - //bytes_read += skip(client, 54 - bytes_read); //palette is always @ 54 bytes_read += skip(client, imageOffset - (4 << depth) - bytes_read); // 54 for regular, diff for colorsimportant for (uint16_t pn = 0; pn < (1 << depth); pn++) { @@ -888,7 +887,6 @@ void drawBitmapFrom_HTTP_ToBuffer(const char* host, const char* path, const char } } uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset; - //Serial.print("skip "); Serial.println(rowPosition - bytes_read); bytes_read += skip(client, rowPosition - bytes_read); for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line { @@ -919,6 +917,7 @@ void drawBitmapFrom_HTTP_ToBuffer(const char* host, const char* path, const char in_bytes = got; in_remain -= got; bytes_read += got; + in_idx = 0; } if (!connection_ok) { @@ -1032,8 +1031,9 @@ void showBitmapFrom_HTTP_Buffered(const char* host, const char* path, const char void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filename, const char* fingerprint, int16_t x, int16_t y, bool with_color, const char* certificate) { // Use WiFiClientSecure class to create TLS connection -#if defined (ESP8266) +#if defined (ESP8266) || defined(ARDUINO_RASPBERRY_PI_PICO_W) BearSSL::WiFiClientSecure client; + //BearSSL::X509List cert(certificate ? certificate : certificate_rawcontent); #else WiFiClientSecure client; #endif @@ -1044,9 +1044,7 @@ void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filena if ((x >= display.epd2.WIDTH) || (y >= display.epd2.HEIGHT)) return; Serial.println(); Serial.print("downloading file \""); Serial.print(filename); Serial.println("\""); Serial.print("connecting to "); Serial.println(host); -#if defined (ESP8266) - //client.setBufferSizes(4096, 4096); // required - client.setBufferSizes(8192, 4096); // may help for some sites +#if defined (ESP8266) || defined(ARDUINO_RASPBERRY_PI_PICO_W) client.setCertStore(&certStore); #elif defined (ESP32) if (certificate) client.setCACert(certificate); @@ -1070,10 +1068,8 @@ void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filena { connection_ok = line.startsWith("HTTP/1.1 200 OK"); if (connection_ok) Serial.println(line); - //if (!connection_ok) Serial.println(line); } if (!connection_ok) Serial.println(line); - //Serial.println(line); if (line == "\r") { Serial.println("headers received"); @@ -1082,7 +1078,6 @@ void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filena } if (!connection_ok) return; // Parse BMP header - //if (read16(client) == 0x4D42) // BMP signature uint16_t signature = 0; for (int16_t i = 0; i < 50; i++) { @@ -1102,8 +1097,6 @@ void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filena uint16_t depth = read16(client); // bits per pixel uint32_t format = read32(client); uint32_t bytes_read = 7 * 4 + 3 * 2; // read so far - //Serial.print("planes: "); Serial.println(planes); - //Serial.print("format: "); Serial.println(format); if ((planes == 1) && ((format == 0) || (format == 3))) // uncompressed is handled, 565 also { Serial.print("File size: "); Serial.println(fileSize); @@ -1113,7 +1106,7 @@ void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filena Serial.print("Image size: "); Serial.print(width); Serial.print('x'); - Serial.println(height); + Serial.println(abs(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; @@ -1191,6 +1184,7 @@ void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filena in_bytes = got; in_remain -= got; bytes_read += got; + in_idx = 0; } if (!connection_ok) { @@ -1293,6 +1287,7 @@ void drawBitmapFrom_HTTPS_ToBuffer(const char* host, const char* path, const cha // Use WiFiClientSecure class to create TLS connection #if defined (ESP8266) BearSSL::WiFiClientSecure client; + //BearSSL::X509List cert(certificate ? certificate : certificate_rawcontent); #else WiFiClientSecure client; #endif @@ -1305,8 +1300,6 @@ void drawBitmapFrom_HTTPS_ToBuffer(const char* host, const char* path, const cha display.fillScreen(GxEPD_WHITE); Serial.print("connecting to "); Serial.println(host); #if defined (ESP8266) - //client.setBufferSizes(4096, 4096); // required - client.setBufferSizes(8192, 4096); // may help client.setCertStore(&certStore); #elif defined (ESP32) if (certificate) client.setCACert(certificate); @@ -1330,10 +1323,8 @@ void drawBitmapFrom_HTTPS_ToBuffer(const char* host, const char* path, const cha { connection_ok = line.startsWith("HTTP/1.1 200 OK"); if (connection_ok) Serial.println(line); - //if (!connection_ok) Serial.println(line); } if (!connection_ok) Serial.println(line); - //Serial.println(line); if (line == "\r") { Serial.println("headers received"); @@ -1342,18 +1333,12 @@ void drawBitmapFrom_HTTPS_ToBuffer(const char* host, const char* path, const cha } if (!connection_ok) return; // Parse BMP header - //if (read16(client) == 0x4D42) // BMP signature uint16_t signature = 0; for (int16_t i = 0; i < 50; i++) { if (!client.available()) delay(100); else signature = read16(client); - //Serial.print("signature: 0x"); Serial.println(signature, HEX); - if (signature == 0x4D42) - { - //Serial.print("signature wait loops: "); Serial.println(i); - break; - } + if (signature == 0x4D42) break; } if (signature == 0x4D42) // BMP signature { @@ -1376,7 +1361,7 @@ void drawBitmapFrom_HTTPS_ToBuffer(const char* host, const char* path, const cha Serial.print("Image size: "); Serial.print(width); Serial.print('x'); - Serial.println(height); + Serial.println(abs(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; @@ -1453,6 +1438,7 @@ void drawBitmapFrom_HTTPS_ToBuffer(const char* host, const char* path, const cha in_bytes = got; in_remain -= got; bytes_read += got; + in_idx = 0; } if (!connection_ok) { @@ -1596,7 +1582,7 @@ uint32_t skip(BearSSL::WiFiClientSecure& client, int32_t bytes) client.read(); remain--; } - else delay(1); + else delay(10); if (millis() - start > 2000) break; // don't hang forever } return bytes - remain; @@ -1614,7 +1600,7 @@ uint32_t read8n(BearSSL::WiFiClientSecure& client, uint8_t* buffer, int32_t byte *buffer++ = uint8_t(v); remain--; } - else delay(1); + else delay(10); if (millis() - start > 2000) break; // don't hang forever } return bytes - remain; diff --git a/examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Example.ino b/examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Example.ino index cbb63908..4e4acd8a 100644 --- a/examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Example.ino +++ b/examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Example.ino @@ -76,6 +76,7 @@ //#define GxEPD2_DRIVER_CLASS GxEPD2_290_BS // DEPG0290BS 128x296, SSD1680, (FPC-7519 rev.b) //#define GxEPD2_DRIVER_CLASS GxEPD2_290_M06 // GDEW029M06 128x296, UC8151D, (WFT0290CZ10) //#define GxEPD2_DRIVER_CLASS GxEPD2_290_GDEY029T94 // GDEY029T94 128x296, SSD1680, (FPC-A005 20.06.15) +//#define GxEPD2_DRIVER_CLASS GxEPD2_290_GDEY029T71H // GDEY029T71H 168x384, SSD1685, (FPC-H004 22.03.24) //#define GxEPD2_DRIVER_CLASS GxEPD2_310_GDEQ031T10 // GDEQ031T10 240x320, UC8253, (no inking, backside mark KEGMO 3100) //#define GxEPD2_DRIVER_CLASS GxEPD2_371 // GDEW0371W7 240x416, UC8171 (IL0324), (missing) //#define GxEPD2_DRIVER_CLASS GxEPD2_370_TC1 // ED037TC1 280x480, SSD1677, (ICA-FU-20 ichia 2029), Waveshare 3.7" @@ -85,6 +86,7 @@ //#define GxEPD2_DRIVER_CLASS GxEPD2_420_GYE042A87 // GYE042A87, 400x300, SSD1683 (HINK-E042-A07-FPC-A1) //#define GxEPD2_DRIVER_CLASS GxEPD2_420_SE0420NQ04 // SE0420NQ04, 400x300, UC8276C (OPM042A2_V1.0) //#define GxEPD2_DRIVER_CLASS GxEPD2_426_GDEQ0426T82 // GDEQ0426T82 480x800, SSD1677 (P426010-MF1-A) +//#define GxEPD2_DRIVER_CLASS GxEPD2_579_GDEY0579T93 // GDEY0579T93 792x272, SSD1683 (FPC-E004 22.04.13) //#define GxEPD2_DRIVER_CLASS GxEPD2_583 // GDEW0583T7 600x448, UC8159c (IL0371), (missing) //#define GxEPD2_DRIVER_CLASS GxEPD2_583_T8 // GDEW0583T8 648x480, EK79655 (GD7965), (WFT0583CZ61) //#define GxEPD2_DRIVER_CLASS GxEPD2_583_GDEQ0583T31 // GDEQ0583T31 648x480, UC8179, (P583010-MF1-B) @@ -92,6 +94,7 @@ //#define GxEPD2_DRIVER_CLASS GxEPD2_750_T7 // GDEW075T7 800x480, EK79655 (GD7965), (WFT0583CZ61) //#define GxEPD2_DRIVER_CLASS GxEPD2_750_GDEY075T7 // GDEY075T7 800x480, UC8179 (GD7965), (FPC-C001 20.08.20) //#define GxEPD2_DRIVER_CLASS GxEPD2_1020_GDEM102T91 // GDEM102T91 960x640, SSD1677, (FPC7705 REV.b) +//#define GxEPD2_DRIVER_CLASS GxEPD2_1085_GDEM1085T51 // GDEM1085T51 1360x480, JD79686AB, (FPC8617) *** needs CS2 *** //#define GxEPD2_DRIVER_CLASS GxEPD2_1160_T91 // GDEH116T91 960x640, SSD1677, (none or hidden) //#define GxEPD2_DRIVER_CLASS GxEPD2_1248 // GDEW1248T3 1304x984, UC8179, (WFT1248BZ23,WFT1248BZ24) //#define GxEPD2_DRIVER_CLASS GxEPD2_1330_GDEM133T91 // GDEM133T91 960x680, SSD1677, (FPC-7701 REV.B) @@ -108,6 +111,8 @@ //#define GxEPD2_DRIVER_CLASS GxEPD2_290_C90c // GDEM029C90 128x296, SSD1680, (FPC-7519 rev.b) //#define GxEPD2_DRIVER_CLASS GxEPD2_420c // GDEW042Z15 400x300, UC8176 (IL0398), (WFT0420CZ15) //#define GxEPD2_DRIVER_CLASS GxEPD2_420c_Z21 // GDEQ042Z21 400x300, UC8276, (hidden) +//#define GxEPD2_DRIVER_CLASS GxEPD2_420c_GDEY042Z98 // GDEY042Z98 400x300, SSD1683 (no inking) +//#define GxEPD2_DRIVER_CLASS GxEPD2_579c_GDEY0579Z93 // GDEY0579Z93 792x272, SSD1683 (FPC-E004 22.04.13) //#define GxEPD2_DRIVER_CLASS GxEPD2_583c // GDEW0583Z21 600x448, UC8159c (IL0371), (missing) //#define GxEPD2_DRIVER_CLASS GxEPD2_583c_Z83 // GDEW0583Z83 648x480, EK79655 (GD7965), (WFT0583CZ61) //#define GxEPD2_DRIVER_CLASS GxEPD2_583c_GDEQ0583Z31 // GDEQ0583Z31 648x480, UC8179C, @@ -118,15 +123,20 @@ //#define GxEPD2_DRIVER_CLASS GxEPD2_1248c // GDEY1248Z51 1304x984, UC8179, (WFT1248BZ23,WFT1248BZ24) //#define GxEPD2_DRIVER_CLASS GxEPD2_1330c_GDEM133Z91 // GDEM133Z91 960x680, SSD1677 (FPC-7701 REV.B) // 4-color e-paper +//#define GxEPD2_DRIVER_CLASS GxEPD2_213c_GDEY0213F51 // GDEY0213F51 122x250, JD79661 (FPC-A002 20.04.08) //#define GxEPD2_DRIVER_CLASS GxEPD2_266c_GDEY0266F51H // GDEY0266F51H 184x360, JD79667 (FPC-H006 22.04.02) //#define GxEPD2_DRIVER_CLASS GxEPD2_290c_GDEY029F51H // GDEY029F51H 168x384, JD79667 (FPC-H004 22.03.24) //#define GxEPD2_DRIVER_CLASS GxEPD2_300c // Waveshare 3.00" 4-color //#define GxEPD2_DRIVER_CLASS GxEPD2_420c_GDEY0420F51 // GDEY0420F51 400x300, HX8717 (no inking) //#define GxEPD2_DRIVER_CLASS GxEPD2_437c // Waveshare 4.37" 4-color +//#define GxEPD2_DRIVER_CLASS GxEPD2_0579c_GDEY0579F51 // GDEY0579F51 792x272, HX8717 (FPC-E009 22.09.25) +//#define GxEPD2_DRIVER_CLASS GxEPD2_1160c_GDEY116F51 // GDEY116F51 960x640, SSD2677, (FPC-K012 23.09.27) // 7-color e-paper //#define GxEPD2_DRIVER_CLASS GxEPD2_565c // Waveshare 5.65" 7-color +//#define GxEPD2_DRIVER_CLASS GxEPD2_565c_GDEP0565D90 // GDEP0565D90 600x448 7-color (E219454, AB1024-EGA AC0750TC1) //#define GxEPD2_DRIVER_CLASS GxEPD2_730c_GDEY073D46 // GDEY073D46 800x480 7-color, (N-FPC-001 2021.11.26) //#define GxEPD2_DRIVER_CLASS GxEPD2_730c_ACeP_730 // Waveshare 7.3" 7-color +//#define GxEPD2_DRIVER_CLASS GxEPD2_730c_GDEP073E01 // GDEP073E01 800x480 7-color, (E350911HF 94V-0 F-6 ROHS 24141) // 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 @@ -239,6 +249,8 @@ GxEPD2_DISPLAY_CLASS displ #if defined (ESP8266) #include +#else +#include #endif #include @@ -255,13 +267,9 @@ const int httpsPort = 443; const char* certificate_rawcontent = github_io_pem; // ok, should work until Fri, 14 Mar 2025 23:59:59 GMT -//const char* certificate_rawcontent = cert_DigiCert_TLS_RSA_SHA256_2020_CA1; // not ok, should work until 2031-04-13 23:59:59 -//const char* certificate_rawcontent = github_io_chain_pem_first; // not ok, should work until Fri, 14 Mar 2025 23:59:59 GMT -//const char* certificate_rawcontent = github_io_chain_pem_second; // ok, should work until Fri, 14 Mar 2025 23:59:59 GMT -//const char* certificate_rawcontent = github_io_chain_pem_third; // not ok, should work until Fri, 14 Mar 2025 23:59:59 GMT - const char* host_rawcontent = "raw.githubusercontent.com"; const char* path_rawcontent = "/ZinggJM/GxEPD2/master/extras/bitmaps/"; +const char* path_workcontent = "/ZinggJM/GxEPD2/work_in_progress/extras/bitmaps/"; const char* path_prenticedavid = "/prenticedavid/MCUFRIEND_kbv/master/extras/bitmaps/"; const char* path_waveshare_c = "/waveshare/e-Paper/master/RaspberryPi_JetsonNano/c/pic/"; const char* path_waveshare_py = "/waveshare/e-Paper/master/RaspberryPi_JetsonNano/python/pic/"; @@ -310,32 +318,19 @@ void setup() //display.init(115200); // default 10ms reset pulse, e.g. for bare panels with DESPI-C02 display.init(115200, true, 2, false); // USE THIS for Waveshare boards with "clever" reset circuit, 2ms reset pulse -#if defined (ESP8266) || defined (ESP32) -#ifdef RE_INIT_NEEDED - WiFi.persistent(true); - WiFi.mode(WIFI_STA); // switch off AP - WiFi.setAutoConnect(true); - WiFi.setAutoReconnect(true); - WiFi.disconnect(); -#endif - - if (!WiFi.getAutoConnect() || ( WiFi.getMode() != WIFI_STA) || ((WiFi.SSID() != ssid) && String(ssid) != "........")) + if (display.pages() > 1) { + delay(100); Serial.println(); - Serial.print("WiFi.getAutoConnect() = "); - Serial.println(WiFi.getAutoConnect()); - Serial.print("WiFi.SSID() = "); - Serial.println(WiFi.SSID()); - WiFi.mode(WIFI_STA); // switch off AP - Serial.print("Connecting to "); - Serial.println(ssid); - WiFi.begin(ssid, password); + Serial.print("pages = "); Serial.print(display.pages()); Serial.print(" page height = "); Serial.println(display.pageHeight()); + delay(1000); } -#else - Serial.print("Connecting to "); - Serial.println(ssid); - WiFi.begin(ssid, password); -#endif + + Serial.println(); + WiFi.mode(WIFI_STA); // switch off AP + Serial.print("Connecting to "); + Serial.println(ssid); + WiFi.begin(ssid, password); int ConnectTimeout = 60; // 30 seconds while (WiFi.status() != WL_CONNECTED) { @@ -439,6 +434,7 @@ void drawBitmaps_test() { int16_t w2 = display.width() / 2; int16_t h2 = display.height() / 2; + //showBitmapFrom_HTTPS(host_rawcontent, path_workcontent, "z0gs/screenshot.bmp", fp_rawcontent, 0, 0); delay(2000); return; showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "betty_4.bmp", fp_rawcontent, w2 - 102, h2 - 126); delay(2000); showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "output5.bmp", fp_rawcontent, 0, 0); @@ -548,6 +544,7 @@ void drawBitmapsBuffered_test() } static const uint16_t input_buffer_pixels = 800; // may affect performance +//static const uint16_t input_buffer_pixels = 960; // may affect performance static const uint16_t max_row_width = 1872; // for up to 7.8" display 1872x1404 static const uint16_t max_palette_pixels = 256; // for depth <= 8 @@ -588,10 +585,8 @@ void showBitmapFrom_HTTP(const char* host, const char* path, const char* filenam { connection_ok = line.startsWith("HTTP/1.1 200 OK"); if (connection_ok) Serial.println(line); - //if (!connection_ok) Serial.println(line); } if (!connection_ok) Serial.println(line); - //Serial.println(line); if (line == "\r") { Serial.println("headers received"); @@ -621,7 +616,7 @@ void showBitmapFrom_HTTP(const char* host, const char* path, const char* filenam Serial.print("Image size: "); Serial.print(width); Serial.print('x'); - Serial.println(height); + Serial.println(abs(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; @@ -646,7 +641,6 @@ void showBitmapFrom_HTTP(const char* host, const char* path, const char* filenam if (depth <= 8) { if (depth < 8) bitmask >>= depth; - //bytes_read += skip(client, 54 - bytes_read); //palette is always @ 54 bytes_read += skip(client, imageOffset - (4 << depth) - bytes_read); // 54 for regular, diff for colorsimportant for (uint16_t pn = 0; pn < (1 << depth); pn++) { @@ -665,7 +659,6 @@ void showBitmapFrom_HTTP(const char* host, const char* path, const char* filenam } display.clearScreen(); uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset; - //Serial.print("skip "); Serial.println(rowPosition - bytes_read); bytes_read += skip(client, rowPosition - bytes_read); for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line { @@ -698,6 +691,7 @@ void showBitmapFrom_HTTP(const char* host, const char* path, const char* filenam in_bytes = got; in_remain -= got; bytes_read += got; + in_idx = 0; } if (!connection_ok) { @@ -827,10 +821,8 @@ void drawBitmapFrom_HTTP_ToBuffer(const char* host, const char* path, const char { connection_ok = line.startsWith("HTTP/1.1 200 OK"); if (connection_ok) Serial.println(line); - //if (!connection_ok) Serial.println(line); } if (!connection_ok) Serial.println(line); - //Serial.println(line); if (line == "\r") { Serial.println("headers received"); @@ -860,7 +852,7 @@ void drawBitmapFrom_HTTP_ToBuffer(const char* host, const char* path, const char Serial.print("Image size: "); Serial.print(width); Serial.print('x'); - Serial.println(height); + Serial.println(abs(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; @@ -885,7 +877,6 @@ void drawBitmapFrom_HTTP_ToBuffer(const char* host, const char* path, const char if (depth <= 8) { if (depth < 8) bitmask >>= depth; - //bytes_read += skip(client, 54 - bytes_read); //palette is always @ 54 bytes_read += skip(client, imageOffset - (4 << depth) - bytes_read); // 54 for regular, diff for colorsimportant for (uint16_t pn = 0; pn < (1 << depth); pn++) { @@ -906,7 +897,6 @@ void drawBitmapFrom_HTTP_ToBuffer(const char* host, const char* path, const char } } uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset; - //Serial.print("skip "); Serial.println(rowPosition - bytes_read); bytes_read += skip(client, rowPosition - bytes_read); for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line { @@ -937,6 +927,7 @@ void drawBitmapFrom_HTTP_ToBuffer(const char* host, const char* path, const char in_bytes = got; in_remain -= got; bytes_read += got; + in_idx = 0; } if (!connection_ok) { @@ -1064,8 +1055,6 @@ void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filena Serial.println(); Serial.print("downloading file \""); Serial.print(filename); Serial.println("\""); Serial.print("connecting to "); Serial.println(host); #if defined (ESP8266) || defined(ARDUINO_RASPBERRY_PI_PICO_W) - client.setBufferSizes(4096, 4096); // required - //client.setBufferSizes(8192, 4096); // may help for some sites if (certificate) client.setTrustAnchors(&cert); else if (fingerprint) client.setFingerprint(fingerprint); else client.setInsecure(); @@ -1091,10 +1080,8 @@ void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filena { connection_ok = line.startsWith("HTTP/1.1 200 OK"); if (connection_ok) Serial.println(line); - //if (!connection_ok) Serial.println(line); } if (!connection_ok) Serial.println(line); - //Serial.println(line); if (line == "\r") { Serial.println("headers received"); @@ -1103,13 +1090,11 @@ void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filena } if (!connection_ok) return; // Parse BMP header - //if (read16(client) == 0x4D42) // BMP signature uint16_t signature = 0; for (int16_t i = 0; i < 50; i++) { if (!client.available()) delay(100); else signature = read16(client); - //Serial.print("signature: 0x"); Serial.println(signature, HEX); if (signature == 0x4D42) break; } if (signature == 0x4D42) // BMP signature @@ -1133,7 +1118,7 @@ void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filena Serial.print("Image size: "); Serial.print(width); Serial.print('x'); - Serial.println(height); + Serial.println(abs(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; @@ -1211,6 +1196,7 @@ void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filena in_bytes = got; in_remain -= got; bytes_read += got; + in_idx = 0; } if (!connection_ok) { @@ -1326,8 +1312,6 @@ void drawBitmapFrom_HTTPS_ToBuffer(const char* host, const char* path, const cha display.fillScreen(GxEPD_WHITE); Serial.print("connecting to "); Serial.println(host); #if defined (ESP8266) - client.setBufferSizes(4096, 4096); // required - //client.setBufferSizes(8192, 4096); // may help for some sites if (certificate) client.setTrustAnchors(&cert); else if (fingerprint) client.setFingerprint(fingerprint); else client.setInsecure(); @@ -1353,10 +1337,8 @@ void drawBitmapFrom_HTTPS_ToBuffer(const char* host, const char* path, const cha { connection_ok = line.startsWith("HTTP/1.1 200 OK"); if (connection_ok) Serial.println(line); - //if (!connection_ok) Serial.println(line); } if (!connection_ok) Serial.println(line); - //Serial.println(line); if (line == "\r") { Serial.println("headers received"); @@ -1365,17 +1347,12 @@ void drawBitmapFrom_HTTPS_ToBuffer(const char* host, const char* path, const cha } if (!connection_ok) return; // Parse BMP header - //if (read16(client) == 0x4D42) // BMP signature uint16_t signature = 0; for (int16_t i = 0; i < 50; i++) { if (!client.available()) delay(100); else signature = read16(client); - if (signature == 0x4D42) - { - //Serial.print("signature wait loops: "); Serial.println(i); - break; - } + if (signature == 0x4D42) break; } if (signature == 0x4D42) // BMP signature { @@ -1398,7 +1375,7 @@ void drawBitmapFrom_HTTPS_ToBuffer(const char* host, const char* path, const cha Serial.print("Image size: "); Serial.print(width); Serial.print('x'); - Serial.println(height); + Serial.println(abs(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; @@ -1475,6 +1452,7 @@ void drawBitmapFrom_HTTPS_ToBuffer(const char* host, const char* path, const cha in_bytes = got; in_remain -= got; bytes_read += got; + in_idx = 0; } if (!connection_ok) { @@ -1618,7 +1596,7 @@ uint32_t skip(BearSSL::WiFiClientSecure& client, int32_t bytes) client.read(); remain--; } - else delay(1); + else delay(10); if (millis() - start > 2000) break; // don't hang forever } return bytes - remain; @@ -1636,7 +1614,7 @@ uint32_t read8n(BearSSL::WiFiClientSecure& client, uint8_t* buffer, int32_t byte *buffer++ = uint8_t(v); remain--; } - else delay(1); + else delay(10); if (millis() - start > 2000) break; // don't hang forever } return bytes - remain; diff --git a/library.properties b/library.properties index 6567df4f..6e6b8b6c 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=GxEPD2 -version=1.6.0 +version=1.6.1 author=Jean-Marc Zingg maintainer=Jean-Marc Zingg sentence=Arduino Display Library for SPI E-Paper displays from Dalian Good Display and Waveshare. From 8ea9f3d566b21cee76a2162ffd7060332ef8af31 Mon Sep 17 00:00:00 2001 From: ZinggJM Date: Fri, 29 Nov 2024 13:56:42 +0100 Subject: [PATCH 3/3] Version 1.6.1 - fixed and updated GxEPD2_WiFi_Example --- .../GxEPD2_WiFi_Buffered.h | 515 ++++++++++++ ...style.h => GxEPD2_WiFi_DisplaySelection.h} | 171 +--- .../GxEPD2_WiFi_Example.ino | 733 +----------------- .../GxEPD2_WiFi_Native4c.h | 457 +++++++++++ .../GxEPD2_selection_check.h | 156 ---- .../GxEPD2_wiring_examples.h | 119 --- src/GxEPD2_4C.h | 2 +- 7 files changed, 993 insertions(+), 1160 deletions(-) create mode 100644 examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Buffered.h rename examples/GxEPD2_WiFi_Example/{GxEPD2_display_selection_new_style.h => GxEPD2_WiFi_DisplaySelection.h} (62%) create mode 100644 examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Native4c.h delete mode 100644 examples/GxEPD2_WiFi_Example/GxEPD2_selection_check.h delete mode 100644 examples/GxEPD2_WiFi_Example/GxEPD2_wiring_examples.h diff --git a/examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Buffered.h b/examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Buffered.h new file mode 100644 index 00000000..0996729b --- /dev/null +++ b/examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Buffered.h @@ -0,0 +1,515 @@ +void drawBitmapFrom_HTTP_ToBuffer(const char* host, const char* path, const char* filename, int16_t x, int16_t y, bool with_color) +{ + WiFiClient client; + bool connection_ok = false; + 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) || (display.epd2.panel == GxEPD2::GDEY073D46); + uint32_t startTime = millis(); + if ((x >= display.width()) || (y >= display.height())) return; + display.fillScreen(GxEPD_WHITE); + Serial.print("connecting to "); Serial.println(host); + if (!client.connect(host, httpPort)) + { + Serial.println("connection failed"); + return; + } + Serial.print("requesting URL: "); + Serial.println(String("http://") + host + path + filename); + client.print(String("GET ") + path + filename + " HTTP/1.1\r\n" + + "Host: " + host + "\r\n" + + "User-Agent: GxEPD2_WiFi_Example\r\n" + + "Connection: close\r\n\r\n"); + Serial.println("request sent"); + while (client.connected()) + { + String line = client.readStringUntil('\n'); + if (!connection_ok) + { + connection_ok = line.startsWith("HTTP/1.1 200 OK"); + if (connection_ok) Serial.println(line); + } + if (!connection_ok) Serial.println(line); + if (line == "\r") + { + Serial.println("headers received"); + break; + } + } + if (!connection_ok) return; + // Parse BMP header + if (read16(client) == 0x4D42) // BMP signature + { + uint32_t fileSize = read32(client); + uint32_t creatorBytes = read32(client); (void)creatorBytes; //unused + uint32_t imageOffset = read32(client); // Start of image data + uint32_t headerSize = read32(client); + uint32_t width = read32(client); + int32_t height = (int32_t) read32(client); + uint16_t planes = read16(client); + uint16_t depth = read16(client); // bits per pixel + uint32_t format = read32(client); + uint32_t bytes_read = 7 * 4 + 3 * 2; // read so far + 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(abs(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 = false; + bool colored = false; + if (depth == 1) with_color = false; + if (depth <= 8) + { + if (depth < 8) bitmask >>= depth; + bytes_read += skip(client, imageOffset - (4 << depth) - bytes_read); // 54 for regular, diff for colorsimportant + for (uint16_t pn = 0; pn < (1 << depth); pn++) + { + blue = client.read(); + green = client.read(); + red = client.read(); + client.read(); + bytes_read += 4; + 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; + //Serial.print("0x00"); Serial.print(red, HEX); Serial.print(green, HEX); Serial.print(blue, HEX); + //Serial.print(" : "); Serial.print(whitish); Serial.print(", "); Serial.println(colored); + rgb_palette_buffer[pn] = ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | ((blue & 0xF8) >> 3); + } + } + uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset; + bytes_read += skip(client, rowPosition - bytes_read); + for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line + { + if (!connection_ok || !(client.connected() || client.available())) break; + delay(1); // yield() to avoid WDT + 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; + for (uint16_t col = 0; col < w; col++) // for each pixel + { + yield(); + if (!connection_ok || !(client.connected() || client.available())) break; + // Time to read more pixel data? + if (in_idx >= in_bytes) // ok, exact match for 24bit also (size IS multiple of 3) + { + uint32_t get = in_remain > sizeof(input_buffer) ? sizeof(input_buffer) : in_remain; + uint32_t got = read8n(client, input_buffer, get); + while ((got < get) && connection_ok) + { + //Serial.print("got "); Serial.print(got); Serial.print(" < "); Serial.print(get); Serial.print(" @ "); Serial.println(bytes_read); + uint32_t gotmore = read8n(client, input_buffer + got, get - got); + got += gotmore; + connection_ok = gotmore > 0; + } + in_bytes = got; + in_remain -= got; + bytes_read += got; + in_idx = 0; + } + if (!connection_ok) + { + Serial.print("Error: got no more after "); Serial.print(bytes_read); Serial.println(" bytes read!"); + break; + } + switch (depth) + { + case 32: + blue = input_buffer[in_idx++]; + green = input_buffer[in_idx++]; + red = input_buffer[in_idx++]; + in_idx++; // skip alpha + 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 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 2: + 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 + } + Serial.print("bytes read "); Serial.println(bytes_read); + } + } + Serial.print("loaded in "); Serial.print(millis() - startTime); Serial.println(" ms"); + client.stop(); + if (!valid) + { + Serial.println("bitmap format not handled."); + } +} + +void showBitmapFrom_HTTP_Buffered(const char* host, const char* path, const char* filename, int16_t x, int16_t y, bool with_color) +{ + Serial.println(); Serial.print("downloading file \""); Serial.print(filename); Serial.println("\""); + display.setFullWindow(); + display.firstPage(); + do + { + drawBitmapFrom_HTTP_ToBuffer(host, path, filename, x, y, with_color); + } + while (display.nextPage()); +} + +void drawBitmapFrom_HTTPS_ToBuffer(const char* host, const char* path, const char* filename, const char* fingerprint, int16_t x, int16_t y, bool with_color, const char* certificate) +{ + // Use WiFiClientSecure class to create TLS connection +#if defined (ESP8266) + BearSSL::WiFiClientSecure client; + BearSSL::X509List cert(certificate ? certificate : certificate_rawcontent); +#else + WiFiClientSecure client; +#endif + bool connection_ok = false; + 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) || (display.epd2.panel == GxEPD2::GDEY073D46); + uint32_t startTime = millis(); + if ((x >= display.width()) || (y >= display.height())) return; + display.fillScreen(GxEPD_WHITE); + Serial.print("connecting to "); Serial.println(host); +#if defined (ESP8266) + if (certificate) client.setTrustAnchors(&cert); + else if (fingerprint) client.setFingerprint(fingerprint); + else client.setInsecure(); +#elif defined (ESP32) + if (certificate) client.setCACert(certificate); +#endif + if (!client.connect(host, httpsPort)) + { + Serial.println("connection failed"); + return; + } + Serial.print("requesting URL: "); + Serial.println(String("https://") + host + path + filename); + client.print(String("GET ") + path + filename + " HTTP/1.1\r\n" + + "Host: " + host + "\r\n" + + "User-Agent: GxEPD2_WiFi_Example\r\n" + + "Connection: close\r\n\r\n"); + Serial.println("request sent"); + while (client.connected()) + { + String line = client.readStringUntil('\n'); + if (!connection_ok) + { + connection_ok = line.startsWith("HTTP/1.1 200 OK"); + if (connection_ok) Serial.println(line); + } + if (!connection_ok) Serial.println(line); + if (line == "\r") + { + Serial.println("headers received"); + break; + } + } + if (!connection_ok) return; + // Parse BMP header + uint16_t signature = 0; + for (int16_t i = 0; i < 50; i++) + { + if (!client.available()) delay(100); + else signature = read16(client); + if (signature == 0x4D42) break; + } + if (signature == 0x4D42) // BMP signature + { + uint32_t fileSize = read32(client); + uint32_t creatorBytes = read32(client); (void)creatorBytes; //unused + uint32_t imageOffset = read32(client); // Start of image data + uint32_t headerSize = read32(client); + uint32_t width = read32(client); + int32_t height = (int32_t) read32(client); + uint16_t planes = read16(client); + uint16_t depth = read16(client); // bits per pixel + uint32_t format = read32(client); + uint32_t bytes_read = 7 * 4 + 3 * 2; // read so far + 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(abs(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 = false; + bool colored = false; + if (depth == 1) with_color = false; + if (depth <= 8) + { + if (depth < 8) bitmask >>= depth; + //bytes_read += skip(client, 54 - bytes_read); //palette is always @ 54 + bytes_read += skip(client, imageOffset - (4 << depth) - bytes_read); // 54 for regular, diff for colorsimportant + for (uint16_t pn = 0; pn < (1 << depth); pn++) + { + blue = client.read(); + green = client.read(); + red = client.read(); + client.read(); + bytes_read += 4; + 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; + //Serial.print("0x00"); Serial.print(red, HEX); Serial.print(green, HEX); Serial.print(blue, HEX); + //Serial.print(" : "); Serial.print(whitish); Serial.print(", "); Serial.println(colored); + rgb_palette_buffer[pn] = ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | ((blue & 0xF8) >> 3); + } + } + uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset; + //Serial.print("skip "); Serial.println(rowPosition - bytes_read); + bytes_read += skip(client, rowPosition - bytes_read); + for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line + { + if (!connection_ok || !(client.connected() || client.available())) break; + delay(1); // yield() to avoid WDT + 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; + for (uint16_t col = 0; col < w; col++) // for each pixel + { + yield(); + if (!connection_ok || !(client.connected() || client.available())) break; + // Time to read more pixel data? + if (in_idx >= in_bytes) // ok, exact match for 24bit also (size IS multiple of 3) + { + uint32_t get = in_remain > sizeof(input_buffer) ? sizeof(input_buffer) : in_remain; + uint32_t got = read8n(client, input_buffer, get); + while ((got < get) && connection_ok) + { + //Serial.print("got "); Serial.print(got); Serial.print(" < "); Serial.print(get); Serial.print(" @ "); Serial.println(bytes_read); + uint32_t gotmore = read8n(client, input_buffer + got, get - got); + got += gotmore; + connection_ok = gotmore > 0; + } + in_bytes = got; + in_remain -= got; + bytes_read += got; + in_idx = 0; + } + if (!connection_ok) + { + Serial.print("Error: got no more after "); Serial.print(bytes_read); Serial.println(" bytes read!"); + break; + } + switch (depth) + { + case 32: + blue = input_buffer[in_idx++]; + green = input_buffer[in_idx++]; + red = input_buffer[in_idx++]; + in_idx++; // skip alpha + 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 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 2: + 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 + } + Serial.print("bytes read "); Serial.println(bytes_read); + } + } + Serial.print("loaded in "); Serial.print(millis() - startTime); Serial.println(" ms"); + client.stop(); + if (!valid) + { + Serial.println("bitmap format not handled."); + } +} + +void showBitmapFrom_HTTPS_Buffered(const char* host, const char* path, const char* filename, const char* fingerprint, int16_t x, int16_t y, bool with_color, const char* certificate) +{ + Serial.println(); Serial.print("downloading file \""); Serial.print(filename); Serial.println("\""); + display.setFullWindow(); + display.firstPage(); + do + { + drawBitmapFrom_HTTPS_ToBuffer(host, path, filename, fingerprint, x, y, with_color, certificate); + } + while (display.nextPage()); +} diff --git a/examples/GxEPD2_WiFi_Example/GxEPD2_display_selection_new_style.h b/examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_DisplaySelection.h similarity index 62% rename from examples/GxEPD2_WiFi_Example/GxEPD2_display_selection_new_style.h rename to examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_DisplaySelection.h index ec02d611..9765eade 100644 --- a/examples/GxEPD2_WiFi_Example/GxEPD2_display_selection_new_style.h +++ b/examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_DisplaySelection.h @@ -1,20 +1,3 @@ -// 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: https://www.good-display.com/companyfile/32/ -// -// Author: Jean-Marc Zingg -// -// Version: see library.properties -// -// Library: https://github.com/ZinggJM/GxEPD2 - -// Supporting Arduino Forum Topics (closed, read only): -// Good Display ePaper for Arduino: https://forum.arduino.cc/t/good-display-epaper-for-arduino/419657 -// Waveshare e-paper displays with SPI: https://forum.arduino.cc/t/waveshare-e-paper-displays-with-spi/467865 -// -// Add new topics in https://forum.arduino.cc/c/using-arduino/displays/23 for new questions and issues - // NOTE: you may need to adapt or select for your wiring in the processor specific conditional compile sections below // select the display class (only one), matching the kind of display panel @@ -128,8 +111,6 @@ #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 @@ -145,44 +126,8 @@ #define IS_GxEPD2_1248(x) IS_GxEPD(GxEPD2_1248_IS_, x) #define IS_GxEPD2_1248c(x) IS_GxEPD(GxEPD2_1248c_IS_, x) -#include "GxEPD2_selection_check.h" - -// for my test use only -//#if defined(_AVR) -//#warning "defined(_AVR)" -//#endif -//#if defined(ESP32) -//#warning "defined(ESP32)" -//#endif -//#if defined(ARDUINO_ARCH_ESP32) -//#warning "defined(ARDUINO_ARCH_ESP32)" -//#endif - -#if defined(ARDUINO_ARCH_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 -#elif defined(ARDUINO_AVR_NANO_EVERY) -#define EPD_CS 10 -#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) || IS_GxEPD2_4C(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)); -// for Arduino Micro or Arduino Leonardo with CS on 10 on my proto boards (SS would be 17) uncomment instead: -//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 10, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); -#endif - -#if defined(ARDUINO_ARCH_MEGAAVR) -#if defined(ARDUINO_AVR_NANO_EVERY) -#define MAX_DISPLAY_BUFFER_SIZE 5000 // e.g. full height for 200x200 +#if defined (ESP8266) +#define MAX_DISPLAY_BUFFER_SIZE (81920ul-34000ul-36000ul) // ~34000 base use, WiFiClientSecure seems to need about 36k more to work (with certificates) #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) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS) @@ -191,11 +136,16 @@ GxEPD2_DISPLAY_CLASS displ #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=*/ 10, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); -#endif +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)); +#undef MAX_DISPLAY_BUFFER_SIZE +#undef MAX_HEIGHT #endif -#if defined(ARDUINO_ARCH_ESP32) +#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)) @@ -206,15 +156,9 @@ GxEPD2_DISPLAY_CLASS displ #endif // adapt the constructor parameters to your wiring #if !IS_GxEPD2_1248(GxEPD2_DRIVER_CLASS) && !IS_GxEPD2_1248c(GxEPD2_DRIVER_CLASS) -#if defined(ARDUINO_NANO_ESP32) // uses Dx pin names -GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ D10, /*DC=*/ D8, /*RST=*/ D9, /*BUSY=*/ D7)); -#elif defined(ARDUINO_LOLIN_D32_PRO) +#if defined(ARDUINO_LOLIN_D32_PRO) GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 0, /*RST=*/ 2, /*BUSY=*/ 15)); // my LOLIN_D32_PRO proto board -#elif defined(ARDUINO_LOLIN_S2_MINI) -GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS*/ 33, /*DC=*/ 35, /*RST=*/ 37, /*BUSY=*/ 39)); // my LOLIN ESP32 S2 mini connection #else -//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 27, /*DC=*/ 14, /*RST=*/ 12, /*BUSY=*/ 13)); // Good Display ESP32 Development Kit ESP32-L -//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 27, /*DC=*/ 14, /*RST=*/ 12, /*BUSY=*/ 13, /*CS2=*/ 4)); // for GDEM1085T51 with ESP32-L GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); // my suggested wiring and proto board //GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ 5, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); // LILYGO_T5_V2.4.1 //GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 19, /*RST=*/ 4, /*BUSY=*/ 34)); // LILYGO® TTGO T5 2.66 @@ -233,47 +177,7 @@ GxEPD2_DISPLAY_CLASS < GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS) > di #undef MAX_HEIGHT #endif -#if defined (ARDUINO_ARCH_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) || IS_GxEPD2_4C(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)); -#undef MAX_DISPLAY_BUFFER_SIZE -#undef MAX_HEIGHT -#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) || IS_GxEPD2_4C(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 -// for Good Display STM32 Development Kit DESPI-L. -// needs jumpers from PA5 (PIN_SPI_SCK) to SCK for EPD and PA7 (PIN_SPI_MOSI) to SDI for EPD. PD9 and PD10 are not HW SPI capable. -//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ PD8, /*DC=*/ PE15, /*RST=*/ PE14, /*BUSY=*/ PE13)); -//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ PD8, /*DC=*/ PE15, /*RST=*/ PE14, /*BUSY=*/ PE13, /*CS2=*/ PD12)); // for GDEM1085T51 -GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=PA4*/ EPD_CS, /*DC=*/ PA3, /*RST=*/ PA2, /*BUSY=*/ PA1)); // my proto board -#undef MAX_DISPLAY_BUFFER_SIZE -#undef MAX_HEIGHT -#endif - -#if defined(ARDUINO_ARCH_RENESAS) -#if defined(ARDUINO_UNOR4_MINIMA) || defined(ARDUINO_UNOR4_WIFI) +#if defined(ARDUINO_UNOR4_WIFI) #define MAX_DISPLAY_BUFFER_SIZE 16384ul // 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)) @@ -285,43 +189,6 @@ GxEPD2_DISPLAY_CLASS displ // adapt the constructor parameters to your wiring GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); #endif -#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) || IS_GxEPD2_4C(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=10*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); -#undef MAX_DISPLAY_BUFFER_SIZE -#undef MAX_HEIGHT -#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) || IS_GxEPD2_4C(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 -#if defined(ARDUINO_SAMD_NANO_33_IOT) -GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=10*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); -#else -// 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 -#undef MAX_DISPLAY_BUFFER_SIZE -#undef MAX_HEIGHT -#endif #if defined(ARDUINO_ARCH_RP2040) #define MAX_DISPLAY_BUFFER_SIZE 131072ul // e.g. half of available ram @@ -336,23 +203,9 @@ GxEPD2_DISPLAY_CLASS displ // 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_RASPBERRY_PI_PICO) -// adapt the constructor parameters to your wiring -//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 5, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); // my proto board -// mapping of GoodDisplay DESPI-PICO. NOTE: uses alternate HW SPI pins! -GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 3, /*DC=*/ 2, /*RST=*/ 1, /*BUSY=*/ 0)); // DESPI-PICO -//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 3, /*DC=*/ 2, /*RST=*/ 11, /*BUSY=*/ 10)); // DESPI-PICO modified -//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 9, /*DC=*/ 8, /*RST=*/ 12, /*BUSY=*/ 13)); // Waveshare Pico-ePaper-2.9 -#endif #if defined(ARDUINO_RASPBERRY_PI_PICO_W) GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 9, /*DC=*/ 8, /*RST=*/ 12, /*BUSY=*/ 13)); // Waveshare Pico-ePaper-2.9 #endif -#if defined(ARDUINO_ADAFRUIT_FEATHER_RP2040_THINKINK) -// Adafruit Feather RP2040 ThinkInk used with package https://github.com/earlephilhower/arduino-pico -GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ PIN_EPD_CS, /*DC=*/ PIN_EPD_DC, /*RST=*/ PIN_EPD_RESET, /*BUSY=*/ PIN_EPD_BUSY)); -#endif #undef MAX_DISPLAY_BUFFER_SIZE #undef MAX_HEIGHT #endif - -#endif diff --git a/examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Example.ino b/examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Example.ino index 4e4acd8a..b4cf1c8b 100644 --- a/examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Example.ino +++ b/examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Example.ino @@ -35,217 +35,7 @@ #include #include -// NOTE: you may need to adapt or select for your wiring in the processor specific conditional compile sections below - -// select the display class (only one), matching the kind of display panel -#define GxEPD2_DISPLAY_CLASS GxEPD2_BW -//#define GxEPD2_DISPLAY_CLASS GxEPD2_3C -//#define GxEPD2_DISPLAY_CLASS GxEPD2_4C -//#define GxEPD2_DISPLAY_CLASS GxEPD2_7C - -// select the display driver class (only one) for your panel -//#define GxEPD2_DRIVER_CLASS GxEPD2_102 // GDEW0102T4 80x128, UC8175, (WFT0102CZA2) -//#define GxEPD2_DRIVER_CLASS GxEPD2_150_BN // DEPG0150BN 200x200, SSD1681, (FPC8101), TTGO T5 V2.4.1 -//#define GxEPD2_DRIVER_CLASS GxEPD2_154 // GDEP015OC1 200x200, IL3829, (WFC0000CZ07), no longer available -//#define GxEPD2_DRIVER_CLASS GxEPD2_154_D67 // GDEH0154D67 200x200, SSD1681, (HINK-E154A07-A1) -//#define GxEPD2_DRIVER_CLASS GxEPD2_154_T8 // GDEW0154T8 152x152, UC8151 (IL0373), (WFT0154CZ17) -//#define GxEPD2_DRIVER_CLASS GxEPD2_154_M09 // GDEW0154M09 200x200, JD79653A, (WFT0154CZB3) -//#define GxEPD2_DRIVER_CLASS GxEPD2_154_M10 // GDEW0154M10 152x152, UC8151D, (WFT0154CZ17) -//#define GxEPD2_DRIVER_CLASS GxEPD2_154_GDEY0154D67 // GDEY0154D67 200x200, SSD1681, (FPC-B001 20.05.21) -//#define GxEPD2_DRIVER_CLASS GxEPD2_213 // GDE0213B1 122x250, IL3895, (HINK-E0213-G01), phased out -//#define GxEPD2_DRIVER_CLASS GxEPD2_213_B72 // GDEH0213B72 122x250, SSD1675A (IL3897), (HINK-E0213A22-A0 SLH1852) -//#define GxEPD2_DRIVER_CLASS GxEPD2_213_B73 // GDEH0213B73 122x250, SSD1675B, (HINK-E0213A22-A0 SLH1914) -//#define GxEPD2_DRIVER_CLASS GxEPD2_213_B74 // GDEM0213B74 122x250, SSD1680, FPC-7528B) -//#define GxEPD2_DRIVER_CLASS GxEPD2_213_flex // GDEW0213I5F 104x212, UC8151 (IL0373), (WFT0213CZ16) -//#define GxEPD2_DRIVER_CLASS GxEPD2_213_M21 // GDEW0213M21 104x212, UC8151 (IL0373), (WFT0213CZ16) -//#define GxEPD2_DRIVER_CLASS GxEPD2_213_T5D // GDEW0213T5D 104x212, UC8151D, (WFT0213CZ16) -//#define GxEPD2_DRIVER_CLASS GxEPD2_213_BN // DEPG0213BN 122x250, SSD1680, (FPC-7528B), TTGO T5 V2.4.1, V2.3.1 -//#define GxEPD2_DRIVER_CLASS GxEPD2_213_GDEY0213B74 // GDEY0213B74 122x250, SSD1680, (FPC-A002 20.04.08) -//#define GxEPD2_DRIVER_CLASS GxEPD2_260 // GDEW026T0 152x296, UC8151 (IL0373), (WFT0154CZ17) -//#define GxEPD2_DRIVER_CLASS GxEPD2_260_M01 // GDEW026M01 152x296, UC8151 (IL0373), (WFT0260CZB2) -//#define GxEPD2_DRIVER_CLASS GxEPD2_266_BN // DEPG0266BN 152x296, SSD1680, (FPC7510), TTGO T5 V2.66, TTGO T5 V2.4.1 -//#define GxEPD2_DRIVER_CLASS GxEPD2_266_GDEY0266T90 // GDEY0266T90 152x296, SSD1680, (FPC-A003 HB) -//#define GxEPD2_DRIVER_CLASS GxEPD2_270 // GDEW027W3 176x264, EK79652 (IL91874), (WFI0190CZ22) -//#define GxEPD2_DRIVER_CLASS GxEPD2_270_GDEY027T91 // GDEY027T91 176x264, SSD1680, (FB) -//#define GxEPD2_DRIVER_CLASS GxEPD2_290 // GDEH029A1 128x296, SSD1608 (IL3820), (E029A01-FPC-A1 SYX1553) -//#define GxEPD2_DRIVER_CLASS GxEPD2_290_T5 // GDEW029T5 128x296, UC8151 (IL0373), (WFT0290CZ10) -//#define GxEPD2_DRIVER_CLASS GxEPD2_290_T5D // GDEW029T5D 128x296, UC8151D, (WFT0290CZ10) -//#define GxEPD2_DRIVER_CLASS GxEPD2_290_I6FD // GDEW029I6FD 128x296, UC8151D, (WFT0290CZ10) -//#define GxEPD2_DRIVER_CLASS GxEPD2_290_T94 // GDEM029T94 128x296, SSD1680, (FPC-7519 rev.b) -//#define GxEPD2_DRIVER_CLASS GxEPD2_290_T94_V2 // GDEM029T94 128x296, SSD1680, (FPC-7519 rev.b), Waveshare 2.9" V2 variant -//#define GxEPD2_DRIVER_CLASS GxEPD2_290_BS // DEPG0290BS 128x296, SSD1680, (FPC-7519 rev.b) -//#define GxEPD2_DRIVER_CLASS GxEPD2_290_M06 // GDEW029M06 128x296, UC8151D, (WFT0290CZ10) -//#define GxEPD2_DRIVER_CLASS GxEPD2_290_GDEY029T94 // GDEY029T94 128x296, SSD1680, (FPC-A005 20.06.15) -//#define GxEPD2_DRIVER_CLASS GxEPD2_290_GDEY029T71H // GDEY029T71H 168x384, SSD1685, (FPC-H004 22.03.24) -//#define GxEPD2_DRIVER_CLASS GxEPD2_310_GDEQ031T10 // GDEQ031T10 240x320, UC8253, (no inking, backside mark KEGMO 3100) -//#define GxEPD2_DRIVER_CLASS GxEPD2_371 // GDEW0371W7 240x416, UC8171 (IL0324), (missing) -//#define GxEPD2_DRIVER_CLASS GxEPD2_370_TC1 // ED037TC1 280x480, SSD1677, (ICA-FU-20 ichia 2029), Waveshare 3.7" -//#define GxEPD2_DRIVER_CLASS GxEPD2_420 // GDEW042T2 400x300, UC8176 (IL0398), (WFT042CZ15) -//#define GxEPD2_DRIVER_CLASS GxEPD2_420_M01 // GDEW042M01 400x300, UC8176 (IL0398), (WFT042CZ15) -//#define GxEPD2_DRIVER_CLASS GxEPD2_420_GDEY042T81 // GDEY042T81 400x300, SSD1683 (no inking) -//#define GxEPD2_DRIVER_CLASS GxEPD2_420_GYE042A87 // GYE042A87, 400x300, SSD1683 (HINK-E042-A07-FPC-A1) -//#define GxEPD2_DRIVER_CLASS GxEPD2_420_SE0420NQ04 // SE0420NQ04, 400x300, UC8276C (OPM042A2_V1.0) -//#define GxEPD2_DRIVER_CLASS GxEPD2_426_GDEQ0426T82 // GDEQ0426T82 480x800, SSD1677 (P426010-MF1-A) -//#define GxEPD2_DRIVER_CLASS GxEPD2_579_GDEY0579T93 // GDEY0579T93 792x272, SSD1683 (FPC-E004 22.04.13) -//#define GxEPD2_DRIVER_CLASS GxEPD2_583 // GDEW0583T7 600x448, UC8159c (IL0371), (missing) -//#define GxEPD2_DRIVER_CLASS GxEPD2_583_T8 // GDEW0583T8 648x480, EK79655 (GD7965), (WFT0583CZ61) -//#define GxEPD2_DRIVER_CLASS GxEPD2_583_GDEQ0583T31 // GDEQ0583T31 648x480, UC8179, (P583010-MF1-B) -//#define GxEPD2_DRIVER_CLASS GxEPD2_750 // GDEW075T8 640x384, UC8159c (IL0371), (WF0583CZ09) -//#define GxEPD2_DRIVER_CLASS GxEPD2_750_T7 // GDEW075T7 800x480, EK79655 (GD7965), (WFT0583CZ61) -//#define GxEPD2_DRIVER_CLASS GxEPD2_750_GDEY075T7 // GDEY075T7 800x480, UC8179 (GD7965), (FPC-C001 20.08.20) -//#define GxEPD2_DRIVER_CLASS GxEPD2_1020_GDEM102T91 // GDEM102T91 960x640, SSD1677, (FPC7705 REV.b) -//#define GxEPD2_DRIVER_CLASS GxEPD2_1085_GDEM1085T51 // GDEM1085T51 1360x480, JD79686AB, (FPC8617) *** needs CS2 *** -//#define GxEPD2_DRIVER_CLASS GxEPD2_1160_T91 // GDEH116T91 960x640, SSD1677, (none or hidden) -//#define GxEPD2_DRIVER_CLASS GxEPD2_1248 // GDEW1248T3 1304x984, UC8179, (WFT1248BZ23,WFT1248BZ24) -//#define GxEPD2_DRIVER_CLASS GxEPD2_1330_GDEM133T91 // GDEM133T91 960x680, SSD1677, (FPC-7701 REV.B) -// 3-color e-papers -//#define GxEPD2_DRIVER_CLASS GxEPD2_154c // GDEW0154Z04 200x200, IL0376F, (WFT0000CZ04), no longer available -//#define GxEPD2_DRIVER_CLASS GxEPD2_154_Z90c // GDEH0154Z90 200x200, SSD1681, (HINK-E154A07-A1) -//#define GxEPD2_DRIVER_CLASS GxEPD2_213c // GDEW0213Z16 104x212, UC8151 (IL0373), (WFT0213CZ16) -//#define GxEPD2_DRIVER_CLASS GxEPD2_213_Z19c // GDEH0213Z19 104x212, UC8151D, (HINK-E0213A20-A2 2020-11-19) -//#define GxEPD2_DRIVER_CLASS GxEPD2_213_Z98c // GDEY0213Z98 122x250, SSD1680, (FPC-A002 20.04.08) -//#define GxEPD2_DRIVER_CLASS GxEPD2_266c // GDEY0266Z90 152x296, SSD1680, (FPC-7510) -//#define GxEPD2_DRIVER_CLASS GxEPD2_270c // GDEW027C44 176x264, IL91874, (WFI0190CZ22) -//#define GxEPD2_DRIVER_CLASS GxEPD2_290c // GDEW029Z10 128x296, UC8151 (IL0373), (WFT0290CZ10) -//#define GxEPD2_DRIVER_CLASS GxEPD2_290_Z13c // GDEH029Z13 128x296, UC8151D, (HINK-E029A10-A3 20160809) -//#define GxEPD2_DRIVER_CLASS GxEPD2_290_C90c // GDEM029C90 128x296, SSD1680, (FPC-7519 rev.b) -//#define GxEPD2_DRIVER_CLASS GxEPD2_420c // GDEW042Z15 400x300, UC8176 (IL0398), (WFT0420CZ15) -//#define GxEPD2_DRIVER_CLASS GxEPD2_420c_Z21 // GDEQ042Z21 400x300, UC8276, (hidden) -//#define GxEPD2_DRIVER_CLASS GxEPD2_420c_GDEY042Z98 // GDEY042Z98 400x300, SSD1683 (no inking) -//#define GxEPD2_DRIVER_CLASS GxEPD2_579c_GDEY0579Z93 // GDEY0579Z93 792x272, SSD1683 (FPC-E004 22.04.13) -//#define GxEPD2_DRIVER_CLASS GxEPD2_583c // GDEW0583Z21 600x448, UC8159c (IL0371), (missing) -//#define GxEPD2_DRIVER_CLASS GxEPD2_583c_Z83 // GDEW0583Z83 648x480, EK79655 (GD7965), (WFT0583CZ61) -//#define GxEPD2_DRIVER_CLASS GxEPD2_583c_GDEQ0583Z31 // GDEQ0583Z31 648x480, UC8179C, -//#define GxEPD2_DRIVER_CLASS GxEPD2_750c // GDEW075Z09 640x384, UC8159c (IL0371), (WF0583CZ09) -//#define GxEPD2_DRIVER_CLASS GxEPD2_750c_Z08 // GDEW075Z08 800x480, EK79655 (GD7965), (WFT0583CZ61) -//#define GxEPD2_DRIVER_CLASS GxEPD2_750c_Z90 // GDEH075Z90 880x528, SSD1677, (HINK-E075A07-A0) -//#define GxEPD2_DRIVER_CLASS GxEPD2_1160c_GDEY116Z91 // GDEY116Z91 960x640, SSD1677, (FPV-K002 22.04.15) -//#define GxEPD2_DRIVER_CLASS GxEPD2_1248c // GDEY1248Z51 1304x984, UC8179, (WFT1248BZ23,WFT1248BZ24) -//#define GxEPD2_DRIVER_CLASS GxEPD2_1330c_GDEM133Z91 // GDEM133Z91 960x680, SSD1677 (FPC-7701 REV.B) -// 4-color e-paper -//#define GxEPD2_DRIVER_CLASS GxEPD2_213c_GDEY0213F51 // GDEY0213F51 122x250, JD79661 (FPC-A002 20.04.08) -//#define GxEPD2_DRIVER_CLASS GxEPD2_266c_GDEY0266F51H // GDEY0266F51H 184x360, JD79667 (FPC-H006 22.04.02) -//#define GxEPD2_DRIVER_CLASS GxEPD2_290c_GDEY029F51H // GDEY029F51H 168x384, JD79667 (FPC-H004 22.03.24) -//#define GxEPD2_DRIVER_CLASS GxEPD2_300c // Waveshare 3.00" 4-color -//#define GxEPD2_DRIVER_CLASS GxEPD2_420c_GDEY0420F51 // GDEY0420F51 400x300, HX8717 (no inking) -//#define GxEPD2_DRIVER_CLASS GxEPD2_437c // Waveshare 4.37" 4-color -//#define GxEPD2_DRIVER_CLASS GxEPD2_0579c_GDEY0579F51 // GDEY0579F51 792x272, HX8717 (FPC-E009 22.09.25) -//#define GxEPD2_DRIVER_CLASS GxEPD2_1160c_GDEY116F51 // GDEY116F51 960x640, SSD2677, (FPC-K012 23.09.27) -// 7-color e-paper -//#define GxEPD2_DRIVER_CLASS GxEPD2_565c // Waveshare 5.65" 7-color -//#define GxEPD2_DRIVER_CLASS GxEPD2_565c_GDEP0565D90 // GDEP0565D90 600x448 7-color (E219454, AB1024-EGA AC0750TC1) -//#define GxEPD2_DRIVER_CLASS GxEPD2_730c_GDEY073D46 // GDEY073D46 800x480 7-color, (N-FPC-001 2021.11.26) -//#define GxEPD2_DRIVER_CLASS GxEPD2_730c_ACeP_730 // Waveshare 7.3" 7-color -//#define GxEPD2_DRIVER_CLASS GxEPD2_730c_GDEP073E01 // GDEP073E01 800x480 7-color, (E350911HF 94V-0 F-6 ROHS 24141) -// 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 -//#define GxEPD2_DRIVER_CLASS GxEPD2_it103_1872x1404 // ES103TC1 1872x1404 - -// SS is usually used for CS. define here for easy change -#ifndef EPD_CS -#define EPD_CS SS -#endif - -// 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_4C_IS_GxEPD2_4C true -#define GxEPD2_7C_IS_GxEPD2_7C true -#define GxEPD2_1248_IS_GxEPD2_1248 true -#define GxEPD2_1248c_IS_GxEPD2_1248c 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_4C(x) IS_GxEPD(GxEPD2_4C_IS_, x) -#define IS_GxEPD2_7C(x) IS_GxEPD(GxEPD2_7C_IS_, x) -#define IS_GxEPD2_1248(x) IS_GxEPD(GxEPD2_1248_IS_, x) -#define IS_GxEPD2_1248c(x) IS_GxEPD(GxEPD2_1248c_IS_, x) - -#if defined (ESP8266) -#define MAX_DISPLAY_BUFFER_SIZE (81920ul-34000ul-36000ul) // ~34000 base use, WiFiClientSecure seems to need about 36k more to work (with certificates) -#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) || IS_GxEPD2_4C(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)); -#undef MAX_DISPLAY_BUFFER_SIZE -#undef MAX_HEIGHT -#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) || IS_GxEPD2_4C(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) && !IS_GxEPD2_1248c(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)); // my LOLIN_D32_PRO proto board -#else -GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); // my suggested wiring and proto board -//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ 5, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); // LILYGO_T5_V2.4.1 -//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 19, /*RST=*/ 4, /*BUSY=*/ 34)); // LILYGO® TTGO T5 2.66 -//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 2, /*RST=*/ 0, /*BUSY=*/ 4)); // e.g. TTGO T8 ESP32-WROVER -//GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 15, /*DC=*/ 27, /*RST=*/ 26, /*BUSY=*/ 25)); // Waveshare ESP32 Driver Board -#endif -#else // GxEPD2_1248 or GxEPD2_1248c -// Waveshare 12.48 b/w or b/w/r SPI display board and frame or Good Display 12.48 b/w panel GDEW1248T3 or b/w/r panel GDEY1248Z51 -// general constructor for use with all parameters, e.g. for Waveshare ESP32 driver board mounted on connection board -GxEPD2_DISPLAY_CLASS < GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS) > display(GxEPD2_DRIVER_CLASS(/*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 -#undef MAX_DISPLAY_BUFFER_SIZE -#undef MAX_HEIGHT -#endif - -#if defined(ARDUINO_UNOR4_WIFI) -#define MAX_DISPLAY_BUFFER_SIZE 16384ul // 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) || IS_GxEPD2_4C(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_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) || IS_GxEPD2_4C(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 -#if defined(ARDUINO_NANO_RP2040_CONNECT) -// 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_RASPBERRY_PI_PICO_W) -GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 9, /*DC=*/ 8, /*RST=*/ 12, /*BUSY=*/ 13)); // Waveshare Pico-ePaper-2.9 -#endif -#undef MAX_DISPLAY_BUFFER_SIZE -#undef MAX_HEIGHT -#endif +#include "GxEPD2_WiFi_DisplaySelection.h" #if defined (ESP8266) #include @@ -285,6 +75,10 @@ void showBitmapFrom_HTTP_Buffered(const char* host, const char* path, const char void showBitmapFrom_HTTPS_Buffered(const char* host, const char* path, const char* filename, const char* fingerprint, int16_t x, int16_t y, bool with_color = true, const char* certificate = certificate_rawcontent); +void showNative4cFrom_HTTP(const char* host, const char* path, const char* filename, int16_t x, int16_t y, bool with_color = true); +void showNative4cFrom_HTTPS(const char* host, const char* path, const char* filename, const char* fingerprint, int16_t x, int16_t y, bool with_color = true, + const char* certificate = certificate_rawcontent); + #if defined(ESP32) // uncomment next line to use HSPI for EPD (and VSPI for SD), e.g. with Waveshare ESP32 Driver Board //#define USE_HSPI_FOR_EPD @@ -435,6 +229,7 @@ void drawBitmaps_test() int16_t w2 = display.width() / 2; int16_t h2 = display.height() / 2; //showBitmapFrom_HTTPS(host_rawcontent, path_workcontent, "z0gs/screenshot.bmp", fp_rawcontent, 0, 0); delay(2000); return; + //showNative4cFrom_HTTPS(host_rawcontent, path_workcontent, "z0gs/screenshot.bmp", fp_rawcontent, 0, 0); delay(2000); return; showBitmapFrom_HTTPS(host_rawcontent, path_prenticedavid, "betty_4.bmp", fp_rawcontent, w2 - 102, h2 - 126); delay(2000); showBitmapFrom_HTTPS(host_rawcontent, path_rawcontent, "output5.bmp", fp_rawcontent, 0, 0); @@ -791,253 +586,6 @@ void showBitmapFrom_HTTP(const char* host, const char* path, const char* filenam } } -void drawBitmapFrom_HTTP_ToBuffer(const char* host, const char* path, const char* filename, int16_t x, int16_t y, bool with_color) -{ - WiFiClient client; - bool connection_ok = false; - 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) || (display.epd2.panel == GxEPD2::GDEY073D46); - uint32_t startTime = millis(); - if ((x >= display.width()) || (y >= display.height())) return; - display.fillScreen(GxEPD_WHITE); - Serial.print("connecting to "); Serial.println(host); - if (!client.connect(host, httpPort)) - { - Serial.println("connection failed"); - return; - } - Serial.print("requesting URL: "); - Serial.println(String("http://") + host + path + filename); - client.print(String("GET ") + path + filename + " HTTP/1.1\r\n" + - "Host: " + host + "\r\n" + - "User-Agent: GxEPD2_WiFi_Example\r\n" + - "Connection: close\r\n\r\n"); - Serial.println("request sent"); - while (client.connected()) - { - String line = client.readStringUntil('\n'); - if (!connection_ok) - { - connection_ok = line.startsWith("HTTP/1.1 200 OK"); - if (connection_ok) Serial.println(line); - } - if (!connection_ok) Serial.println(line); - if (line == "\r") - { - Serial.println("headers received"); - break; - } - } - if (!connection_ok) return; - // Parse BMP header - if (read16(client) == 0x4D42) // BMP signature - { - uint32_t fileSize = read32(client); - uint32_t creatorBytes = read32(client); (void)creatorBytes; //unused - uint32_t imageOffset = read32(client); // Start of image data - uint32_t headerSize = read32(client); - uint32_t width = read32(client); - int32_t height = (int32_t) read32(client); - uint16_t planes = read16(client); - uint16_t depth = read16(client); // bits per pixel - uint32_t format = read32(client); - uint32_t bytes_read = 7 * 4 + 3 * 2; // read so far - 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(abs(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 = false; - bool colored = false; - if (depth == 1) with_color = false; - if (depth <= 8) - { - if (depth < 8) bitmask >>= depth; - bytes_read += skip(client, imageOffset - (4 << depth) - bytes_read); // 54 for regular, diff for colorsimportant - for (uint16_t pn = 0; pn < (1 << depth); pn++) - { - blue = client.read(); - green = client.read(); - red = client.read(); - client.read(); - bytes_read += 4; - 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; - //Serial.print("0x00"); Serial.print(red, HEX); Serial.print(green, HEX); Serial.print(blue, HEX); - //Serial.print(" : "); Serial.print(whitish); Serial.print(", "); Serial.println(colored); - rgb_palette_buffer[pn] = ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | ((blue & 0xF8) >> 3); - } - } - uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset; - bytes_read += skip(client, rowPosition - bytes_read); - for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line - { - if (!connection_ok || !(client.connected() || client.available())) break; - delay(1); // yield() to avoid WDT - 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; - for (uint16_t col = 0; col < w; col++) // for each pixel - { - yield(); - if (!connection_ok || !(client.connected() || client.available())) break; - // Time to read more pixel data? - if (in_idx >= in_bytes) // ok, exact match for 24bit also (size IS multiple of 3) - { - uint32_t get = in_remain > sizeof(input_buffer) ? sizeof(input_buffer) : in_remain; - uint32_t got = read8n(client, input_buffer, get); - while ((got < get) && connection_ok) - { - //Serial.print("got "); Serial.print(got); Serial.print(" < "); Serial.print(get); Serial.print(" @ "); Serial.println(bytes_read); - uint32_t gotmore = read8n(client, input_buffer + got, get - got); - got += gotmore; - connection_ok = gotmore > 0; - } - in_bytes = got; - in_remain -= got; - bytes_read += got; - in_idx = 0; - } - if (!connection_ok) - { - Serial.print("Error: got no more after "); Serial.print(bytes_read); Serial.println(" bytes read!"); - break; - } - switch (depth) - { - case 32: - blue = input_buffer[in_idx++]; - green = input_buffer[in_idx++]; - red = input_buffer[in_idx++]; - in_idx++; // skip alpha - 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 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 2: - 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 - } - Serial.print("bytes read "); Serial.println(bytes_read); - } - } - Serial.print("loaded in "); Serial.print(millis() - startTime); Serial.println(" ms"); - client.stop(); - if (!valid) - { - Serial.println("bitmap format not handled."); - } -} - -void showBitmapFrom_HTTP_Buffered(const char* host, const char* path, const char* filename, int16_t x, int16_t y, bool with_color) -{ - Serial.println(); Serial.print("downloading file \""); Serial.print(filename); Serial.println("\""); - display.setFullWindow(); - display.firstPage(); - do - { - drawBitmapFrom_HTTP_ToBuffer(host, path, filename, x, y, with_color); - } - while (display.nextPage()); -} - void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filename, const char* fingerprint, int16_t x, int16_t y, bool with_color, const char* certificate) { // Use WiFiClientSecure class to create TLS connection @@ -1294,274 +842,9 @@ void showBitmapFrom_HTTPS(const char* host, const char* path, const char* filena } } -void drawBitmapFrom_HTTPS_ToBuffer(const char* host, const char* path, const char* filename, const char* fingerprint, int16_t x, int16_t y, bool with_color, const char* certificate) -{ - // Use WiFiClientSecure class to create TLS connection -#if defined (ESP8266) - BearSSL::WiFiClientSecure client; - BearSSL::X509List cert(certificate ? certificate : certificate_rawcontent); -#else - WiFiClientSecure client; -#endif - bool connection_ok = false; - 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) || (display.epd2.panel == GxEPD2::GDEY073D46); - uint32_t startTime = millis(); - if ((x >= display.width()) || (y >= display.height())) return; - display.fillScreen(GxEPD_WHITE); - Serial.print("connecting to "); Serial.println(host); -#if defined (ESP8266) - if (certificate) client.setTrustAnchors(&cert); - else if (fingerprint) client.setFingerprint(fingerprint); - else client.setInsecure(); -#elif defined (ESP32) - if (certificate) client.setCACert(certificate); -#endif - if (!client.connect(host, httpsPort)) - { - Serial.println("connection failed"); - return; - } - Serial.print("requesting URL: "); - Serial.println(String("https://") + host + path + filename); - client.print(String("GET ") + path + filename + " HTTP/1.1\r\n" + - "Host: " + host + "\r\n" + - "User-Agent: GxEPD2_WiFi_Example\r\n" + - "Connection: close\r\n\r\n"); - Serial.println("request sent"); - while (client.connected()) - { - String line = client.readStringUntil('\n'); - if (!connection_ok) - { - connection_ok = line.startsWith("HTTP/1.1 200 OK"); - if (connection_ok) Serial.println(line); - } - if (!connection_ok) Serial.println(line); - if (line == "\r") - { - Serial.println("headers received"); - break; - } - } - if (!connection_ok) return; - // Parse BMP header - uint16_t signature = 0; - for (int16_t i = 0; i < 50; i++) - { - if (!client.available()) delay(100); - else signature = read16(client); - if (signature == 0x4D42) break; - } - if (signature == 0x4D42) // BMP signature - { - uint32_t fileSize = read32(client); - uint32_t creatorBytes = read32(client); (void)creatorBytes; //unused - uint32_t imageOffset = read32(client); // Start of image data - uint32_t headerSize = read32(client); - uint32_t width = read32(client); - int32_t height = (int32_t) read32(client); - uint16_t planes = read16(client); - uint16_t depth = read16(client); // bits per pixel - uint32_t format = read32(client); - uint32_t bytes_read = 7 * 4 + 3 * 2; // read so far - 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(abs(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 = false; - bool colored = false; - if (depth == 1) with_color = false; - if (depth <= 8) - { - if (depth < 8) bitmask >>= depth; - //bytes_read += skip(client, 54 - bytes_read); //palette is always @ 54 - bytes_read += skip(client, imageOffset - (4 << depth) - bytes_read); // 54 for regular, diff for colorsimportant - for (uint16_t pn = 0; pn < (1 << depth); pn++) - { - blue = client.read(); - green = client.read(); - red = client.read(); - client.read(); - bytes_read += 4; - 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; - //Serial.print("0x00"); Serial.print(red, HEX); Serial.print(green, HEX); Serial.print(blue, HEX); - //Serial.print(" : "); Serial.print(whitish); Serial.print(", "); Serial.println(colored); - rgb_palette_buffer[pn] = ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | ((blue & 0xF8) >> 3); - } - } - uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset; - //Serial.print("skip "); Serial.println(rowPosition - bytes_read); - bytes_read += skip(client, rowPosition - bytes_read); - for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line - { - if (!connection_ok || !(client.connected() || client.available())) break; - delay(1); // yield() to avoid WDT - 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; - for (uint16_t col = 0; col < w; col++) // for each pixel - { - yield(); - if (!connection_ok || !(client.connected() || client.available())) break; - // Time to read more pixel data? - if (in_idx >= in_bytes) // ok, exact match for 24bit also (size IS multiple of 3) - { - uint32_t get = in_remain > sizeof(input_buffer) ? sizeof(input_buffer) : in_remain; - uint32_t got = read8n(client, input_buffer, get); - while ((got < get) && connection_ok) - { - //Serial.print("got "); Serial.print(got); Serial.print(" < "); Serial.print(get); Serial.print(" @ "); Serial.println(bytes_read); - uint32_t gotmore = read8n(client, input_buffer + got, get - got); - got += gotmore; - connection_ok = gotmore > 0; - } - in_bytes = got; - in_remain -= got; - bytes_read += got; - in_idx = 0; - } - if (!connection_ok) - { - Serial.print("Error: got no more after "); Serial.print(bytes_read); Serial.println(" bytes read!"); - break; - } - switch (depth) - { - case 32: - blue = input_buffer[in_idx++]; - green = input_buffer[in_idx++]; - red = input_buffer[in_idx++]; - in_idx++; // skip alpha - 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 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 2: - 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 - } - Serial.print("bytes read "); Serial.println(bytes_read); - } - } - Serial.print("loaded in "); Serial.print(millis() - startTime); Serial.println(" ms"); - client.stop(); - if (!valid) - { - Serial.println("bitmap format not handled."); - } -} +#include "GxEPD2_WiFi_Buffered.h" -void showBitmapFrom_HTTPS_Buffered(const char* host, const char* path, const char* filename, const char* fingerprint, int16_t x, int16_t y, bool with_color, const char* certificate) -{ - Serial.println(); Serial.print("downloading file \""); Serial.print(filename); Serial.println("\""); - display.setFullWindow(); - display.firstPage(); - do - { - drawBitmapFrom_HTTPS_ToBuffer(host, path, filename, fingerprint, x, y, with_color, certificate); - } - while (display.nextPage()); -} +#include "GxEPD2_WiFi_Native4c.h" uint16_t read16(WiFiClient& client) { diff --git a/examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Native4c.h b/examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Native4c.h new file mode 100644 index 00000000..56ee6725 --- /dev/null +++ b/examples/GxEPD2_WiFi_Example/GxEPD2_WiFi_Native4c.h @@ -0,0 +1,457 @@ +uint8_t output_row_native4c_buffer[max_row_width / 4]; // buffer for at least one row of native color bits + +uint8_t color4(uint16_t red, uint16_t green, uint16_t blue) +{ + uint8_t cv4 = 0x00; + if ((red < 0x80) && (green < 0x80) && (blue < 0x80)) cv4 = 0x00; // black + else if ((red >= 0x80) && (green >= 0x80) && (blue >= 0x80)) cv4 = 0x01; // white + else if ((red >= 0x80) && (blue >= 0x80)) cv4 = 0x03; // red, blue > red + else if ((green >= 0x80) && (blue >= 0x80)) cv4 = 0x01; // green, blue > white + else if ((red >= 0x80) && (green >= 0xC0)) cv4 = 0x02; // yellow + else if ((red >= 0x80) && (green >= 0x40)) cv4 = 0x03; // orange > red + else if (red >= 0x80) cv4 = 0x03; // red + else if (green >= 0x80) cv4 = 0x00; // green > black + else cv4 = 0x03; // blue + return cv4; +} + +void showNative4cFrom_HTTP(const char* host, const char* path, const char* filename, int16_t x, int16_t y) +{ + WiFiClient client; + bool connection_ok = false; + bool valid = false; // valid format to be handled + bool flip = true; // bitmap is stored bottom-to-top + uint32_t startTime = millis(); + if ((x >= display.epd2.WIDTH) || (y >= display.epd2.HEIGHT)) return; + Serial.println(); Serial.print("downloading file \""); Serial.print(filename); Serial.println("\""); + Serial.print("connecting to "); Serial.println(host); + if (!client.connect(host, httpPort)) + { + Serial.println("connection failed"); + return; + } + Serial.print("requesting URL: "); + Serial.println(String("http://") + host + path + filename); + client.print(String("GET ") + path + filename + " HTTP/1.1\r\n" + + "Host: " + host + "\r\n" + + "User-Agent: GxEPD2_WiFi_Example\r\n" + + "Connection: close\r\n\r\n"); + Serial.println("request sent"); + while (client.connected()) + { + String line = client.readStringUntil('\n'); + if (!connection_ok) + { + connection_ok = line.startsWith("HTTP/1.1 200 OK"); + if (connection_ok) Serial.println(line); + } + if (!connection_ok) Serial.println(line); + if (line == "\r") + { + Serial.println("headers received"); + break; + } + } + if (!connection_ok) return; + // Parse BMP header + if (read16(client) == 0x4D42) // BMP signature + { + uint32_t fileSize = read32(client); + uint32_t creatorBytes = read32(client); (void)creatorBytes; //unused + uint32_t imageOffset = read32(client); // Start of image data + uint32_t headerSize = read32(client); + uint32_t width = read32(client); + int32_t height = (int32_t) read32(client); + uint16_t planes = read16(client); + uint16_t depth = read16(client); // bits per pixel + uint32_t format = read32(client); + uint32_t bytes_read = 7 * 4 + 3 * 2; // read so far + 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(abs(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.epd2.WIDTH) w = display.epd2.WIDTH - x; + if ((y + h - 1) >= display.epd2.HEIGHT) h = display.epd2.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; + uint8_t cv4 = 0x00; + if (depth <= 8) + { + if (depth < 8) bitmask >>= depth; + bytes_read += skip(client, imageOffset - (4 << depth) - bytes_read); // 54 for regular, diff for colorsimportant + for (uint16_t pn = 0; pn < (1 << depth); pn++) + { + blue = client.read(); + green = client.read(); + red = client.read(); + client.read(); + bytes_read += 4; + rgb_palette_buffer[pn] = color4(red, green, blue); + } + } + display.clearScreen(); + uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset; + bytes_read += skip(client, rowPosition - bytes_read); + for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line + { + if (!connection_ok || !(client.connected() || client.available())) break; + delay(1); // yield() to avoid WDT + 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 + uint8_t out_cv4_byte = 0x00; + uint32_t out_idx = 0; + for (uint16_t col = 0; col < w; col++) // for each pixel + { + yield(); + if (!connection_ok || !(client.connected() || client.available())) break; + // Time to read more pixel data? + if (in_idx >= in_bytes) // ok, exact match for 24bit also (size IS multiple of 3) + { + uint32_t get = in_remain > sizeof(input_buffer) ? sizeof(input_buffer) : in_remain; + uint32_t got = read8n(client, input_buffer, get); + while ((got < get) && connection_ok) + { + //Serial.print("got "); Serial.print(got); Serial.print(" < "); Serial.print(get); Serial.print(" @ "); Serial.println(bytes_read); + uint32_t gotmore = read8n(client, input_buffer + got, get - got); + got += gotmore; + connection_ok = gotmore > 0; + } + in_bytes = got; + in_remain -= got; + bytes_read += got; + in_idx = 0; + } + if (!connection_ok) + { + Serial.print("Error: got no more after "); Serial.print(bytes_read); Serial.println(" bytes read!"); + break; + } + switch (depth) + { + case 32: + blue = input_buffer[in_idx++]; + green = input_buffer[in_idx++]; + red = input_buffer[in_idx++]; + in_idx++; // skip alpha + cv4 = color4(red, green, blue); + break; + case 24: + blue = input_buffer[in_idx++]; + green = input_buffer[in_idx++]; + red = input_buffer[in_idx++]; + cv4 = color4(red, green, blue); + 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); + } + } + cv4 = color4(red, green, blue); + break; + case 1: + case 2: + case 4: + case 8: + { + if (0 == in_bits) + { + in_byte = input_buffer[in_idx++]; + in_bits = 8; + } + uint16_t pn = (in_byte >> bitshift) & bitmask; + cv4 = rgb_palette_buffer[pn]; + in_byte <<= depth; + in_bits -= depth; + } + break; + } + out_cv4_byte |= cv4 << (2 * (3 - col % 4)); + if ((3 == col % 4) || (col == w - 1)) // write that last byte! (for w%4!=0 border) + { + output_row_native4c_buffer[out_idx++] = out_cv4_byte; + out_cv4_byte = 0x00; + } + } // end pixel + int16_t yrow = y + (flip ? h - row - 1 : row); + display.writeNative(output_row_native4c_buffer, 0, x, yrow, w, 1, false, false, false); + } // end line + Serial.print("downloaded in "); + Serial.print(millis() - startTime); + Serial.println(" ms"); + display.refresh(); + } + Serial.print("bytes read "); Serial.println(bytes_read); + } + } + client.stop(); + if (!valid) + { + Serial.println("bitmap format not handled."); + } +} + +void showNative4cFrom_HTTPS(const char* host, const char* path, const char* filename, const char* fingerprint, int16_t x, int16_t y, bool with_color, const char* certificate) +{ + // Use WiFiClientSecure class to create TLS connection +#if defined (ESP8266) || defined(ARDUINO_RASPBERRY_PI_PICO_W) + BearSSL::WiFiClientSecure client; + BearSSL::X509List cert(certificate ? certificate : certificate_rawcontent); +#else + WiFiClientSecure client; +#endif + bool connection_ok = false; + bool valid = false; // valid format to be handled + bool flip = true; // bitmap is stored bottom-to-top + uint32_t startTime = millis(); + if ((x >= display.epd2.WIDTH) || (y >= display.epd2.HEIGHT)) return; + Serial.println(); Serial.print("downloading file \""); Serial.print(filename); Serial.println("\""); + Serial.print("connecting to "); Serial.println(host); +#if defined (ESP8266) || defined(ARDUINO_RASPBERRY_PI_PICO_W) + if (certificate) client.setTrustAnchors(&cert); + else if (fingerprint) client.setFingerprint(fingerprint); + else client.setInsecure(); +#elif defined (ESP32) + if (certificate) client.setCACert(certificate); +#endif + if (!client.connect(host, httpsPort)) + { + Serial.println("connection failed"); + return; + } + Serial.print("requesting URL: "); + Serial.println(String("https://") + host + path + filename); + client.print(String("GET ") + path + filename + " HTTP/1.1\r\n" + + "Host: " + host + "\r\n" + + "User-Agent: GxEPD2_WiFi_Example\r\n" + + "Connection: close\r\n\r\n"); + Serial.println("request sent"); + while (client.connected()) + { + String line = client.readStringUntil('\n'); + if (!connection_ok) + { + connection_ok = line.startsWith("HTTP/1.1 200 OK"); + if (connection_ok) Serial.println(line); + } + if (!connection_ok) Serial.println(line); + if (line == "\r") + { + Serial.println("headers received"); + break; + } + } + if (!connection_ok) return; + // Parse BMP header + uint16_t signature = 0; + for (int16_t i = 0; i < 50; i++) + { + if (!client.available()) delay(100); + else signature = read16(client); + if (signature == 0x4D42) break; + } + if (signature == 0x4D42) // BMP signature + { + uint32_t fileSize = read32(client); + uint32_t creatorBytes = read32(client); (void)creatorBytes; //unused + uint32_t imageOffset = read32(client); // Start of image data + uint32_t headerSize = read32(client); + uint32_t width = read32(client); + int32_t height = (int32_t) read32(client); + uint16_t planes = read16(client); + uint16_t depth = read16(client); // bits per pixel + uint32_t format = read32(client); + uint32_t bytes_read = 7 * 4 + 3 * 2; // read so far + 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(abs(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.epd2.WIDTH) w = display.epd2.WIDTH - x; + if ((y + h - 1) >= display.epd2.HEIGHT) h = display.epd2.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; + uint8_t cv4 = 0x00; + if (depth <= 8) + { + if (depth < 8) bitmask >>= depth; + bytes_read += skip(client, imageOffset - (4 << depth) - bytes_read); // 54 for regular, diff for colorsimportant + for (uint16_t pn = 0; pn < (1 << depth); pn++) + { + blue = client.read(); + green = client.read(); + red = client.read(); + client.read(); + bytes_read += 4; + rgb_palette_buffer[pn] = color4(red, green, blue); + } + } + display.clearScreen(); + uint32_t rowPosition = flip ? imageOffset + (height - h) * rowSize : imageOffset; + bytes_read += skip(client, rowPosition - bytes_read); + for (uint16_t row = 0; row < h; row++, rowPosition += rowSize) // for each line + { + if (!connection_ok || !(client.connected() || client.available())) break; + delay(1); // yield() to avoid WDT + 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 + uint8_t out_cv4_byte = 0x00; + uint32_t out_idx = 0; + for (uint16_t col = 0; col < w; col++) // for each pixel + { + yield(); + if (!connection_ok || !(client.connected() || client.available())) break; + // Time to read more pixel data? + if (in_idx >= in_bytes) // ok, exact match for 24bit also (size IS multiple of 3) + { + uint32_t get = in_remain > sizeof(input_buffer) ? sizeof(input_buffer) : in_remain; + uint32_t got = read8n(client, input_buffer, get); + while ((got < get) && connection_ok) + { + //Serial.print("got "); Serial.print(got); Serial.print(" < "); Serial.print(get); Serial.print(" @ "); Serial.println(bytes_read); + uint32_t gotmore = read8n(client, input_buffer + got, get - got); + got += gotmore; + connection_ok = gotmore > 0; + } + in_bytes = got; + in_remain -= got; + bytes_read += got; + in_idx = 0; + } + if (!connection_ok) + { + Serial.print("Error: got no more after "); Serial.print(bytes_read); Serial.println(" bytes read!"); + break; + } + switch (depth) + { + case 32: + blue = input_buffer[in_idx++]; + green = input_buffer[in_idx++]; + red = input_buffer[in_idx++]; + in_idx++; // skip alpha + cv4 = color4(red, green, blue); + break; + case 24: + blue = input_buffer[in_idx++]; + green = input_buffer[in_idx++]; + red = input_buffer[in_idx++]; + cv4 = color4(red, green, blue); + 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); + } + } + cv4 = color4(red, green, blue); + break; + case 1: + case 2: + case 4: + case 8: + { + if (0 == in_bits) + { + in_byte = input_buffer[in_idx++]; + in_bits = 8; + } + uint16_t pn = (in_byte >> bitshift) & bitmask; + cv4 = rgb_palette_buffer[pn]; + in_byte <<= depth; + in_bits -= depth; + } + break; + } + out_cv4_byte |= cv4 << (2 * (3 - col % 4)); + if ((3 == col % 4) || (col == w - 1)) // write that last byte! (for w%4!=0 border) + { + output_row_native4c_buffer[out_idx++] = out_cv4_byte; + out_cv4_byte = 0x00; + } + } // end pixel + int16_t yrow = y + (flip ? h - row - 1 : row); + // void writeNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm) + display.writeNative(output_row_native4c_buffer, 0, x, yrow, w, 1, false, false, false); + } // end line + Serial.print("downloaded in "); + Serial.print(millis() - startTime); + Serial.println(" ms"); + display.refresh(); + } + Serial.print("bytes read "); Serial.println(bytes_read); + } + } + client.stop(); + if (!valid) + { + Serial.println("bitmap format not handled."); + } +} diff --git a/examples/GxEPD2_WiFi_Example/GxEPD2_selection_check.h b/examples/GxEPD2_WiFi_Example/GxEPD2_selection_check.h deleted file mode 100644 index 44b0772d..00000000 --- a/examples/GxEPD2_WiFi_Example/GxEPD2_selection_check.h +++ /dev/null @@ -1,156 +0,0 @@ -// 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: https://www.good-display.com/companyfile/32/ -// -// Author: Jean-Marc Zingg -// -// Version: see library.properties -// -// Library: https://github.com/ZinggJM/GxEPD2 - -// Supporting Arduino Forum Topics (closed, read only): -// Good Display ePaper for Arduino: https://forum.arduino.cc/t/good-display-epaper-for-arduino/419657 -// Waveshare e-paper displays with SPI: https://forum.arduino.cc/t/waveshare-e-paper-displays-with-spi/467865 -// -// Add new topics in https://forum.arduino.cc/c/using-arduino/displays/23 for new questions and issues - -#define GxEPD2_102_IS_BW true -#define GxEPD2_150_BN_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_154_GDEY0154D67_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_213_BN_IS_BW true -#define GxEPD2_213_GDEY0213B74_IS_BW true -#define GxEPD2_260_IS_BW true -#define GxEPD2_260_M01_IS_BW true -#define GxEPD2_266_BN_IS_BW true -#define GxEPD2_266_GDEY0266T90_IS_BW true -#define GxEPD2_270_IS_BW true -#define GxEPD2_270_GDEY027T91_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_I6FD_IS_BW true -#define GxEPD2_290_T94_IS_BW true -#define GxEPD2_290_T94_V2_IS_BW true -#define GxEPD2_290_BS_IS_BW true -#define GxEPD2_290_M06_IS_BW true -#define GxEPD2_290_GDEY029T94_IS_BW true -#define GxEPD2_290_GDEY029T71H_IS_BW true -#define GxEPD2_310_GDEQ031T10_IS_BW true -#define GxEPD2_371_IS_BW true -#define GxEPD2_370_TC1_IS_BW true -#define GxEPD2_420_IS_BW true -#define GxEPD2_420_M01_IS_BW true -#define GxEPD2_420_GDEY042T81_IS_BW true -#define GxEPD2_420_GYE042A87_IS_BW true -#define GxEPD2_420_SE0420NQ04_IS_BW true -#define GxEPD2_426_GDEQ0426T82_IS_BW true -#define GxEPD2_579_GDEY0579T93_IS_BW true -#define GxEPD2_583_IS_BW true -#define GxEPD2_583_T8_IS_BW true -#define GxEPD2_583_GDEQ0583T31_IS_BW true -#define GxEPD2_750_IS_BW true -#define GxEPD2_750_T7_IS_BW true -#define GxEPD2_750_GDEY075T7_IS_BW true -#define GxEPD2_1020_GDEM102T91_IS_BW true -#define GxEPD2_1085_GDEM1085T51_IS_BW true -#define GxEPD2_1160_T91_IS_BW true -#define GxEPD2_1248_IS_BW true -#define GxEPD2_1330_GDEM133T91_IS_BW true -#define GxEPD2_it60_IS_BW true -#define GxEPD2_it60_1448x1072_IS_BW true -#define GxEPD2_it78_1872x1404_IS_BW true -#define GxEPD2_it103_1872x1404_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_213_Z98c_IS_3C true -#define GxEPD2_266c_IS_3C true -#define GxEPD2_270c_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_420c_IS_3C true -#define GxEPD2_420c_Z21_IS_3C true -#define GxEPD2_420c_GDEY042Z98_IS_3C true -#define GxEPD2_579c_GDEY0579Z93_IS_3C true -#define GxEPD2_583c_IS_3C true -#define GxEPD2_583c_Z83_IS_3C true -#define GxEPD2_583c_GDEQ0583Z31_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_1160c_GDEY116Z91_IS_3C true -#define GxEPD2_1248c_IS_3C true -#define GxEPD2_1330c_GDEM133Z91_IS_3C true -// 4-color e-paper -#define GxEPD2_213c_GDEY0213F51_IS_4C true -#define GxEPD2_266c_GDEY0266F51H_IS_4C true -#define GxEPD2_290c_GDEY029F51H_IS_4C true -#define GxEPD2_300c_IS_4C true -#define GxEPD2_420c_GDEY0420F51_IS_4C true -#define GxEPD2_437c_IS_4C true -#define GxEPD2_0579c_GDEY0579F51_IS_4C true -#define GxEPD2_1160c_GDEY116F51_IS_4C true -// 7-color e-paper -#define GxEPD2_565c_IS_7C true -#define GxEPD2_565c_GDEP0565D90_IS_7C true -#define GxEPD2_730c_GDEY073D46_IS_7C true -#define GxEPD2_730c_ACeP_730_IS_7C true -#define GxEPD2_730c_GDEP073E01_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) -#define IS_GxEPD2_DRIVER_4C(x) IS_GxEPD2_DRIVER(x, _IS_4C) -#define IS_GxEPD2_DRIVER_7C(x) IS_GxEPD2_DRIVER(x, _IS_7C) -#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_BW(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_4C(GxEPD2_DRIVER_CLASS) -#error "GxEPD2_BW used with 4-color driver class" -#endif -#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_7C(GxEPD2_DRIVER_CLASS) -#error "GxEPD2_BW used with 7-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 -#if IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_4C(GxEPD2_DRIVER_CLASS) -#error "GxEPD2_3C used with 4-color driver class" -#endif -#if IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_7C(GxEPD2_DRIVER_CLASS) -#error "GxEPD2_3C used with 7-color driver class" -#endif -#if IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_BW(GxEPD2_DRIVER_CLASS) -#error "GxEPD2_4C used with b/w driver class" -#endif -#if IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_3C(GxEPD2_DRIVER_CLASS) -#error "GxEPD2_4C used with 3-color driver class" -#endif -#if IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_7C(GxEPD2_DRIVER_CLASS) -#error "GxEPD2_4C used with 7-color driver class" -#endif -#if IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) && !IS_GxEPD2_DRIVER_7C(GxEPD2_DRIVER_CLASS) -#error "GxEPD2_7C used with less colors driver class" -#endif -#if !IS_GxEPD2_DRIVER_BW(GxEPD2_DRIVER_CLASS) && !IS_GxEPD2_DRIVER_3C(GxEPD2_DRIVER_CLASS) && !IS_GxEPD2_DRIVER_4C(GxEPD2_DRIVER_CLASS) && !IS_GxEPD2_DRIVER_7C(GxEPD2_DRIVER_CLASS) -#error "neither BW nor 3C nor 4C nor 7C kind defined for driver class (error in GxEPD2_selection_check.h)" -#endif - -#endif diff --git a/examples/GxEPD2_WiFi_Example/GxEPD2_wiring_examples.h b/examples/GxEPD2_WiFi_Example/GxEPD2_wiring_examples.h deleted file mode 100644 index 7b077d50..00000000 --- a/examples/GxEPD2_WiFi_Example/GxEPD2_wiring_examples.h +++ /dev/null @@ -1,119 +0,0 @@ -// 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: https://www.good-display.com/companyfile/32/ -// -// Author: Jean-Marc Zingg -// -// Version: see library.properties -// -// Library: https://github.com/ZinggJM/GxEPD2 - -// Supporting Arduino Forum Topics (closed, read only): -// Good Display ePaper for Arduino: https://forum.arduino.cc/t/good-display-epaper-for-arduino/419657 -// Waveshare e-paper displays with SPI: https://forum.arduino.cc/t/waveshare-e-paper-displays-with-spi/467865 -// -// Add new topics in https://forum.arduino.cc/c/using-arduino/displays/23 for new questions and issues - -// mapping of Good Display Arduino UNO Development Kit DEArduino-L, e.g. to DESPI-C02 -// BUSY -> 4, RES -> 5, D/C -> 6, CS-> 7, SCK -> 13, SDI -> 11 - -// mapping of Good Display ESP8266 Development Kit ESP8266-L, e.g. to DESPI-C02 -// BUSY -> GPIO16, RES -> GPIO5, D/C -> GPIO4, CS-> GPIO2, SCK -> GPIO14, SDI -> GPIO13 - -// mapping of Good Display ESP32 Development Kit ESP32-L, e.g. to DESPI-C02 -// BUSY -> GPIO13, RES -> GPIO12, D/C -> GPIO14, CS-> GPIO27, SCK -> GPIO18, SDI -> GPIO23 - -// mapping of Good Display STM32 Development Kit DESPI-L, e.g. to DESPI-C02 -// BUSY -> PE13, RES -> PE14, D/C -> PE14, CS-> PD8, SCK -> PD9, SDI -> PD10 -// needs jumpers from PA5 (PIN_SPI_SCK) to SCK for EPD and PA7 (PIN_SPI_MOSI) to SDI for EPD. PD9 and PD10 are not HW SPI capable. -// BUSY -> PE13, RES -> PE14, D/C -> PE14, CS-> PD8, SCK -> PA5, SDI -> PA7 // for HW SPI with GxEPD2 - -// connection suggestions concerning Waveshare e-paper HAT Rev 2.3: -// DON'T FORGET to connect the PWR pin to VCC, to enable power to the board. -// RST is no longer used to disable power to the board, as with earlier revisions. -// Note, for 3.3V processors, no level converters are needed. Use DESPI-C02 instead. Uses less power. -// See https://www.buyepaper.com/products/development-kit-connection-adapter-board-for-eaper-display-demo-kit - -// mapping suggestion from Waveshare SPI e-Paper to Wemos D1 mini -// BUSY -> D2, RST -> D4, DC -> D3, CS -> D8, CLK -> D5, DIN -> D7, GND -> GND, 3.3V -> 3.3V -// NOTE: connect 3.3k pull-down from D8 to GND if your board or shield has level converters -// NOTE for ESP8266: using SS (GPIO15) for CS may cause boot mode problems, use different pin in case, or 3.3k pull-down -// NOTE: connect 1k pull-up from D4 (RST) to 3.3V if your board or shield has the "clever" reset circuit, or use a different pin - -// mapping suggestion from Waveshare SPI e-Paper to generic ESP8266 -// BUSY -> GPIO4, RST -> GPIO2, DC -> GPIO0, CS -> GPIO15, CLK -> GPIO14, DIN -> GPIO13, GND -> GND, 3.3V -> 3.3V -// NOTE: connect 3.3k pull-down from GPIO15 to GND if your board or shield has level converters -// NOTE for ESP8266: using SS (GPIO15) for CS may cause boot mode problems, use different pin in case, or 3.3k pull-down -// NOTE: connect 1k pull-up from GPIO2 (RST) to 3.3V if your board or shield has the "clever" reset circuit, or use a different pin - -// mapping of Waveshare e-Paper ESP8266 Driver Board, new version -// BUSY -> GPIO5, RST -> GPIO2, DC -> GPIO4, CS -> GPIO15, CLK -> GPIO14, DIN -> GPIO13, GND -> GND, 3.3V -> 3.3V -// NOTE for ESP8266: using SS (GPIO15) for CS may cause boot mode problems, add a 3.3k pull-down in case -// the e-Paper ESP8266 Driver Board should have no boot mode issue, as it doesn't use level converters - -// mapping of Waveshare e-Paper ESP8266 Driver Board, old version -// BUSY -> GPIO16, RST -> GPIO5, DC -> GPIO4, CS -> GPIO15, CLK -> GPIO14, DIN -> GPIO13, GND -> GND, 3.3V -> 3.3V -// NOTE for ESP8266: using SS (GPIO15) for CS may cause boot mode problems, add a 3.3k pull-down in case -// the e-Paper ESP8266 Driver Board should have no boot mode issue, as it doesn't use level converters - -// mapping suggestion for ESP32, e.g. LOLIN32, see .../variants/.../pins_arduino.h for your board -// NOTE: there are variants with different pins for SPI ! CHECK SPI PINS OF YOUR BOARD -// BUSY -> 4, RST -> 16, DC -> 17, CS -> SS(5), CLK -> SCK(18), DIN -> MOSI(23), GND -> GND, 3.3V -> 3.3V - -// mapping of Waveshare ESP32 Driver Board -// BUSY -> 25, RST -> 26, DC -> 27, CS-> 15, CLK -> 13, DIN -> 14 -// NOTE: this board uses "unusual" SPI pins and requires re-mapping of HW SPI to these pins in SPIClass -// see example GxEPD2_WS_ESP32_Driver.ino, it shows how this can be done easily - -// mapping suggestion for ESP32, e.g. LOLIN32 D32 PRO -// BUSY -> 15, RST -> 2, DC -> 0, CS -> 5, CLK -> SCK(18), DIN -> MOSI(23), GND -> GND, 3.3V -> 3.3V -// note: use explicit value for CS, as SS is re-defined to TF_CS(4) in pins_arduino.h for Board: "LOLIN D32 PRO" - -// mapping suggestion for ESP32, e.g. TTGO T8 ESP32-WROVER -// BUSY -> 4, RST -> 0, DC -> 2, CS -> SS(5), CLK -> SCK(18), DIN -> MOSI(23), GND -> GND, 3.3V -> 3.3V -// for use with Board: "ESP32 Dev Module": - -// mapping suggestion for ESP32S2, e.g. LOLIN ESP32 S2 mini, direct connection of DESPI-C02 -// BUSY -> 39, RST -> 37, DC -> 35, CS -> 33, CLK -> 18, DIN -> 16, GND -> GND, 3.3V -> 3.3V -// for use with Board: "LOLIN S2 MINI": - -// new mapping suggestion for STM32F1, e.g. STM32F103C8T6 "BluePill" -// BUSY -> A1, RST -> A2, DC -> A3, CS-> A4, CLK -> A5, DIN -> A7 - -// mapping suggestion for AVR, UNO, NANO etc. -// BUSY -> 7, RST -> 9, DC -> 8, CS-> 10, CLK -> 13, DIN -> 11 - -// mapping suggestion for AVR, Arduino Micro, Leonardo -// note: on Leonardo board HW SPI pins are on 6-pin ICSP header -// BUSY -> 7, RST -> 9, DC -> 8, CS-> 10, CLK -> 15, DIN -> 16 - -// mapping of Waveshare Universal e-Paper Raw Panel Driver Shield for Arduino / NUCLEO -// BUSY -> 7, RST -> 8, DC -> 9, CS-> 10, CLK -> 13, DIN -> 11 - -// mapping suggestion for Arduino MEGA -// BUSY -> 7, RST -> 9, DC -> 8, CS-> 53, CLK -> 52, DIN -> 51 - -// mapping suggestion for Arduino DUE, note: pin 77 is on board pin 10, SS is 10 -// BUSY -> 7, RST -> 9, DC -> 8, CS-> 10, CLK -> 76, DIN -> 75 -// SPI pins are on 6 pin 2x3 SPI header, no SS on SPI header! - -// mapping suggestion for Arduino MKR1000 or MKRZERO -// note: can't use SS on MKR1000: is defined as 24, should be 4 -// BUSY -> 5, RST -> 6, DC -> 7, CS-> 4, CLK -> 9, DIN -> 8 - -// mapping suggestion for Arduino Nano RP2040 Connect (Arduino MBED OS Nano Boards) -// BUSY -> 7, RST -> 9, DC -> 8, CS-> 10, CLK -> 13, DIN -> 11 - -// mapping suggestion for Raspberry Pi Pico RP2040 (Arduino MBED OS RP2040 Boards) -// BUSY -> 7, RST -> 9, DC -> 8, CS-> 5, CLK -> 18, DIN -> 19 - -// mapping of my proto board for Raspberry Pi Pico RP2040 (previous default SPI pins) -// BUSY -> 7, RST -> 9, DC -> 8, CS-> 5, CLK -> 2, DIN -> 3 - -// mapping of my new proto board like Waveshare Pico-ePaper-2.9 -// needs 10k pull-up on RST when used with Arduino MBED OS RP2040 Boards -// BUSY -> 13, RST -> 12, DC -> 8, CS-> 9, CLK -> 10, DIN -> 11 - -// mapping of Waveshare Pico-ePaper-2.9 -// BUSY -> 13, RST -> 12, DC -> 8, CS-> 9, CLK -> 10, DIN -> 11 diff --git a/src/GxEPD2_4C.h b/src/GxEPD2_4C.h index 5a31a4a9..77f53036 100644 --- a/src/GxEPD2_4C.h +++ b/src/GxEPD2_4C.h @@ -542,7 +542,7 @@ class GxEPD2_4C : public GxEPD2_GFX_BASE_CLASS else if ((green >= 0x8000) && (blue >= 0x8000)) cv4 = 0x01; // green, blue > white else if ((red >= 0x8000) && (green >= 0xC000)) cv4 = 0x02; // yellow else if ((red >= 0x8000) && (green >= 0x4000)) cv4 = 0x03; // orange > red - else if (red >= 0x8000) cv4 = 0x04; // red + else if (red >= 0x8000) cv4 = 0x03; // red else if (green >= 0x8000) cv4 = 0x00; // green > black else cv4 = 0x03; // blue }