commit 0eefd45e11622c1d52acf42a4ef496dd49c717b7 Author: Miroslav Pivovarsky Date: Thu Apr 18 21:55:30 2024 +0200 initial commit diff --git a/ESP32_PrusaConnectCam/Camera_cfg.h b/ESP32_PrusaConnectCam/Camera_cfg.h new file mode 100644 index 0000000..3b2e8bc --- /dev/null +++ b/ESP32_PrusaConnectCam/Camera_cfg.h @@ -0,0 +1,37 @@ +/** + @file Camera_cfg.h + + @brief Here is saved camera GPIO cfg for camera module OV2640 + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#ifndef _CAMERA_CFG_H_ +#define _CAMERA_CFG_H_ + +// OV2640 camera module pins (CAMERA_MODEL_AI_THINKER) +#define PWDN_GPIO_NUM 32 ///< Power down control pin +#define RESET_GPIO_NUM -1 ///< Reset control pin +#define XCLK_GPIO_NUM 0 ///< External clock pin +#define SIOD_GPIO_NUM 26 ///< SCCB: SI/O data pin +#define SIOC_GPIO_NUM 27 ///< SCCB: SI/O control pin +#define Y9_GPIO_NUM 35 ///< SCCB: Y9 pin +#define Y8_GPIO_NUM 34 ///< SCCB: Y8 pin +#define Y7_GPIO_NUM 39 ///< SCCB: Y7 pin +#define Y6_GPIO_NUM 36 ///< SCCB: Y6 pin +#define Y5_GPIO_NUM 21 ///< SCCB: Y5 pin +#define Y4_GPIO_NUM 19 ///< SCCB: Y4 pin +#define Y3_GPIO_NUM 18 ///< SCCB: Y3 pin +#define Y2_GPIO_NUM 5 ///< SCCB: Y2 pin +#define VSYNC_GPIO_NUM 25 ///< Vertical sync pin +#define HREF_GPIO_NUM 23 ///< Line sync pin +#define PCLK_GPIO_NUM 22 ///< Pixel clock pin + +#define FLASH_GPIO_NUM 4 ///< Flash control pin + +#endif + +/* EOF */ diff --git a/ESP32_PrusaConnectCam/Certificate.cpp b/ESP32_PrusaConnectCam/Certificate.cpp new file mode 100644 index 0000000..f68baff --- /dev/null +++ b/ESP32_PrusaConnectCam/Certificate.cpp @@ -0,0 +1,174 @@ +/** + @file Certificate.cpp + + @brief Here is saved certificate for communication with servers + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#include "Certificate.h" + +/* + echo -n | openssl s_client -servername github.com -connect github.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > github_com.crt + echo -n | openssl s_client -servername objects.githubusercontent.com -connect objects.githubusercontent.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > objects_githubusercontent_com.crt + echo -n | openssl s_client -servername raw.githubusercontent.com -connect raw.githubusercontent.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > raw_githubusercontent_com.crt + echo -n | openssl s_client -servername api.github.com -connect api.github.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > api_github_com.crt + echo -n | openssl s_client -servername connect.prusa.com -connect connect.prusa.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > connect_prusa_com.crt +*/ + +/* + Certificates list: + - connect.prusa.com certificate +*/ +const char* root_CAs = + "-----BEGIN CERTIFICATE-----\n" + "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" + "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" + "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" + "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" + "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" + "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" + "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" + "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" + "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" + "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" + "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" + "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" + "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" + "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" + "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" + "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" + "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" + "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" + "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" + "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" + "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" + "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" + "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" + "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" + "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" + "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" + "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" + "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" + "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" + "-----END CERTIFICATE-----"; + +/* + Certificates list: + - github.com certificate + - objects.githubusercontent.com certificate + - raw.githubusercontent.com certificate + - api.github.com certificate +*/ +const char* root_CAs_ota = + " -----BEGIN CERTIFICATE-----\n" + "MIIEozCCBEmgAwIBAgIQTij3hrZsGjuULNLEDrdCpTAKBggqhkjOPQQDAjCBjzEL\n" + "MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE\n" + "BxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQDEy5T\n" + "ZWN0aWdvIEVDQyBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBMB4X\n" + "DTI0MDMwNzAwMDAwMFoXDTI1MDMwNzIzNTk1OVowFTETMBEGA1UEAxMKZ2l0aHVi\n" + "LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABARO/Ho9XdkY1qh9mAgjOUkW\n" + "mXTb05jgRulKciMVBuKB3ZHexvCdyoiCRHEMBfFXoZhWkQVMogNLo/lW215X3pGj\n" + "ggL+MIIC+jAfBgNVHSMEGDAWgBT2hQo7EYbhBH0Oqgss0u7MZHt7rjAdBgNVHQ4E\n" + "FgQUO2g/NDr1RzTK76ZOPZq9Xm56zJ8wDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB\n" + "/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMEkGA1UdIARCMEAw\n" + "NAYLKwYBBAGyMQECAgcwJTAjBggrBgEFBQcCARYXaHR0cHM6Ly9zZWN0aWdvLmNv\n" + "bS9DUFMwCAYGZ4EMAQIBMIGEBggrBgEFBQcBAQR4MHYwTwYIKwYBBQUHMAKGQ2h0\n" + "dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGlnb0VDQ0RvbWFpblZhbGlkYXRpb25T\n" + "ZWN1cmVTZXJ2ZXJDQS5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3Rp\n" + "Z28uY29tMIIBgAYKKwYBBAHWeQIEAgSCAXAEggFsAWoAdwDPEVbu1S58r/OHW9lp\n" + "LpvpGnFnSrAX7KwB0lt3zsw7CAAAAY4WOvAZAAAEAwBIMEYCIQD7oNz/2oO8VGaW\n" + "WrqrsBQBzQH0hRhMLm11oeMpg1fNawIhAKWc0q7Z+mxDVYV/6ov7f/i0H/aAcHSC\n" + "Ii/QJcECraOpAHYAouMK5EXvva2bfjjtR2d3U9eCW4SU1yteGyzEuVCkR+cAAAGO\n" + "Fjrv+AAABAMARzBFAiEAyupEIVAMk0c8BVVpF0QbisfoEwy5xJQKQOe8EvMU4W8C\n" + "IGAIIuzjxBFlHpkqcsa7UZy24y/B6xZnktUw/Ne5q5hCAHcATnWjJ1yaEMM4W2zU\n" + "3z9S6x3w4I4bjWnAsfpksWKaOd8AAAGOFjrv9wAABAMASDBGAiEA+8OvQzpgRf31\n" + "uLBsCE8ktCUfvsiRT7zWSqeXliA09TUCIQDcB7Xn97aEDMBKXIbdm5KZ9GjvRyoF\n" + "9skD5/4GneoMWzAlBgNVHREEHjAcggpnaXRodWIuY29tgg53d3cuZ2l0aHViLmNv\n" + "bTAKBggqhkjOPQQDAgNIADBFAiEAru2McPr0eNwcWNuDEY0a/rGzXRfRrm+6XfZe\n" + "SzhYZewCIBq4TUEBCgapv7xvAtRKdVdi/b4m36Uyej1ggyJsiesA\n" + "-----END CERTIFICATE-----\n" + " -----BEGIN CERTIFICATE-----\n" + "MIIHOTCCBiGgAwIBAgIQBj1JF0BNOeUTyz/uzRsuGzANBgkqhkiG9w0BAQsFADBZ\n" + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMTMwMQYDVQQDEypE\n" + "aWdpQ2VydCBHbG9iYWwgRzIgVExTIFJTQSBTSEEyNTYgMjAyMCBDQTEwHhcNMjQw\n" + "MzE1MDAwMDAwWhcNMjUwMzE0MjM1OTU5WjBnMQswCQYDVQQGEwJVUzETMBEGA1UE\n" + "CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMGA1UEChMM\n" + "R2l0SHViLCBJbmMuMRQwEgYDVQQDDAsqLmdpdGh1Yi5pbzCCASIwDQYJKoZIhvcN\n" + "AQEBBQADggEPADCCAQoCggEBAK0rFKU6TEGvuLCY3ZOuXlG+3jerD6EP1gc1qe35\n" + "g68FqyGuVPOUddYNZiymjYMZxywoNp3qxlbFFBTf9etsayavT+uW+2UMjqCotAdK\n" + "KicBEspuExoACFuNgTi7sSUT7A55+k4/+5O+VtpaxQ5dmQk7HxcqvMYx5owBU+fB\n" + "wYDD+hXeg3YvxLZNeIlN8OlqWL8w9HbG+3ccegVEjOJQbkrcrW7IQMq2Uk92XjxI\n" + "PmMVIvaefqcC1poGYvS4VvEh3x64vJK1hEM4YLMKBaE/hqFtcMozi+H/8JqTCfzP\n" + "Qhnu21HIop9rSucxxnZbe9AeHz2LERpUTf3rjgOMg9PB1RUCAwEAAaOCA+0wggPp\n" + "MB8GA1UdIwQYMBaAFHSFgMBmx9833s+9KTeqAx2+7c0XMB0GA1UdDgQWBBTob1fr\n" + "hlGY65+lvlPa25SsKC777TB7BgNVHREEdDByggsqLmdpdGh1Yi5pb4IJZ2l0aHVi\n" + "LmlvghVnaXRodWJ1c2VyY29udGVudC5jb22CDnd3dy5naXRodWIuY29tggwqLmdp\n" + "dGh1Yi5jb22CFyouZ2l0aHVidXNlcmNvbnRlbnQuY29tggpnaXRodWIuY29tMD4G\n" + "A1UdIAQ3MDUwMwYGZ4EMAQICMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGln\n" + "aWNlcnQuY29tL0NQUzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUH\n" + "AwEGCCsGAQUFBwMCMIGfBgNVHR8EgZcwgZQwSKBGoESGQmh0dHA6Ly9jcmwzLmRp\n" + "Z2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbEcyVExTUlNBU0hBMjU2MjAyMENBMS0x\n" + "LmNybDBIoEagRIZCaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xv\n" + "YmFsRzJUTFNSU0FTSEEyNTYyMDIwQ0ExLTEuY3JsMIGHBggrBgEFBQcBAQR7MHkw\n" + "JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBRBggrBgEFBQcw\n" + "AoZFaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsRzJU\n" + "TFNSU0FTSEEyNTYyMDIwQ0ExLTEuY3J0MAwGA1UdEwEB/wQCMAAwggF/BgorBgEE\n" + "AdZ5AgQCBIIBbwSCAWsBaQB2AE51oydcmhDDOFts1N8/Uusd8OCOG41pwLH6ZLFi\n" + "mjnfAAABjkN89oAAAAQDAEcwRQIgU/M527Wcx0KQ3II7kCuG5WMuOHRSxKkf1xAj\n" + "JuSkyPACIQCVX0uurcIA2Ug7ipNN2S1ZygukWqJCh7hjIH0XsrXh8QB2AH1ZHhLh\n" + "eCp7HGFnfF79+NCHXBSgTpWeuQMv2Q6MLnm4AAABjkN89oEAAAQDAEcwRQIgCxpL\n" + "BDak+TWKarrCHlZn4DlqwEfAN3lvlgSo21HQuU8CIQDicrb72c0lA2suMWPWT92P\n" + "FLaRvFrFn9HVzI6Vh50YZgB3AObSMWNAd4zBEEEG13G5zsHSQPaWhIb7uocyHf0e\n" + "N45QAAABjkN89pQAAAQDAEgwRgIhAPJQX4QArFCjM0sKKzsWLmqmmU8lMhKEYR2T\n" + "ges1AQyQAiEA2Y3VhP5RG+dapcbwYgVbrTlgWzO7KE/lg1x11CVcz3QwDQYJKoZI\n" + "hvcNAQELBQADggEBAHKlvzObJBxxgyLaUNCEFf37mNFsUtXmaWvkmcfIt9V+TZ7Q\n" + "mtvjx5bsd5lqAflp/eqk4+JYpnYcKWrZfM/vMdxPQTeh/VQWewY/hYn6X/V1s2JI\n" + "MtjqEkW4aotVdWjHVvsx4rAjz5vtub/wVYgtrU8jusH3TVpT9/0AoFhKE5m2IS7M\n" + "Ig7wKR+DDxoNj4fFFluxteVNgbtwuJcb23NkBQqfHXCvQWqxXZZA4Nwl/WoGPoGG\n" + "dW5qVOc3BlhtITW53ASyhvKC7HArhj7LwQH8C/dRgn1agIHP9vVJ1NaZnPXhK98T\n" + "ohv++OO0E/F/bVGNWVnLBQ4v5PjQzRQUTGvM2mU=\n" + "-----END CERTIFICATE-----\n" + "-----BEGIN CERTIFICATE-----\n" + "MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh\n" + "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" + "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n" + "MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT\n" + "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" + "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG\n" + "9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI\n" + "2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx\n" + "1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ\n" + "q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz\n" + "tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ\n" + "vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP\n" + "BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV\n" + "5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY\n" + "1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4\n" + "NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG\n" + "Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91\n" + "8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe\n" + "pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl\n" + "MrY=\n" + "-----END CERTIFICATE-----\n" + "-----BEGIN CERTIFICATE-----\n" + "MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL\n" + "MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl\n" + "eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT\n" + "JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx\n" + "MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT\n" + "Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg\n" + "VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm\n" + "aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo\n" + "I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng\n" + "o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G\n" + "A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD\n" + "VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB\n" + "zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW\n" + "RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=\n" + "-----END CERTIFICATE-----"; + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/Certificate.h b/ESP32_PrusaConnectCam/Certificate.h new file mode 100644 index 0000000..80af7b1 --- /dev/null +++ b/ESP32_PrusaConnectCam/Certificate.h @@ -0,0 +1,20 @@ +/** + @file Certificate.h + + @brief Here is saved certificate for communication with servers + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#ifndef _CERTIFICATE_H_ +#define _CERTIFICATE_H_ + +extern const char* root_CAs; ///< root certificate for communication with prusa servers +extern const char* root_CAs_ota; ///< root certificate for communication with servers + +#endif + +/* EOF */ diff --git a/ESP32_PrusaConnectCam/ESP32_PrusaConnectCam.ino b/ESP32_PrusaConnectCam/ESP32_PrusaConnectCam.ino new file mode 100644 index 0000000..afba124 --- /dev/null +++ b/ESP32_PrusaConnectCam/ESP32_PrusaConnectCam.ino @@ -0,0 +1,146 @@ +/* + This code is adapted for the ESP32-CAM board Ai Thinker version + + It's neccesary install support for ESP32 board to the arduino IDE. In the board manager we need add next link + https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + Then we can install "ESP32 by Espressif Systems" board in the board manager. + ESP32 lib version: 2.0.15 (ESP-IDF v4.4.7) by Espressif Systems + + This project uses other libraries. It is necessary to install them in the arduino IDE. + - Library - License - Version - Link + - ESPAsyncWebSrv - LGPL 2.1 - 1.2.7 - https://github.com/dvarrel/ESPAsyncWebSrv + - AsyncTCP - LGPL 3.0 - 1.1.4 - https://github.com/dvarrel/ESPAsyncTCP + - ArduinoJson - MIT - 7.0.4 - https://github.com/bblanchon/ArduinoJson + - ArduinoUniqueID - MIT - 1.3.0 - https://github.com/ricaun/ArduinoUniqueID + - ESP32 - LGPL 2.1 - 2.0.15 - https://github.com/espressif/arduino-esp32 + + Board configuration in the arduino IDE 2.3.2 + Tools -> Board -> ESP32 Arduino -> AI Thinker ESP32 + Tools -> Flash frequency -> 80MHz + Tools -> Flash Mode -> DIO + Tools -> Partition scheme -> Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS) + + When flashing the firmware to a new, empty ESP32-CAM device for the first time, it is necessary to use the 'Erase' function. + This can be found under 'Tools' -> 'Erase all Flash Before Sketch Upload' -> 'Enable'. + After the initial firmware upload to the MCU, it is possible to disable this option. + If you do not disable this option, your camera configuration will continue to be erased from the flash memory + after uploading new firmware from the Arduino IDE. + + Here is partitions table + # Name, Type, SubType, Offset, Size, Flags + nvs, data, nvs, 0x9000, 0x5000, + otadata, data, ota, 0xe000, 0x2000, + app0, app, ota_0, 0x10000, 0x1E0000, + app1, app, ota_1, 0x1F0000,0x1E0000, + spiffs, data, spiffs, 0x3D0000,0x20000, + coredump, data, coredump,0x3F0000,0x10000, + + Project: ESP32 PrusaConnect Camera + Developed for: Prusa Research, prusa3d.com + Author: Miroslav Pivovarsky + e-mail: miroslav.pivovarsky@gmail.com +*/ + +/* includes */ +#include +#include "Arduino.h" +#include +#include +#include +#include "esp32-hal-cpu.h" + +#include "server.h" +#include "cfg.h" +#include "var.h" +#include "mcu_cfg.h" +#include "system.h" +#include "micro_sd.h" +#include "log.h" +#include "connect.h" +#include "wifi_mngt.h" +#include "stream.h" +#include "serial_cfg.h" + +void setup() { + /* Serial port for debugging purposes */ + Serial.begin(SERIAL_PORT_SPEED); + Serial.println("----------------------------------------------------------------"); + Serial.println("Start MCU!"); + Serial.println("Prusa ESP32-cam https://prusa3d.cz"); + Serial.print("SW Version: "); + Serial.println(SW_VERSION); + Serial.print("Build: "); + Serial.println(SW_BUILD); +#if (CONSOLE_VERBOSE_DEBUG == true) + Serial.setDebugOutput(true); +#endif + + /* Init EEPROM */ + EEPROM.begin(EEPROM_SIZE); + + /* init system led */ + system_led.init(); + + /* init micro SD card and logs */ + SystemLog.SetLogLevel((LogLevel_enum)EEPROM.read(EEPROM_ADDR_LOG_LEVEL)); + SystemLog.Init(); + + /* init System lib */ + System_Init(); + + /* read cfg from EEPROM */ + SystemConfig.Init(); + SystemConfig.CheckResetCfg(); + System_LoadCfg(); + Server_LoadCfg(); + SystemCamera.LoadCameraCfgFromEeprom(); + Connect.LoadCfgFromEeprom(); + SystemWifiMngt.LoadCfgFromEeprom(); + + /* init WiFi mngt */ + SystemWifiMngt.Init(); + + /* init camera interface */ + SystemCamera.Init(); + SystemCamera.CapturePhoto(); + + /* init WEB server */ + Server_InitWebServer(); + + /* init class for communication with PrusaConnect */ + Connect.Init(); + + /* init tasks */ + SystemLog.AddEvent(LogLevel_Info, "Start tasks"); + xTaskCreatePinnedToCore(System_TaskMain, "SystemNtpOtaUpdate", 8000, NULL, 1, &Task_SystemMain, 0); /*function, description, stack size, parameters, priority, task handle, core*/ + xTaskCreatePinnedToCore(System_TaskCaptureAndSendPhoto, "CaptureAndSendPhoto", 10000, NULL, 2, &Task_CapturePhotoAndSend, 0); /*function, description, stack size, parameters, priority, task handle, core*/ + xTaskCreatePinnedToCore(System_TaskWifiManagement, "WiFiManagement", 6000, NULL, 3, &Task_WiFiManagement, 0); /*function, description, stack size, parameters, priority, task handle, core*/ + xTaskCreatePinnedToCore(System_TaskSdCardCheck, "CheckMicroSdCard", 5000, NULL, 4, &Task_SdCardCheck, 0); /*function, description, stack size, parameters, priority, task handle, core*/ + xTaskCreatePinnedToCore(System_TaskSerialCfg, "CheckSerialConfiguration", 3000, NULL, 5, &Task_SerialCfg, 0); /*function, description, stack size, parameters, priority, task handle, core*/ + xTaskCreatePinnedToCore(System_TaskStreamTelemetry, "PrintStreamTelemetry", 3000, NULL, 6, &Task_StreamTelemetry, 0); /*function, description, stack size, parameters, priority, task handle, core*/ + xTaskCreatePinnedToCore(System_TaskSysLed, "SystemLed", 3000, NULL, 7, &Task_SysLed, 0); /*function, description, stack size, parameters, priority, task handle, core*/ + xTaskCreatePinnedToCore(System_TaskWiFiWatchdog, "WiFiWatchdog", 3000, NULL, 8, &Task_WiFiWatchdog, 0); /*function, description, stack size, parameters, priority, task handle, core*/ + + /* init wdg */ + SystemLog.AddEvent(LogLevel_Info, "Init WDG"); + esp_task_wdt_init(WDG_TIMEOUT, true); /* enable panic so ESP32 restarts */ + esp_task_wdt_add(NULL); /* add current thread to WDT watch */ + esp_task_wdt_add(Task_CapturePhotoAndSend); + esp_task_wdt_add(Task_WiFiManagement); + esp_task_wdt_add(Task_SystemMain); + esp_task_wdt_add(Task_SdCardCheck); + esp_task_wdt_add(Task_SerialCfg); + esp_task_wdt_add(Task_StreamTelemetry); + esp_task_wdt_add(Task_SysLed); + esp_task_wdt_add(Task_WiFiWatchdog); + esp_task_wdt_reset(); /* reset wdg */ + + SystemLog.AddEvent(LogLevel_Info, "MCU configuration done"); +} + +void loop() { + /* reset wdg */ + esp_task_wdt_reset(); +} + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/WebPage.h b/ESP32_PrusaConnectCam/WebPage.h new file mode 100644 index 0000000..d0a7e2f --- /dev/null +++ b/ESP32_PrusaConnectCam/WebPage.h @@ -0,0 +1,1346 @@ +/** + @file WebPage.h + + @brief Here is saved root WEB page + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + https://codebeautify.org/remove-extra-spaces + + @bug: no know bug +*/ + +#ifndef _WEB_PAGE_H_ +#define _WEB_PAGE_H_ + +#define MSG_REBOOT_MCU "Reboot process started, wait several seconds for mcu to boot up. You can close this window now." +#define MSG_SAVE_OK_REBOOT "Save OK. Please reboot MCU" ///< WEB app msg save OK +#define MSG_SAVE_OK_WIFI "Save OK. Connecting to Wi-Fi. Please wait several second." +#define MSG_SAVE_OK "Save cfg OK" ///< WEB app msg save OK +#define MSG_SAVE_NOTOK "Save cfg NOT OK!" ///< WEB app msg save NOT OK +#define MSG_SCANNING "Scanning Wi-Fi networks. Wait 8s..." ///< WEB app msg Scanning wifi +#define MSG_UPDATE_START "Start updating." + +/* ------------------------------------------------------------------------------------------------------------ */ +const char index_html[] PROGMEM = R"rawliteral( + + + + + Prusa ESP32-cam + + + + + + + + +
+ +
+
+
+ +
+
+
+
+

Trigger interval: s



+

+


+

+
+
+
+ +
+ + + + + +
+ +
+
+ +



+ + + + + +

Prusa Connect ESP32 cam

Author

Miroslav Pivovarsky

Licence | General Terms and Conditions | Privacy Policy | Cookie Preferences
+ + + + + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char page_auth_html[] PROGMEM = R"rawliteral( + + + + +
+ + + + + + + +
Set web authentication
WEB authentication
Username
Password Show Password
Confirm Password Show Password
+
+ + + + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char page_wifi_html[] PROGMEM = R"rawliteral( + + + + + + + + + + + + + + +
+

Connection status

+

Status:

+

SSID:

+

Signal: % / dBm

+

IP Address:

+

mDNS: https://.local

+
+
+

Available networks

+
+ + + + + + + + + + + + + +
Network name (SSID)Signal strength (RSSI)ChannelEncryption
+
+ +
+
+
+ + + + + +
Connect to Wi-Fi network
Wi-Fi network name (SSID)
Password
+
+ + + + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char page_config_html[] PROGMEM = R"rawliteral( + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
Basic image settings
Connect Token 
Fingerprint
Trigger Interval [s] 
Image qualityLow High
Resolution + +
BrightnessLow High
ContrastLow High
SaturationLow High
Horizontal mirror
Vertical flip
LED light
LED flash
Flash durationLow High
Flash duration ms
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
Advanced image settings
Automatic white balancing
Automatic white balancing gain
Automatic white balancing mode + +
Automatic Exposure Control
Second Level Automatic Exposure Control
Automatic exposure levelLow High
Automatic exposure timeLow High
Automatic exposure time ms
Automatic gain control
Automatic gain control levelLow High
Bad pixel correction
White pixel correction
Raw gamma correction
Lens correction
+
+ + + + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char page_system_html[] PROGMEM = R"rawliteral( + + + +
+ + + + + + + + + + + + + + + + +
System status
PrusaConnect Status
Wi-Fi mode
Wi-Fi service AP SSID
Uptime
Software version
Software build
Available software update Check update from cloud
System configuration
mDNS record.local 
Log level + +
Get logs
+
+
+
+ + + + + + + +
Firmware update
Status:Ready
Processing:
0%
+
+ + + + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char styles_css[] PROGMEM = R"rawliteral( +body { + font-family: sans-serif; +} + +/* index styles */ + .p1 { + color: #797979; + font: normal normal normal 18px/5px sans-serif; + letter-spacing: 0px; +} + .p2 { + text-align: left; + font: normal normal bold 14px/20px sans-serif; + letter-spacing: 0px; + color: #808080; + opacity: 1; + display: inline-block; +} + .p3 { + text-align: left; + font: normal normal normal 14px/20px sans-serif; + letter-spacing: 0px; + color: #808080; + opacity: 1; + display: inline-block; +} + .p4 { + text-align: left; + font: normal normal normal 14px/20px sans-serif; + letter-spacing: 0px; + color: #808080; + opacity: 1; +} + .p5 { + text-align: center; + font: normal normal bold 14px/20px sans-serif; + letter-spacing: 0px; + color: #000000; + opacity: 1; +} +/* NAVIGATION BAR */ + nav { + display: flex; + background-color: transparent; +} + .top_bar { + display: flex; + width: 100%; +} + .top_bar li { + display: inline-block; + padding: 5px; +} + .top_bar li a { + text-decoration: none; + cursor: pointer; + display: flex; + align-items: center; +} + .top_bar li a:hover { + text-decoration: underline #fa6831; + text-underline-position: under; + text-underline-offset: 8px; + text-decoration-thickness: 2px; +} + #links li a.active { + text-decoration: underline #fa6831; + text-underline-position: under; + text-underline-offset: 8px; + text-decoration-thickness: 2px; +} +/* CFG BAR */ +cfg { + display: flex; + flex-direction: column; + text-align: center; + font: normal normal bold 14px/20px sans-serif; + letter-spacing: 0px; + color: #2A2A2A; + opacity: 1; +} +cfg_bar li { + display: inline-block; + padding: 14px; + font-size: 16px; + left: 50%; +} +cfg_bar li a { + text-decoration: none; + cursor: pointer; + color: #212529; +} +cfg_bar li a:hover { + color: #fa6831; +} +/* CONTAINER */ + .container { + display: table; + height: 100%; + width: 100%; +} + .container_left-half { + grid-column: 1; + display: table-cell; + vertical-align: middle; + width: 50%; + text-align: center; +} + .container_right-half { + grid-column: 2; + display: table-cell; + vertical-align: middle; + width: 50%; +} +/* CHECKBOX SLIDER */ + .switch { + position: relative; + display: inline-block; + width: 30px; + height: 17px; + vertical-align: middle; +} + .switch input { + opacity: 0; + width: 0; + height: 0; +} + .checkbox_slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} + .checkbox_slider:before { + position: absolute; + content: ""; + height: 13px; + width: 13px; + left: 2px; + bottom: 2px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + input:checked+.checkbox_slider { + background-color: #797979; +} + input:focus+.checkbox_slider { + box-shadow: 0 0 1px #797979; +} + input:checked+.checkbox_slider:before { + -webkit-transform: translateX(13px); + -ms-transform: translateX(13px); + transform: translateX(13px); +} + .checkbox_slider.round { + border-radius: 13px; +} + .checkbox_slider.round:before { + border-radius: 50%; +} +/* BUTTON */ + .btn { + width: 306px; + height: 30px; + text-align: center; + font: normal normal bold 14px/5px sans-serif; + color: #000000; + background-color: white; + border-radius: 5px; + border: 1px solid #343a40; +} + .btn:hover { + background-color: #FA6831; + color: white; +} +/* BOTTON table */ + #botton { + width: 100%; + text-align: center; + background: #F5F5F5 0% 0% no-repeat padding-box; + opacity: 1; + bottom: 0; +} +/* ----- styles config ----- */ + .pc1 { + text-align: right; + font: normal normal normal 11px/5px sans-serif; + letter-spacing: 0px; + color: #797979; + opacity: 1; +} + .pc2 { + text-align: left; + font: normal normal normal 12px/5px sans-serif; + letter-spacing: 0px; + color: #000000; + opacity: 1; +} + .pc3 { + text-align: right; + font: normal normal normal bold 12px/17px sans-serif; + letter-spacing: 0px; + color: #2A2A2A; + opacity: 1; +} +/* data table */ + #data { + font-family: Arial, Helvetica, sans-serif; + border-collapse: collapse; + width: 100%; + table-layout: fixed; +} + #data td, #data th { + padding: 8px; +} + #data th { + padding-top: 12px; + padding-bottom: 12px; + text-align: left; +} +/* BUTTON */ + .btn_save { + width: 69px; + height: 24px; + text-align: center; + font: normal normal bold 14px/5px sans-serif; + color: #000000; + background-color: white; + border-radius: 5px; + border: 1px solid #343a40; +} + .btn_save:hover { + background-color: #FA6831; + color: white; +} +/* RANGE */ + .slider { + -webkit-appearance: none; + width: 133px; + height: 10px; + border-radius: 5px; + background: linear-gradient(to right, #d3d3d3 50%, #FA6831 50%); + outline: none; +} + .slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: #FA6831; + cursor: pointer; +} + .slider::-moz-range-thumb { + width: 16px; + height: 16px; + border-radius: 50%; + background: #FA6831; + cursor: pointer; +} +/* ----- styles wifi ----- */ + .w1 { + text-align: left; + font: normal normal bold 12px/17px sans-serif; + letter-spacing: 0px; + color: #2A2A2A; + opacity: 1; +} + .w2 { + font: normal normal normal 11px/5px sans-serif; + letter-spacing: 0px; + color: #797979; + opacity: 1; +} + .w2 span { + vertical-align: middle; +} + #center_tb { + border-collapse: collapse; + width: 100%; + table-layout: fixed; + text-align: left; +} +/* wifi_ntw table */ + #wifi_ntw { + font: normal normal normal 12px/5px sans-serif; + border-collapse: collapse; + width: 100%; + table-layout: fixed; + text-align: left; +} + #wifi_ntw td { + border-bottom: 1px solid #ddd; + padding: 8px; +} + #wifi_ntw tr:nth-child(even) { + background: #F8F8F8 0% 0% no-repeat padding-box; +} + #wifi_ntw tr:hover { + background-color: #ddd; +} + #wifi_ntw th { + background-color: transparent; + text-align: left; + font: normal normal bold 13px/11px sans-serif; + letter-spacing: 0px; + color: #2A2A2A; + opacity: 1; +} + #wifi_ntw tr { + border-bottom: 1px solid #ccc; +} + #wifi_ntw img { + width: 19px; + height: 12px; +} +/* BUTTON */ + .btn_save_w { + width: 178px; + height: 24px; + text-align: center; + font: normal normal bold 14px/5px sans-serif; + color: #000000; + background-color: white; + border-radius: 5px; + border: 1px solid #343a40; +} + .btn_save_w:hover { + background-color: #FA6831; + color: white; +} +/* ----- styles auth ----- */ + .pa1 { + text-align: right; + font: normal normal normal 11px/5px sans-serif; + letter-spacing: 0px; + color: #797979; + opacity: 1; +} + .pa2 { + text-align: left; + font: normal normal bold 12px/17px sans-serif; + letter-spacing: 0px; + color: #2A2A2A; + opacity: 1; +} +.pa3 { + text-align: right; + font: normal normal normal bold 12px/17px sans-serif; + letter-spacing: 0px; + color: #2A2A2A; + opacity: 1; +} + +/* BUTTON */ + .btn_save_a { + width: 178px; + height: 24px; + text-align: center; + font: normal normal bold 14px/5px sans-serif; + color: #000000; + background-color: white; + border-radius: 5px; + border: 1px solid #343a40; +} + .btn_save_a:hover { + background-color: #FA6831; + color: white; +} + .toggle-password { + position: relative; + cursor: pointer; +} + .toggle-password img { + position: absolute; + top: 50%; + right: 0; + transform: translateY(-50%); +} + .password-container { + display: flex; + align-items: center; +} + #auth_password.reveal { + -webkit-text-security: none; +} + #auth_password { + -webkit-text-security: disc; +} +/* ----- styles system ----- */ + .ps1 { + text-align: right; + font: normal normal normal 11px/5px sans-serif; + letter-spacing: 0px; + color: #797979; + opacity: 1; +} + .ps2 { + text-align: left; + font: normal normal normal 12px/5px sans-serif; + letter-spacing: 0px; + color: #000000; + opacity: 1; +} +.ps3 { + text-align: right; + font: normal normal normal bold 12px/17px sans-serif; + letter-spacing: 0px; + color: #2A2A2A; + opacity: 1; +} +/* data table */ + #data { + font-family: Arial, Helvetica, sans-serif; + border-collapse: collapse; + width: 100%; + table-layout: fixed; +} + #data td, #data th { + padding: 8px; +} + #data th { + padding-top: 12px; + padding-bottom: 12px; + text-align: left; +} +/* update table */ + update { + font-family: Arial, Helvetica, sans-serif; + border-collapse: collapse; + width: 100%; + table-layout: fixed; +} + #update td, #update th { + padding: 8px; +} + #update th { + padding-top: 12px; + padding-bottom: 12px; + text-align: left; +} +/* BUTTON */ + .btn_update { + width: 178px; + height: 24px; + text-align: center; + font: normal normal bold 14px/5px sans-serif; + color: #000000; + background-color: white; + border-radius: 5px; + border: 1px solid #343a40; +} + .btn_update:hover { + background-color: #FA6831; + color: white; +} + .underlined-text { + text-decoration: underline; + color: blue; + cursor: pointer; +} +/* progress bar*/ + .progress-container { + width: 100%; + background-color: #ccc; +} + .progress-bar { + width: 0px; + height: 15px; + background-color: #FA6831; + text-align: center; + line-height: 15px; + color: white; +} + +/* advanced cam cfg */ +.content { + display: none; +} + +.btn_collapsible { + width: 300px; + height: 24px; + text-align: center; + font: normal normal bold 14px/5px sans-serif; + color: #000000; + background-color: white; + border-radius: 5px; + border: 1px solid #343a40; +} +.btn_collapsible:hover { + background-color: #FA6831; + color: white; +} +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char scripts_js[] PROGMEM = R"rawliteral( +function get_data(val) { + jQuery.ajax({ + url: 'json_input', + type: 'GET', + timeout: 5000, + success: function(data) { + console.log("Incommming data: "); + console.log(data); + var obj = JSON.parse(data); + console.log(obj); + + if (!document.querySelector('#light-icon img')) { + var img = document.createElement('img'); + img.src = (obj.led == "true") ? 'light-on-icon.svg' : 'light-off-icon.svg'; + document.getElementById('light-icon').appendChild(img); + } + + if (val == "config") { + $("#fingerprint").text(obj.fingerprint); + $("#refreshInterval").text(obj.refreshInterval); + document.getElementById('tokenid').value = obj.token; + document.getElementById('refreshid').value = obj.refreshInterval; + document.getElementById('photo_qualityid').value = obj.photoquality; + document.getElementById('framesizeid').value = obj.framesize; + document.getElementById('brightnessid').value = obj.brightness; + document.getElementById('contrastid').value = obj.contrast; + document.getElementById('saturationid').value = obj.saturation; + document.getElementById('hmirrorid').checked = obj.hmirror; + document.getElementById('vflipid').checked = obj.vflip; + document.getElementById('lencid').checked = obj.lensc; + document.getElementById('exposure_ctrlid').checked = obj.exposure_ctrl; + document.getElementById('awbid').checked = obj.awb; + document.getElementById('awb_gainid').checked = obj.awb_gain; + document.getElementById('wb_modeid').value = obj.wb_mode; + document.getElementById('ledid').checked = obj.led; + document.getElementById('flashid').checked = obj.flash; + document.getElementById('flash_timeid').value = obj.flash_time; + document.getElementById('bpcid').checked = obj.bpc; + document.getElementById('wpcid').checked = obj.wpc; + document.getElementById('raw_gamaid').checked = obj.raw_gama; + document.getElementById('aec2id').checked = obj.aec2; + document.getElementById('ae_levelid').value = obj.ae_level; + document.getElementById('aec_valueid').value = obj.aec_value; + document.getElementById('gain_ctrlid').checked = obj.gain_ctrl; + document.getElementById('agc_gainid').value = obj.agc_gain; + document.getElementById("flash_time_value").innerText = obj.flash_time; + document.getElementById("aec_value_value").innerText = obj.aec_value; + $("#status_hmirror").text((obj.hmirror == "true") ? "On" : "Off"); + $("#status_vflip").text((obj.vflip == "true") ? "On" : "Off"); + $("#status_lensc").text((obj.lensc == "true") ? "On" : "Off"); + $("#status_exposure_ctrl").text((obj.exposure_ctrl == "true") ? "On" : "Off"); + $("#status_awb").text((obj.awb == "true") ? "On" : "Off"); + $("#status_awb_gain").text((obj.awb_gain == "true") ? "On" : "Off"); + $("#status_led").text((obj.led == "true") ? "On" : "Off"); + $("#status_flash").text((obj.flash == "true") ? "On" : "Off"); + $("#status_bpc").text((obj.bpc == "true") ? "On" : "Off"); + $("#status_wpc").text((obj.wpc == "true") ? "On" : "Off"); + $("#status_raw_gama").text((obj.raw_gama == "true") ? "On" : "Off"); + $("#status_aec2").text((obj.aec2 == "true") ? "On" : "Off"); + $("#status_gain_ctrl").text((obj.gain_ctrl == "true") ? "On" : "Off"); + sliderCheck(); + } + + if (val == "auth") { + document.getElementById('authid').checked = obj.auth; + $("#status_auth").text((obj.auth == "true") ? "On" : "Off"); + document.getElementById('auth_username').value = obj.auth_username; + } + + if (val == "wifi") { + $("#ssid").text(obj.ssid); + $("#rssi").text(obj.rssi); + $("#rssi_percentage").text(obj.rssi_percentage); + $("#ip").text(obj.ip); + $("#mdns").text(obj.mdns); + $("#wifi_network_status").text(obj.wifi_network_status); + + if (!document.querySelector('#main-wifi-signal wifi_img')) { + var wifi_img = document.createElement('wifi_img'); + wifi_img.width = 19; + wifi_img.height = 12; + wifi_img.src = getIconPath(obj.rssi); + document.getElementById('main-wifi-signal').appendChild(wifi_img); + } + } + + if (val == "system") { + $("#uptime").text(obj.uptime); + $("#sw_ver").text(obj.sw_ver); + $("#sw_build").text(obj.sw_build); + $("#last_upload_status").text(obj.last_upload_status); + $("#wifi_mode").text(obj.wifi_mode); + $("#sw_new_ver").text(obj.sw_new_ver); + $("#service_ap_ssid").text(obj.service_ap_ssid); + document.getElementById('mdnsid').value = obj.mdns; + document.getElementById('loglevelid').value = obj.log_level; + } + }, + error: function(html) { + console.log("json Timeout or error"); + //alert("jquery timeout or comunication error"); + } + }); +} + +function sliderCheck() { + var ranges = document.querySelectorAll(".slider"); + ranges.forEach(function(range) { + var percent = (range.value - range.min) / (range.max - range.min) * 100; + var gradient = "linear-gradient(to right, #FA6831 " + percent + "%, #d3d3d3 " + percent + "%)"; + range.style.background = gradient; + range.oninput = function() { + var percent = (this.value - this.min) / (this.max - this.min) * 100; + var gradient = "linear-gradient(to right, #FA6831 " + percent + "%, #d3d3d3 " + percent + "%)"; + this.style.background = gradient; + } + }); +} + +function getIconPath(rssi) { + let path; + if (rssi == 0) { + path = 'wifi-icon-0.svg'; + } else if (rssi <= -70) { + path = 'wifi-icon-1.svg'; + } else if (rssi > -70 && rssi <= -60) { + path = 'wifi-icon-2.svg'; + } else if (rssi > -60 && rssi <= -50) { + path = 'wifi-icon-3.svg'; + } else { + path = 'wifi-icon-4.svg'; + } + return path; +} + +var OpenImageclickCount = 0; + +function openImage() { + var img = document.getElementById("photo"); + if (OpenImageclickCount % 2 == 0) { + img.style.position = "fixed"; + img.style.top = "5%"; + img.style.left = "5%"; + img.style.width = "auto"; + img.style.height = "auto"; + img.style.maxWidth = "100%"; + img.style.maxHeight = "90%"; + img.style.zIndex = "9999"; + } else { + img.style.position = ""; + img.style.top = ""; + img.style.left = ""; + img.style.width = ""; + img.style.height = ""; + img.style.zIndex = ""; + } + OpenImageclickCount++; +} + +function actionButton(url, reload, msg) { + var xhr = new XMLHttpRequest(); + + if (msg != '') { + alert(msg); + } + + xhr.onreadystatechange = function() { + if (xhr.readyState == 4 && xhr.status == 200) { + if (reload == true) { + setTimeout(function() { + location.reload(); + }, 200); + } + } + }; + + xhr.open('GET', url, true); + xhr.send(); +} + +function setActive(link) { + var links = document.querySelectorAll('#links li a'); + links.forEach(function(item) { + item.classList.remove('active'); + }); + link.classList.add('active'); +} + +var links = document.querySelectorAll('#links li a'); +links.forEach(function(link) { + link.addEventListener('click', function() { + setActive(link); + }); +}); + +function addClickListener(id) { + var link = document.getElementById(id); + if (!link.hasOwnProperty('clickListener')) { + link.addEventListener('click', function(event) { + event.preventDefault(); + window.open(link.href, '_blank'); + }); + link.clickListener = true; + } +} + +/* wifi page */ +function setWifi(val_ssid, val_pass) { + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", "wifi_cfg?wifi_ssid=" + encodeURIComponent(val_ssid) + "&wifi_pass=" + encodeURIComponent(val_pass), false); + xmlHttp.send(null); + alert(xmlHttp.responseText); + get_data("wifi"); +} + +function scanWifi() { + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", "wifi_scan?", false); + xmlHttp.send(null); + alert(xmlHttp.responseText); + get_data("wifi"); + setTimeout(function() { + GetDataAndPrintTableWiFi(); + }, 8000); +} + +function GetDataAndPrintTableWiFi() { + $("#wifi_ntw").find("tr:gt(0)").remove(); + $.ajax({ + url: 'json_wifi', + type: 'GET', + timeout: 15000, + dataType: 'json', + data: {}, + + success: function(data) { + for (var i = 0; i < data.length; i++) { + const IconName = "wifi-icon-" + i; + var row = $('' + data[i].ssid + '
' + data[i].channel + '' + data[i].encryption + ''); + $('#wifi_ntw').append(row); + + if (!document.querySelector('#' + IconName + ' img')) { + var img = document.createElement('img'); + img.src = getIconPath(data[i].rssi); + document.getElementById(IconName).prepend(img); + document.getElementById(IconName).append(data[i].rssi_percentage); + document.getElementById(IconName).append("% / "); + document.getElementById(IconName).append(data[i].rssi); + document.getElementById(IconName).append("dBm"); + } + } + }, + error: function(jqXHR, textStatus, errorThrown) { + console.log('Error:' + textStatus + '-' + errorThrown); + } + }); +} + +/* auth page */ +function setAuth(val_name, val_pass) { + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", "basicauth_cfg?" + "auth_username=" + encodeURIComponent(val_name) + "&auth_password=" + encodeURIComponent(val_pass), false); + xmlHttp.send(null); + alert(xmlHttp.responseText); + get_data("auth"); +} + +function changeValue(val, url, reload) { + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", url + val, false); + xmlHttp.send(null); + if ((url == "set_int?refresh=") || (url == "set_token?token=") || (url == "set_mdns?mdns=")) { + alert(xmlHttp.responseText); + } + if (url == "set_flash_time?flash_time=") { + document.getElementById("flash_time_value").innerText = val; + } + get_data(reload); +} + +function togglePasswordVisibility() { + const passwordInput = document.getElementById("auth_password"); + const eyeIcon = document.getElementById("eye-icon"); + if (passwordInput.getAttribute("type") === "password") { + passwordInput.setAttribute("type", "text"); + passwordInput.classList.add("reveal"); + eyeIcon.src = "eye-slash.svg"; + eyeIcon.alt = "Hide Password"; + } else { + passwordInput.setAttribute("type", "password"); + passwordInput.classList.remove("reveal"); + eyeIcon.src = "eye.svg"; + eyeIcon.alt = "Show Password"; + } +} + +/* system page */ +if (typeof uploadingFirmware === 'undefined') { + var uploadingFirmware = false; +} + +if (typeof FileSize === 'undefined') { + var FileSize = 0; +} + +function uploadFile() { + alert("Started updating..."); + const firmwareInput = document.getElementById('firmwareInput'); + const statusDiv = document.getElementById('status'); + const file = firmwareInput.files[0]; + FileSize = file.size; + SetFirmwareSize(file.size); + + if (file) { + statusDiv.innerText = 'Updating...'; + uploadingFirmware = true; + const formData = new FormData(); + formData.append('firmware', file); + + fetch('/upload', { + method: 'POST', + body: formData, + }) + .then((response) => { + if (response.ok) { + response.text().then((data) => { + const jsonData = JSON.parse(data); + updateProgress(); + uploadingFirmware = false; + if (jsonData.errorMessage) { + alert(`Error message: ${jsonData.errorMessage}`); + } + }); + } else { + uploadingFirmware = false; + response.text().then((errorMessage) => { + alert(`Error message: ${errorMessage}`); + }); + } + }) + .catch((error) => { + console.error('Error:', error); + uploadingFirmware = false; + }); + } else { + statusDiv.innerText = 'No file selected'; + } +} + +function SetFirmwareSize(val) { + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", "set_firmware_size?size=" + val, false); + xmlHttp.send(null); +} + +function updateProgress() { + if (!uploadingFirmware) { + return; + } + + fetch('/UpdateProcessing', { + method: 'GET', + }) + .then((response) => { + if (response.ok) { + return response.json(); + } else { + throw new Error('Failed to fetch progress'); + } + }) + .then((data) => { + const statusDiv = document.getElementById('status'); + var progressBar = document.getElementById("myProgressBar"); + progressBar.style.width = data.processed_percent + "%"; + progressBar.innerHTML = data.processed_percent + "%"; + + statusDiv.innerText = data.message; + uploadingFirmware = data.updating; + if (data.updating == false && !updateCompleted) { + alert('Operation done. Please reboot MCU.'); + uploadingFirmware = false; + updateCompleted = true; + clearInterval(updateInterval); + } + }) + .catch((error) => { + console.error('Error:', error); + var progressBar = document.getElementById("myProgressBar"); + progressBar.innerHTML = "Error"; + clearInterval(updateInterval); + }); +} + +function checkUpdate() { + var xmlHttp = new XMLHttpRequest(); + alert("Connecting to server... Please wait several second"); + xmlHttp.open("GET", "/check_web_ota_update", false); + xmlHttp.send(null); + alert(xmlHttp.responseText); + get_data("system"); +} + +function updateWeb() { + alert("Started updating from cloud."); + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", "web_ota_update?update=true", false); + xmlHttp.send(null); + uploadingFirmware = true; +} + +function validatePasswords() { + var password = document.getElementById("auth_password").value; + var confirmPassword = document.getElementById("auth_password_confirm").value; + var saveButton = document.querySelector(".btn_save_a"); + var passwordStatus = document.getElementById("pass_match"); + + if (password === confirmPassword) { + passwordStatus.innerHTML = "✔️"; + saveButton.disabled = false; + } else { + passwordStatus.innerHTML = "❌"; + saveButton.disabled = true; + } +} + +if (document.getElementById("auth_password")) { + document.getElementById("auth_password").addEventListener("input", validatePasswords); +} + +if (document.getElementById("auth_password_confirm")) { + document.getElementById("auth_password_confirm").addEventListener("input", validatePasswords); +} + +if ((document.getElementById("auth_password")) && (document.getElementById("auth_password_confirm"))) { + validatePasswords(); +} + +function setupCollapsibleButtons() { + $(".btn_collapsible").click(function(){ + $(this).toggleClass("active"); + var content = $(this).parent().next(); + if (content.css("display") === "block") { + content.css("display", "none"); + } else { + content.css("display", "block"); + } + }); +} +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char license_html[] PROGMEM = R"rawliteral( + + +

The software for device falls under the GPL-3.0 license terms. To read the license terms please visit this page.

+ + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char gtac_html[] PROGMEM = R"rawliteral( + + +

To read the General Terms and Conditions, please visit this page.

+ + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char privacypolicy_html[] PROGMEM = R"rawliteral( + + +

To read the Privacy Policy, please visit this page.

+ + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char cookies_html[] PROGMEM = R"rawliteral( + + +

To read the Cookie policy, please visit this page.

+ + +)rawliteral"; + +#endif + +/* EOF */ diff --git a/ESP32_PrusaConnectCam/WebPage_Icons.h b/ESP32_PrusaConnectCam/WebPage_Icons.h new file mode 100644 index 0000000..11960e3 --- /dev/null +++ b/ESP32_PrusaConnectCam/WebPage_Icons.h @@ -0,0 +1,95 @@ +/** + @file WebPage_Icons.h + + @brief Here are all icons for web page + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + + @bug: no know bug +*/ + +#ifndef _WEB_PAGE_ICONS_H_ +#define _WEB_PAGE_ICONS_H_ + +/* ------------------------------------------------------------------------------------------------------------ */ +const char esp32_cam_logo_svg[] PROGMEM = R"rawliteral( + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char github_icon_svg[] PROGMEM = R"rawliteral( + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char light_icon_on_svg[] PROGMEM = R"rawliteral( + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char light_icon_off_svg[] PROGMEM = R"rawliteral( + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char refresh_icon_svg[] PROGMEM = R"rawliteral( + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char reboot_icon_svg[] PROGMEM = R"rawliteral( + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char wifi_icon_0_svg[] PROGMEM = R"rawliteral( + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char wifi_icon_1_svg[] PROGMEM = R"rawliteral( + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char wifi_icon_2_svg[] PROGMEM = R"rawliteral( + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char wifi_icon_3_svg[] PROGMEM = R"rawliteral( + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char wifi_icon_4_svg[] PROGMEM = R"rawliteral( + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char eye_slash_svg[] PROGMEM = R"rawliteral( + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char eye_svg[] PROGMEM = R"rawliteral( + +)rawliteral"; + +/* ------------------------------------------------------------------------------------------------------------ */ +const char favicon_svg[] PROGMEM = R"rawliteral( + + + + + + + +)rawliteral"; + + +#endif + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/camera.cpp b/ESP32_PrusaConnectCam/camera.cpp new file mode 100644 index 0000000..e11549c --- /dev/null +++ b/ESP32_PrusaConnectCam/camera.cpp @@ -0,0 +1,988 @@ +/** + @file camera.cpp + + @brief Library for working with a camera + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ +#include "camera.h" + +Camera SystemCamera(&SystemConfig, &SystemLog, FLASH_GPIO_NUM); + +/** + @brief Init camera constructor + @param Configuration* - pointer to Configuration class + @param Logs* - pointer to Logs class + @param uint8_t - flash pin + @return none +*/ +Camera::Camera(Configuration* i_conf, Logs* i_log, uint8_t i_FlashPin) { + config = i_conf; + log = i_log; + CameraFlashPin = i_FlashPin; + StreamOnOff = false; + frameBufferSemaphore = xSemaphoreCreateMutex(); +} + +/** + @brief Init the camera module and set the camera configuration + @param none + @return none +*/ +void Camera::Init() { + log->AddEvent(LogLevel_Info, "Init camera lib"); + + log->AddEvent(LogLevel_Info, "Init GPIO"); + ledcSetup(FLASH_PWM_CHANNEL, FLASH_PWM_FREQ, FLASH_PWM_RESOLUTION); + ledcAttachPin(FLASH_GPIO_NUM, FLASH_PWM_CHANNEL); + ledcWrite(FLASH_PWM_CHANNEL, FLASH_OFF_STATUS); + + InitCameraModule(); + ApplyCameraCfg(); +} + +/** + @brief Init the camera module + @param none + @return none +*/ +void Camera::InitCameraModule() { + log->AddEvent(LogLevel_Info, "Init camera module"); + /* Turn-off the 'brownout detector' */ + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); + esp_err_t err; + + CameraConfig.ledc_channel = LEDC_CHANNEL_0; + CameraConfig.ledc_timer = LEDC_TIMER_0; + CameraConfig.pin_d0 = Y2_GPIO_NUM; + CameraConfig.pin_d1 = Y3_GPIO_NUM; + CameraConfig.pin_d2 = Y4_GPIO_NUM; + CameraConfig.pin_d3 = Y5_GPIO_NUM; + CameraConfig.pin_d4 = Y6_GPIO_NUM; + CameraConfig.pin_d5 = Y7_GPIO_NUM; + CameraConfig.pin_d6 = Y8_GPIO_NUM; + CameraConfig.pin_d7 = Y9_GPIO_NUM; + CameraConfig.pin_xclk = XCLK_GPIO_NUM; + CameraConfig.pin_pclk = PCLK_GPIO_NUM; + CameraConfig.pin_vsync = VSYNC_GPIO_NUM; + CameraConfig.pin_href = HREF_GPIO_NUM; + CameraConfig.pin_sccb_sda = SIOD_GPIO_NUM; + CameraConfig.pin_sccb_scl = SIOC_GPIO_NUM; + CameraConfig.pin_pwdn = PWDN_GPIO_NUM; + CameraConfig.pin_reset = RESET_GPIO_NUM; + CameraConfig.xclk_freq_hz = 16500000; // or 3000000; 16500000; 20000000 + CameraConfig.pixel_format = PIXFORMAT_JPEG; /* YUV422,GRAYSCALE,RGB565,JPEG */ + + /* OV2640 + FRAMESIZE_QVGA (320 x 240) + FRAMESIZE_CIF (352 x 288) + FRAMESIZE_VGA (640 x 480) + FRAMESIZE_SVGA (800 x 600) + FRAMESIZE_XGA (1024 x 768) + FRAMESIZE_SXGA (1280 x 1024) + FRAMESIZE_UXGA (1600 x 1200) + */ + + CameraConfig.frame_size = TFrameSize; /* FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA */ + CameraConfig.jpeg_quality = PhotoQuality; /* 10-63 lower number means higher quality */ + CameraConfig.fb_count = 1; /* picture frame buffer alocation */ + CameraConfig.grab_mode = CAMERA_GRAB_LATEST; /* CAMERA_GRAB_WHEN_EMPTY or CAMERA_GRAB_LATEST */ + + if (CameraConfig.fb_location == CAMERA_FB_IN_DRAM) { + log->AddEvent(LogLevel_Verbose, "Camera frame buffer location: DRAM"); + } else if (CameraConfig.fb_location == CAMERA_FB_IN_PSRAM) { + log->AddEvent(LogLevel_Verbose, "Camera frame buffer location: PSRAM"); + } else { + log->AddEvent(LogLevel_Verbose, "Camera frame buffer location: Unknown"); + } + + /* Camera init */ + err = esp_camera_init(&CameraConfig); + if (err != ESP_OK) { + log->AddEvent(LogLevel_Warning, "Camera init failed. Error: " + String(err, HEX)); + log->AddEvent(LogLevel_Warning, "Reset ESP32-cam!"); + ESP.restart(); + } +} + +/** + @brief Load camera CFG from EEPROM + @param none + @return none +*/ +void Camera::LoadCameraCfgFromEeprom() { + log->AddEvent(LogLevel_Info, "Load camera CFG from EEPROM"); + PhotoQuality = config->LoadPhotoQuality(); + FrameSize = config->LoadFrameSize(); + TFrameSize = TransformFrameSizeDataType(config->LoadFrameSize()); + brightness = config->LoadBrightness(); + contrast = config->LoadContrast(); + saturation = config->LoadSaturation(); + awb = config->LoadAwb(); + awb_gain = config->LoadAwbGain(); + wb_mode = config->LoadAwbMode(); + aec2 = config->LoadAec2(); + ae_level = config->LoadAeLevel(); + aec_value = config->LoadAecValue(); + gain_ctrl = config->LoadGainCtrl(); + agc_gain = config->LoadAgcGain(); + bpc = config->LoadBpc(); + wpc = config->LoadWpc(); + raw_gama = config->LoadRawGama(); + hmirror = config->LoadHmirror(); + vflip = config->LoadVflip(); + lensc = config->LoadLensCorrect(); + exposure_ctrl = config->LoadExposureCtrl(); + CameraFlashEnable = config->LoadCameraFlashEnable(); + CameraFlashTime = config->LoadCameraFlashTime(); +} + +/** + @brief transform uint8_t from web interface to framesize_t + @param uint8_t - int value from web + @return framesize_t +*/ +framesize_t Camera::TransformFrameSizeDataType(uint8_t i_data) { + framesize_t ret = FRAMESIZE_QVGA; + switch (i_data) { + case 0: + ret = FRAMESIZE_QVGA; + break; + case 1: + ret = FRAMESIZE_CIF; + break; + case 2: + ret = FRAMESIZE_VGA; + break; + case 3: + ret = FRAMESIZE_SVGA; + break; + case 4: + ret = FRAMESIZE_XGA; + break; + case 5: + ret = FRAMESIZE_SXGA; + break; + case 6: + ret = FRAMESIZE_UXGA; + break; + default: + ret = FRAMESIZE_QVGA; + log->AddEvent(LogLevel_Warning, "Bad frame size. Set default value. " + String(i_data)); + break; + } + + return ret; +} + +/** + @brief Function set flash status + @param bool i_data - true = on, false = off + @return none +*/ +void Camera::SetFlashStatus(bool i_data) { + if (true == i_data) { + ledcWrite(FLASH_PWM_CHANNEL, FLASH_ON_STATUS); + } else if (false == i_data) { + ledcWrite(FLASH_PWM_CHANNEL, FLASH_OFF_STATUS); + } +} + +/** + @brief Function get flash status + @param none + @return bool - true = on, false = off +*/ +bool Camera::GetFlashStatus() { + if (ledcRead(FLASH_PWM_CHANNEL) == FLASH_OFF_STATUS) { + return false; + } else if (ledcRead(FLASH_PWM_CHANNEL) == FLASH_ON_STATUS) { + return true; + } + + return false; +} + +/** + @brief Function set camer acfg + @param none + @return none +*/ +void Camera::ApplyCameraCfg() { + log->AddEvent(LogLevel_Info, "Set camera CFG"); + + /* sensor configuration */ + sensor_t* sensor = esp_camera_sensor_get(); + sensor->set_brightness(sensor, brightness); // -2 to 2 + sensor->set_contrast(sensor, contrast); // -2 to 2 + sensor->set_saturation(sensor, saturation); // -2 to 2 + sensor->set_special_effect(sensor, 0); // 0 to 6 (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia) + sensor->set_whitebal(sensor, awb); // automatic white balancing 0 = disable , 1 = enable + sensor->set_awb_gain(sensor, awb_gain); // automatic white balancing gain 0 = disable , 1 = enable + sensor->set_wb_mode(sensor, wb_mode); // white balancing mode 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home) + sensor->set_exposure_ctrl(sensor, exposure_ctrl); // exposition controll 0 = disable , 1 = enable + sensor->set_aec2(sensor, aec2); // enable exposition controll 0 = disable , 1 = enable + sensor->set_ae_level(sensor, ae_level); // automatic exposition level -2 to 2 + sensor->set_aec_value(sensor, aec_value); // expozition time - 0 to 1200 + sensor->set_gain_ctrl(sensor, gain_ctrl); // automatic gain controll 0 = disable , 1 = enable + sensor->set_agc_gain(sensor, agc_gain); // automatic gain controll level 0 to 30 + sensor->set_gainceiling(sensor, (gainceiling_t)0); // maximum gain 0 to 6 + sensor->set_bpc(sensor, bpc); // bad pixel correction 0 = disable , 1 = enable + sensor->set_wpc(sensor, wpc); // white pixel correction 0 = disable , 1 = enable + sensor->set_raw_gma(sensor, raw_gama); // raw gama correction 0 = disable , 1 = enable + sensor->set_lenc(sensor, lensc); // lens correction 0 = disable , 1 = enable + sensor->set_hmirror(sensor, hmirror); // horizontal mirror 0 = disable , 1 = enable + sensor->set_vflip(sensor, vflip); // vertical flip 0 = disable , 1 = enable + sensor->set_dcw(sensor, 1); // 0 = disable , 1 = enable + sensor->set_colorbar(sensor, 0); // external collor lines, 0 = disable , 1 = enable +} + +/** + @brief Function for reinit camera module + @param none + @return none +*/ +void Camera::ReinitCameraModule() { + esp_err_t err = esp_camera_deinit(); + if (err != ESP_OK) { + log->AddEvent(LogLevel_Warning, "Camera error deinit camera module. Error: " + String(err, HEX)); + } + InitCameraModule(); + ApplyCameraCfg(); +} + +/** + @brief Capture Photo and Save it to string array + @param none + @return none +*/ +void Camera::CapturePhoto() { + if (false == StreamOnOff) { + if (xSemaphoreTake(frameBufferSemaphore, portMAX_DELAY)) { + + /* check flash, and enable FLASH LED */ + if (true == CameraFlashEnable) { + ledcWrite(FLASH_PWM_CHANNEL, FLASH_ON_STATUS); + delay(CameraFlashTime); + } + + /* get train photo */ + FrameBuffer = esp_camera_fb_get(); + esp_camera_fb_return(FrameBuffer); + + do { + log->AddEvent(LogLevel_Info, "Taking photo..."); + + /* capture final photo */ + FrameBuffer = esp_camera_fb_get(); + if (!FrameBuffer) { + log->AddEvent(LogLevel_Error, "Camera capture failed! photo"); + return; + + } else { + /* copy photo from buffer to string array */ + char buf[150] = { '\0' }; + uint8_t ControlFlag = (uint8_t)FrameBuffer->buf[15]; + sprintf(buf, "The picture has been saved. Size: %d bytes, Photo resolution: %zu x %zu", FrameBuffer->len, FrameBuffer->width, FrameBuffer->height); + log->AddEvent(LogLevel_Info, buf); + + /* check corrupted photo */ + if (ControlFlag != 0x00) { + log->AddEvent(LogLevel_Error, "Camera capture failed! photo " + String(ControlFlag, HEX)); + FrameBuffer->len = 0; + + } else { + log->AddEvent(LogLevel_Info, "Photo OK! " + String(ControlFlag, HEX)); + } + + esp_camera_fb_return(FrameBuffer); + } + + /* check if photo is correctly saved */ + } while (!(FrameBuffer->len > 100)); + + /* Disable flash */ + if (true == CameraFlashEnable) { + delay(CameraFlashTime); + ledcWrite(FLASH_PWM_CHANNEL, FLASH_OFF_STATUS); + } + xSemaphoreGive(frameBufferSemaphore); + } + } +} + +/** + @brief Capture Stream + @param camera_fb_t * - pointer to camera_fb_t + @return none +*/ +void Camera::CaptureStream(camera_fb_t* i_buf) { + if (xSemaphoreTake(frameBufferSemaphore, portMAX_DELAY)) { + do { + /* capture final photo */ + FrameBuffer = esp_camera_fb_get(); + if (!FrameBuffer) { + log->AddEvent(LogLevel_Error, "Camera capture failed! stream"); + i_buf = NULL; + return; + } + + /* check if photo is correctly saved */ + } while (!(FrameBuffer->len > 100)); + *i_buf = *FrameBuffer; + xSemaphoreGive(frameBufferSemaphore); + } +} + +/** + @brief Capture Return Frame Buffer + @param none + @return none +*/ +void Camera::CaptureReturnFrameBuffer() { + esp_camera_fb_return(FrameBuffer); +} + +/** + @brief Set Stream Status + @param bool - true = on, false = off + @return none +*/ +void Camera::SetStreamStatus(bool i_status) { + StreamOnOff = i_status; + log->AddEvent(LogLevel_Info, "Camera video stream: " + String(StreamOnOff)); +} + +/** + @brief Get Stream Status + @param none + @return bool - true = on, false = off +*/ +bool Camera::GetStreamStatus() { + return StreamOnOff; +} + +/** + @brief Set Frame Size + @param uint16_t - frame size + @return none +*/ +void Camera::StreamSetFrameSize(uint16_t i_data) { + StreamAverageSize = (StreamAverageSize + i_data) / 2; +} + +/** + @brief Set average Frame Fps + @param float - frame fps + @return none +*/ +void Camera::StreamSetFrameFps(float i_data) { + StreamAverageFps = (StreamAverageFps + i_data) / 2.0; +} + +/** + @brief Get average stream Frame Size + @param none + @return uint16_t - frame size +*/ +uint16_t Camera::StreamGetFrameAverageSize() { + return StreamAverageSize; +} + +/** + @brief Get average stream Frame Fps + @param none + @return float - frame fps +*/ +float Camera::StreamGetFrameAverageFps() { + return StreamAverageFps; +} + +/** + @brief Clear Frame Data + @param none + @return none +*/ +void Camera::StreamClearFrameData() { + StreamAverageFps = 0.0; + StreamAverageSize = 0; +} + +/** + @brief Get Photo + @param none + @return String - photo +*/ +String Camera::GetPhoto() { + Photo = ""; + for (size_t i = 0; i < FrameBuffer->len; i++) { + Photo += (char)FrameBuffer->buf[i]; + } + return Photo; +} + +/** + @brief Get Photo Frame Buffer + @param none + @return camera_fb_t* - photo frame buffer +*/ +camera_fb_t* Camera::GetPhotoFb() { + return FrameBuffer; +} + +/** + @brief Copy Photo + @param camera_fb_t* - pointer to camera_fb_t + @return none +*/ +void Camera::CopyPhoto(camera_fb_t* i_data) { + *i_data = *FrameBuffer; +} + +/** + @brief Copy Photo + @param String* - pointer to string + @return none +*/ +void Camera::CopyPhoto(String* i_data) { + Photo = ""; + for (size_t i = 0; i < FrameBuffer->len; i++) { + Photo += (char)FrameBuffer->buf[i]; + } + *i_data = Photo; +} + +/** + @brief Set Photo Quality + @param uint8_t - photo quality + @return none +*/ +void Camera::SetPhotoQuality(uint8_t i_data) { + config->SavePhotoQuality(i_data); + PhotoQuality = i_data; + ReinitCameraModule(); +} + +/** + @brief Set Frame Size + @param uint8_t - frame size + @return none +*/ +void Camera::SetFrameSize(uint8_t i_data) { + config->SaveFrameSize(i_data); + FrameSize = i_data; + TFrameSize = TransformFrameSizeDataType(i_data); + ReinitCameraModule(); +} + +/** + @brief Set Brightness + @param int8_t - brightness + @return none +*/ +void Camera::SetBrightness(int8_t i_data) { + config->SaveBrightness(i_data); + brightness = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set Contrast + @param int8_t - contrast + @return none +*/ +void Camera::SetContrast(int8_t i_data) { + config->SaveContrast(i_data); + contrast = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set Saturation + @param int8_t - saturation + @return none +*/ +void Camera::SetSaturation(int8_t i_data) { + config->SaveSaturation(i_data); + saturation = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set automatic white balance + @param bool - automatic white balance + @return none +*/ +void Camera::SetAwb(bool i_data) { + config->SaveAwb(i_data); + awb = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set automatic white balance gain + @param bool - automatic white balance gain + @return none +*/ +void Camera::SetAwbGain(bool i_data) { + config->SaveAwbGain(i_data); + awb_gain = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set automatic white balance mode + @param uint8_t - automatic white balance mode + @return none +*/ +void Camera::SetAwbMode(uint8_t i_data) { + config->SaveAwbMode(i_data); + wb_mode = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set automatic exposure control 2 + @param bool - automatic exposure control 2 + @return none +*/ +void Camera::SetAec2(bool i_data) { + config->SaveAec2(i_data); + aec2 = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set automatic exposure level + @param int8_t - automatic exposure level + @return none +*/ +void Camera::SetAeLevel(int8_t i_data) { + config->SaveAeLevel(i_data); + ae_level = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set automatic exposition control value + @param uint16_t - automatic exposure control value + @return none +*/ +void Camera::SetAecValue(uint16_t i_data) { + config->SaveAecValue(i_data); + aec_value = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set Gain Ctrl + @param bool - gain ctrl + @return none +*/ +void Camera::SetGainCtrl(bool i_data) { + config->SaveGainCtrl(i_data); + gain_ctrl = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set automatic gain control gain + @param uint8_t - automatic gain control gain + @return none +*/ +void Camera::SetAgcGain(uint8_t i_data) { + config->SaveAgcGain(i_data); + agc_gain = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set bad pixel correction + @param bool - bad pixel correction + @return none +*/ +void Camera::SetBpc(bool i_data) { + config->SaveBpc(i_data); + bpc = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set white pixel correction + @param bool - white pixel correction + @return none +*/ +void Camera::SetWpc(bool i_data) { + config->SaveWpc(i_data); + wpc = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set Raw Gama + @param bool - raw gama + @return none +*/ +void Camera::SetRawGama(bool i_data) { + config->SaveRawGama(i_data); + raw_gama = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set horizontal Mirror + @param bool - horizontal mirror + @return none +*/ +void Camera::SetHMirror(bool i_data) { + config->SaveHmirror(i_data); + hmirror = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set V Flip + @param bool - vflip + @return none +*/ +void Camera::SetVFlip(bool i_data) { + config->SaveVflip(i_data); + vflip = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set Lens correction + @param bool - lens correction + @return none +*/ +void Camera::SetLensC(bool i_data) { + config->SaveLensCorrect(i_data); + lensc = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set Exposure Ctrl + @param bool - exposure ctrl + @return none +*/ +void Camera::SetExposureCtrl(bool i_data) { + config->SaveExposureCtrl(i_data); + exposure_ctrl = i_data; + ApplyCameraCfg(); +} + +/** + @brief Set Camera Flash Enable + @param bool - flash enable/disable + @return none +*/ +void Camera::SetCameraFlashEnable(bool i_data) { + config->SaveCameraFlashEnable(i_data); + CameraFlashEnable = i_data; +} + +/** + @brief Set Camera Flash Time + @param uint16_t - flash time + @return none +*/ +void Camera::SetCameraFlashTime(uint16_t i_data) { + config->SaveCameraFlashTime(i_data); + CameraFlashTime = i_data; +} + +/** + @brief Get Photo Quality + @param none + @return uint8_t - photo quality +*/ +uint8_t Camera::GetPhotoQuality() { + return PhotoQuality; +} + +/** + @brief Get Frame Size + @param none + @return uint8_t - frame size +*/ +uint8_t Camera::GetFrameSize() { + return FrameSize; +} + +/** + * @brief transform framesize_t to uint16_t width + * + FRAMESIZE_QVGA (320 x 240) + FRAMESIZE_CIF (352 x 288) + FRAMESIZE_VGA (640 x 480) + FRAMESIZE_SVGA (800 x 600) + FRAMESIZE_XGA (1024 x 768) + FRAMESIZE_SXGA (1280 x 1024) + FRAMESIZE_UXGA (1600 x 1200) + * + * @return uint16_t + */ +uint16_t Camera::GetFrameSizeWidth() { + uint16_t ret = 0; + + switch (FrameSize) { + case 0: + ret = 320; + break; + case 1: + ret = 352; + break; + case 2: + ret = 640; + break; + case 3: + ret = 800; + break; + case 4: + ret = 1024; + break; + case 5: + ret = 1280; + break; + case 6: + ret = 1600; + break; + default: + ret = 320; + break; + } + + return ret; +} + +/** + * @brief transform framesize_t to uint16_t height + * + FRAMESIZE_QVGA (320 x 240) + FRAMESIZE_CIF (352 x 288) + FRAMESIZE_VGA (640 x 480) + FRAMESIZE_SVGA (800 x 600) + FRAMESIZE_XGA (1024 x 768) + FRAMESIZE_SXGA (1280 x 1024) + FRAMESIZE_UXGA (1600 x 1200) + * + * @return uint16_t + */ +uint16_t Camera::GetFrameSizeHeight() { + uint16_t ret = 0; + switch (FrameSize) { + case 0: + ret = 240; + break; + case 1: + ret = 288; + break; + case 2: + ret = 480; + break; + case 3: + ret = 600; + break; + case 4: + ret = 768; + break; + case 5: + ret = 1024; + break; + case 6: + ret = 1200; + break; + default: + ret = 240; + break; + } + + return ret; +} + +/** + @brief Get Brightness + @param none + @return int8_t - brightness +*/ +int8_t Camera::GetBrightness() { + return brightness; +} + +/** + @brief Get Contrast + @param none + @return int8_t - contrast +*/ +int8_t Camera::GetContrast() { + return contrast; +} + +/** + @brief Get Saturation + @param none + @return int8_t - saturation +*/ +int8_t Camera::GetSaturation() { + return saturation; +} + +/** + @brief Get Auto white balance status + @param none + @return bool - Auto white balance status +*/ +bool Camera::GetAwb() { + return awb; +} + +/** + @brief Get Auto white balance gain status + @param none + @return bool - Auto white balance gain status +*/ +bool Camera::GetAwbGain() { + return awb_gain; +} + +/** + @brief Get Auto white balance mode + @param none + @return uint8_t - Auto white balance mode +*/ +uint8_t Camera::GetAwbMode() { + return wb_mode; +} + +/** + @brief Get automatic exposure control 2 status + @param none + @return bool - Automatic exposure control 2 status +*/ +bool Camera::GetAec2() { + return aec2; +} + +/** + @brief Get automatic exposure level + @param none + @return int8_t - Automatic exposure level +*/ +int8_t Camera::GetAeLevel() { + return ae_level; +} + +/** + @brief Get automatic exposure control value + @param none + @return uint16_t - Automatic exposure control value +*/ +uint16_t Camera::GetAecValue() { + return aec_value; +} + +/** + @brief Get Gain Ctrl status + @param none + @return bool - Gain control status +*/ +bool Camera::GetGainCtrl() { + return gain_ctrl; +} + +/** + @brief Get Agc Gaint + @param none + @return uint8_t - agc gain +*/ +uint8_t Camera::GetAgcGaint() { + return agc_gain; +} + +/** + @brief Get Bpc + @param none + @return bool - bpc status +*/ +bool Camera::GetBpc() { + return bpc; +} + +/** + @brief Get Wpc + @param none + @return bool - wpc status +*/ +bool Camera::GetWpc() { + return wpc; +} + +/** + @brief Get Raw Gama value + @param none + @return bool - raw gamma value +*/ +bool Camera::GetRawGama() { + return raw_gama; +} + +/** + @brief Get horizontal Mirror status + @param none + @return bool - horizontal mirror status +*/ +bool Camera::GetHMirror() { + return hmirror; +} + +/** + @brief Get vertical Flip status + @param none + @return bool - vertical flip status +*/ +bool Camera::GetVFlip() { + return vflip; +} + +/** + @brief Get Lens correction status + @param none + @return bool - lens correction status +*/ +bool Camera::GetLensC() { + return lensc; +} + +/** + @brief Get exposure control status + @param none + @return bool - exposure control status +*/ +bool Camera::GetExposureCtrl() { + return exposure_ctrl; +} + +/** + @brief Get Camera Flash Enable status + @param none + @return bool - camera flash enable status +*/ +bool Camera::GetCameraFlashEnable() { + return CameraFlashEnable; +} + +/** + * @brief Get camera flash time + * @param none + * @return uint16_t - camera flash time + */ +uint16_t Camera::GetCameraFlashTime() { + return CameraFlashTime; +} + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/camera.h b/ESP32_PrusaConnectCam/camera.h new file mode 100644 index 0000000..e8efb97 --- /dev/null +++ b/ESP32_PrusaConnectCam/camera.h @@ -0,0 +1,150 @@ +/** + @file camera.h + + @brief Library for working with a camera + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#ifndef _CAMERA_H_ +#define _CAMERA_H_ + +#include "esp_camera.h" +#include "esp_timer.h" +#include "img_converters.h" +#include "soc/soc.h" +#include "soc/rtc_cntl_reg.h" +#include "cfg.h" + +#include "Camera_cfg.h" +#include "Arduino.h" +#include "mcu_cfg.h" +#include "var.h" +#include "log.h" + +class Camera { +private: + uint8_t PhotoQuality; ///< photo quality + uint8_t FrameSize; ///< framesize + int8_t brightness; ///< brightness + int8_t contrast; ///< contrast + int8_t saturation; ///< saturation + bool awb; ///< automatic white balancing + bool awb_gain; ///< automatic white balancing gain + uint8_t wb_mode; ///< white balancing mode + bool aec2; ///< automatic exposition controll + int8_t ae_level; ///< automatic exposition level + uint16_t aec_value; ///< automatic exposition time + bool gain_ctrl; ///< automatic gain + uint8_t agc_gain; ///< automatic gain level + bool bpc; ///< bad pixel correction + bool wpc; ///< white pixel correction + bool raw_gama; ///< raw gama correction + bool hmirror; ///< horizontal mirror + bool vflip; ///< vertical flip + bool lensc; ///< lens corection + bool exposure_ctrl; ///< exposure control + bool CameraFlashEnable; ///< enable/disable camera flash function + uint16_t CameraFlashTime; ///< camera fash duration time + uint8_t CameraFlashPin; ///< GPIO pin for LED + framesize_t TFrameSize; ///< framesize_t type for camera module + + /* OV2640 camera module pinout and cfg*/ + camera_config_t CameraConfig; ///< camera configuration + camera_fb_t *FrameBuffer; ///< frame buffer + String Photo; ///< photo in string format + bool StreamOnOff; ///< stream on/off + SemaphoreHandle_t frameBufferSemaphore; ///< semaphore for frame buffer + float StreamAverageFps; ///< stream average fps + uint16_t StreamAverageSize; ///< stream average size + + Configuration *config; ///< pointer to Configuration object + Logs *log; ///< pointer to Logs object + + void InitCameraModule(); + +public: + Camera(Configuration*, Logs*, uint8_t); + ~Camera(){}; + void Init(); + void ApplyCameraCfg(); + void LoadCameraCfgFromEeprom(); + void ReinitCameraModule(); + void CapturePhoto(); + void CaptureStream(camera_fb_t *); + void CaptureReturnFrameBuffer(); + void SetStreamStatus(bool); + bool GetStreamStatus(); + + void StreamSetFrameSize(uint16_t); + void StreamSetFrameFps(float); + uint16_t StreamGetFrameAverageSize(); + float StreamGetFrameAverageFps(); + void StreamClearFrameData(); + + void CopyPhoto(camera_fb_t *); + void CopyPhoto(String*); + String GetPhoto(); + camera_fb_t *GetPhotoFb(); + framesize_t TransformFrameSizeDataType(uint8_t); + + void SetFlashStatus(bool); + bool GetFlashStatus(); + + void SetPhotoQuality(uint8_t); + void SetFrameSize(uint8_t); + void SetBrightness(int8_t); + void SetContrast(int8_t); + void SetSaturation(int8_t); + void SetAwb(bool); + void SetAwbGain(bool); + void SetAwbMode(uint8_t); + void SetAec2(bool); + void SetAeLevel(int8_t); + void SetAecValue(uint16_t); + void SetGainCtrl(bool); + void SetAgcGain(uint8_t); + void SetBpc(bool); + void SetWpc(bool); + void SetRawGama(bool); + void SetHMirror(bool); + void SetVFlip(bool); + void SetLensC(bool); + void SetExposureCtrl(bool); + void SetCameraFlashEnable(bool); + void SetCameraFlashTime(uint16_t); + + uint8_t GetPhotoQuality(); + uint8_t GetFrameSize(); + uint16_t GetFrameSizeWidth(); + uint16_t GetFrameSizeHeight(); + int8_t GetBrightness(); + int8_t GetContrast(); + int8_t GetSaturation(); + bool GetAwb(); + bool GetAwbGain(); + uint8_t GetAwbMode(); + bool GetAec2(); + int8_t GetAeLevel(); + uint16_t GetAecValue(); + bool GetGainCtrl(); + uint8_t GetAgcGaint(); + bool GetBpc(); + bool GetWpc(); + bool GetRawGama(); + bool GetHMirror(); + bool GetVFlip(); + bool GetLensC(); + bool GetExposureCtrl(); + bool GetCameraFlashEnable(); + uint16_t GetCameraFlashTime(); +}; + +extern Camera SystemCamera; ///< Camera object + +#endif + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/cfg.cpp b/ESP32_PrusaConnectCam/cfg.cpp new file mode 100644 index 0000000..a774765 --- /dev/null +++ b/ESP32_PrusaConnectCam/cfg.cpp @@ -0,0 +1,1072 @@ +/** + @file cfg.cpp + + @brief Library for save and load MCU configuration + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#include "cfg.h" + +Configuration SystemConfig(&SystemLog); + +/** + @info Constructor + @param Logs* - pointer to log module + @return none +*/ +Configuration::Configuration(Logs* i_log) { + Log = i_log; + WiFiMacAddress = WiFi.macAddress(); +} + +/** + @info Init configuration module + @param none + @return none +*/ +void Configuration::Init() { + Log->AddEvent(LogLevel_Info, "Init cfg module: " + String(EEPROM_SIZE)); + //EEPROM.begin(EEPROM_SIZE); + + /* check, when it is first MCU start. If yes, then set default CFG */ + if (CheckFirstMcuStart() == true) { + Log->AddEvent(LogLevel_Warning, "First MCU start! Set factory cfg"); + DefaultCfg(); + SaveFirstMcuStartFlag(CFG_FIRST_MCU_START_NAK); + } + + /* set reset pin */ + pinMode(CFG_RESET_PIN, INPUT_PULLUP); +} + +/** + @info Read MCU cfg from EEPROM + @param none + @return none +*/ +void Configuration::ReadCfg() { + Log->AddEvent(LogLevel_Info, "Load CFG from EEPROM"); + LoadRefreshInterval(); + LoadToken(); + LoadFingerprint(); + LoadPhotoQuality(); + LoadFrameSize(); + LoadBrightness(); + LoadContrast(); + LoadSaturation(); + LoadHmirror(); + LoadVflip(); + LoadLensCorrect(); + LoadExposureCtrl(); + LoadAwb(); + LoadAwbGain(); + LoadAwbMode(); + LoadBpc(); + LoadWpc(); + LoadRawGama(); + LoadWifiSsid(); + LoadWifiPassowrd(); + LoadBasicAuthUsername(); + LoadBasicAuthPassword(); + LoadBasicAuthFlag(); + LoadCameraFlashEnable(); + LoadCameraFlashTime(); + LoadMdnsRecord(); + LoadAec2(); + LoadAeLevel(); + LoadAecValue(); + LoadGainCtrl(); + LoadAgcGain(); + LoadPrusaConnectHostname(); + Log->AddEvent(LogLevel_Info, "Active WiFi client cfg: " + String(CheckActifeWifiCfgFlag() ? "true" : "false")); + Log->AddEvent(LogLevel_Info, "Load CFG from EEPROM done"); +} + +/** + @info Function for check if it's first MCU start + @param none + @return bool - status +*/ +bool Configuration::CheckFirstMcuStart() { + Log->AddEvent(LogLevel_Info, "Read FirstMcuStart: "); + uint8_t flag = EEPROM.read(EEPROM_ADDR_FIRST_MCU_START_FLAG_START); + + if (CFG_FIRST_MCU_START_NAK == flag) { + Log->AddEvent(LogLevel_Info, "It's not first start MCU: " + String(flag)); + return false; + } else { + Log->AddEvent(LogLevel_Warning, "First start MCU!: " + String(flag)); + return true; + } + + return false; +} + +/** + @info save flag about first MCU start. for settings basic auth and more + @param uint8_t - flag + @return none +*/ +void Configuration::SaveFirstMcuStartFlag(uint8_t i_data) { + Log->AddEvent(LogLevel_Info, "Save first MCU start flag: " + String(i_data)); + SaveUint8(EEPROM_ADDR_FIRST_MCU_START_FLAG_START, i_data); +} + +/** + @info Function for set default MCU cfg + @param none + @return none +*/ +void Configuration::DefaultCfg() { + Log->AddEvent(LogLevel_Warning, "+++++++++++++++++++++++++++"); + Log->AddEvent(LogLevel_Warning, "Start set factory cfg!"); + + SaveRefreshInterval(FACTORY_CFG_PHOTO_REFRESH_INTERVAL); + SaveToken(""); + GetFingerprint(); + SavePhotoQuality(FACTORY_CFG_PHOTO_QUALITY); + SaveFrameSize(FACTORY_CFG_FRAME_SIZE); + SaveBrightness(FACTORY_CFG_BRIGHTNESS); + SaveContrast(FACTORY_CFG_CONTRAST); + SaveSaturation(FACTORY_CFG_SATURATION); + SaveHmirror(FACTORY_CFG_H_MIRROR); + SaveVflip(FACTORY_CFG_V_FLIP); + SaveLensCorrect(FACTORY_CFG_LENS_CORRECT); + SaveExposureCtrl(FACTORY_CFG_EXPOSURE_CTRL); + SaveAwb(FACTORY_CFG_AWB); + SaveAwbGain(FACTORY_CFG_AWB_GAIN); + SaveAwbMode(FACTORY_CFG_AWB_MODE); + SaveBpc(FACTORY_CFG_BPC); + SaveWpc(FACTORY_CFG_WPC); + SaveRawGama(FACTORY_CFG_RAW_GAMA); + SaveWifiCfgFlag(CFG_WIFI_SETTINGS_NOT_SAVED); + SaveWifiPassword(""); + SaveWifiSsid(""); + SaveBasicAuthUsername(FACTORY_CFG_WEB_AUTH_USERNAME); + SaveBasicAuthPassword(FACTORY_CFG_WEB_AUTH_PASSWORD); + SaveBasicAuthFlag(FACTORY_CFG_WEB_AUTH_ENABLE); + SaveCameraFlashEnable(FACTORY_CFG_CAMERA_FLASH_ENABLE); + SaveCameraFlashTime(FACTORY_CFG_CAMERA_FLASH_TIME); + SaveMdnsRecord(FACTORY_CFG_MDNS_RECORD_HOST); + SaveAec2(FACTORY_CFG_AEC2); + SaveAeLevel(FACTORY_CFG_AE_LEVEL); + SaveAecValue(FACTORY_CFG_AEC_VALUE); + SaveGainCtrl(FACTORY_CFG_GAIN_CTRL); + SaveAgcGain(FACTORY_CFG_AGC_GAIN); + SaveLogLevel(LogLevel_Info); + SavePrusaConnectHostname(FACTORY_CFG_HOSTNAME); + Log->AddEvent(LogLevel_Warning, "+++++++++++++++++++++++++++"); +} + +/** + @info the function checks whether the configuration for connecting to WI-FI network is saved + @param none + @return bool - status +*/ +bool Configuration::CheckActifeWifiCfgFlag() { + uint8_t flag = EEPROM.read(EEPROM_ADDR_WIFI_ACTIVE_FLAG_START); + Log->AddEvent(LogLevel_Verbose, "Read ActifeWifiCfgFlag: " + String(flag)); + + if (CFG_WIFI_SETTINGS_SAVED == flag) { + return true; + } else { + return false; + } + + return false; +} + +/** + @info Function checks the GPIO pin, which is used to reset the CFG to default + @param none + @return none +*/ +void Configuration::CheckResetCfg() { + Log->AddEvent(LogLevel_Verbose, "Check reset MCU cfg"); + bool ResetPinStatus = digitalRead(CFG_RESET_PIN); + + /* wait 10s to pressed reset pin */ + uint8_t i = 0; + for (i = 0; i < (CFG_RESET_TIME_WAIT / CFG_RESET_LOOP_DELAY); i++) { + Log->AddEvent(LogLevel_Verbose, "Reset pin status: " + String(ResetPinStatus)); + if (ResetPinStatus == HIGH) { + break; + } + + delay(CFG_RESET_LOOP_DELAY); + ResetPinStatus = digitalRead(CFG_RESET_PIN); + } + + /* check if is reset pin grounded more at 10s */ + if (i == (CFG_RESET_TIME_WAIT / CFG_RESET_LOOP_DELAY)) { + Log->AddEvent(LogLevel_Warning, "Reset MCU to factory CFG!"); + + /* wait for ungrounded reset pin, and binking led */ + while (digitalRead(CFG_RESET_PIN) == LOW) { + analogWrite(FLASH_GPIO_NUM, 20); + delay(100); + analogWrite(FLASH_GPIO_NUM, 0); + delay(100); + } + + /* turn off LED, reset cfg, reset MCU */ + analogWrite(FLASH_GPIO_NUM, 0); + DefaultCfg(); + ESP.restart(); + + } else { + Log->AddEvent(LogLevel_Verbose, "Reset MCU cfg false"); + } +} + +/** + @info get Fingerprint value from system + @param none + @return none +*/ +void Configuration::GetFingerprint() { + String Id = ""; + for (size_t i = 0; i < UniqueIDsize; i++) { + Id += String(UniqueID[i]); + //Id += "."; + } + //String Random = String(esp_random()); + String encoded = base64::encode(Id + " " + WiFiMacAddress); + SaveFingerprint(encoded); + Log->AddEvent(LogLevel_Verbose, "UniqueID: " + Id); + Log->AddEvent(LogLevel_Verbose, "WiFi MAC: " + WiFiMacAddress); + //Log->AddEvent(LogLevel_Verbose, "Random number: " + Random); + Log->AddEvent(LogLevel_Warning, "Calculated device fingerprint: " + encoded); +} + +/** + @info Function for save uint8_t to EEPROM + @param uint16_t data address + @param uint8_t data + @return none +*/ +void Configuration::SaveUint8(uint16_t address, uint8_t data) { + EEPROM.write(address, data); + EEPROM.commit(); +} +/** + @info Function for save int8_t to EEPROM + @param uint16_t data address + @param int8_t data + @return none +*/ +void Configuration::SaveInt8(uint16_t address, int8_t data) { + EEPROM.write(address, data); + EEPROM.commit(); +} +/** + @info Function for save bool to EEPROM + @param uint16_t data address + @param bool data + @return none +*/ +void Configuration::SaveBool(uint16_t address, bool data) { + EEPROM.write(address, data); + EEPROM.commit(); +} +/** + @info Function for save uint16_t to EEPROM + @param uint16_t data address + @param uint16_t data + @return none +*/ +void Configuration::SaveUint16(uint16_t address, uint16_t data) { + uint8_t highByte = highByte(data); + uint8_t lowByte = lowByte(data); + + EEPROM.write(address, highByte); + EEPROM.write(address + 1, lowByte); + EEPROM.commit(); +} + +/** + @info Function for save string to EEPROM + @param uint16_t data address + @param uint16_t maximum data length + @param String data + @return none +*/ +void Configuration::SaveString(uint16_t address, uint16_t max_length, String data) { + if (data.length() < max_length) { + /* save data length to first byte */ + EEPROM.write(address, data.length()); + + /* save data */ + for (uint16_t i = address + 1, j = 0; j < data.length(); i++, j++) { + EEPROM.write(i, data.charAt(j)); + } + EEPROM.commit(); + Log->AddEvent(LogLevel_Verbose, "Write string done"); + } else { + Log->AddEvent(LogLevel_Verbose, "Skip write string"); + } +} +/** + @info Function for read uint16_t data from EEPROM + @param uint16_t fist byte address + @return uint16_t data +*/ +uint16_t Configuration::LoadUint16(uint16_t address) { + uint16_t tmp = uint16_t(EEPROM.read(address) << 8) | (EEPROM.read(address + 1)); + return tmp; +} +/** + @info Function for load string from EEPROM + @param uint16_t data address + @param uint16_t maximum data length + @param bool show/hide sensitive data + @return String data +*/ +String Configuration::LoadString(uint16_t address, uint16_t max_length, bool show_sensitive_data) { + String tmp = ""; + uint8_t len = EEPROM.read(address); + + if ((len <= max_length) && (len > 0)) { + for (uint16_t i = address + 1, j = 0; j < len; i++, j++) { + tmp += (char)EEPROM.read(i); + } + } + + String LogMsg = ""; + if (false == show_sensitive_data) { + for (uint16_t i = 0; i <= tmp.length(); i++) { + LogMsg += "*"; + } + } else { + LogMsg = tmp; + } + Log->AddEvent(LogLevel_Info, LogMsg, true, false); + + return tmp; +} + +/** + @info save refresh interval to eeprom + @param uint8_t - refresh interval + @return none +*/ +void Configuration::SaveRefreshInterval(uint8_t i_interval) { + Log->AddEvent(LogLevel_Verbose, "Save RefreshInterval: " + String(i_interval)); + SaveUint8(EEPROM_ADDR_REFRESH_INTERVAL_START, i_interval); +} + +/** + @info save token to eeprom + @param string - token + @return none +*/ +void Configuration::SaveToken(String i_token) { + Log->AddEvent(LogLevel_Verbose, "Save Token[" + String(i_token.length()) + "]: " + i_token); + SaveString(EEPROM_ADDR_TOKEN_START, EEPROM_ADDR_TOKEN_LENGTH, i_token); +} + +/** + @info save fingerprint to EEPROM + @param String - fingerprint + @return none +*/ +void Configuration::SaveFingerprint(String i_fingerprint) { + Log->AddEvent(LogLevel_Verbose, "Save Fingerprint[" + String(i_fingerprint.length()) + "]: " + i_fingerprint); + SaveString(EEPROM_ADDR_FINGERPRINT_START, EEPROM_ADDR_FINGERPRINT_LENGTH, i_fingerprint); +} + +/** + @info save photo quality to EEPROM + @param uint8_t - photo quality + @return none +*/ +void Configuration::SavePhotoQuality(uint8_t i_data) { + Log->AddEvent(LogLevel_Verbose, "Save PhotoQuality: " + String(i_data)); + SaveUint8(EEPROM_ADDR_PHOTO_QUALITY_START, i_data); +} + +/** + @info save framesize to EEPROM + @param uint8_t - framesize + @return none +*/ +void Configuration::SaveFrameSize(uint8_t i_data) { + Log->AddEvent(LogLevel_Verbose, "Save FrameSize: " + String(i_data)); + SaveUint8(EEPROM_ADDR_FRAMESIZE_START, i_data); +} + +/** + @info save brightness to EEPROM + @param uint8_t - brightness + @return none +*/ +void Configuration::SaveBrightness(int8_t i_data) { + Log->AddEvent(LogLevel_Verbose, "Save Brightness: " + String(i_data)); + SaveInt8(EEPROM_ADDR_BRIGHTNESS_START, i_data); +} + +/** + @info save contrast to EEPROM + @param uint8_t - contrast + @return none +*/ +void Configuration::SaveContrast(int8_t i_data) { + Log->AddEvent(LogLevel_Verbose, "Save Contrast: " + String(i_data)); + SaveInt8(EEPROM_ADDR_CONTRAST_START, i_data); +} + +/** + @info save saturation to EEPROM + @param int8_t - saturation + @return none +*/ +void Configuration::SaveSaturation(int8_t i_data) { + Log->AddEvent(LogLevel_Verbose, "Save Saturation: " + String(i_data)); + SaveInt8(EEPROM_ADDR_SATURATION_START, i_data); +} + +/** + @info save horizontal mirror to EEPROM + @param uint8_t - hmirror value + @return none +*/ +void Configuration::SaveHmirror(bool i_data) { + Log->AddEvent(LogLevel_Verbose, "Save Hmirror: " + String(i_data)); + SaveBool(EEPROM_ADDR_HMIRROR_START, i_data); +} + +/** + @info save vertical flip to EEPROM + @param uint8_t - vertical flip + @return none +*/ +void Configuration::SaveVflip(bool i_data) { + Log->AddEvent(LogLevel_Verbose, "Save vflip: " + String(i_data)); + SaveBool(EEPROM_ADDR_VFLIP_START, i_data); +} + +/** + @info save lens correction to EEPROM + @param uint8_t - lens correction + @return none +*/ +void Configuration::SaveLensCorrect(bool i_data) { + Log->AddEvent(LogLevel_Verbose, "Save lensc: " + String(i_data)); + SaveBool(EEPROM_ADDR_LENSC_START, i_data); +} + +/** + @info save exposure ctrl to EEPROM + @param uint8_t - exposure ctrl + @return none +*/ +void Configuration::SaveExposureCtrl(bool i_data) { + Log->AddEvent(LogLevel_Verbose, "Save exposure_ctrl: " + String(i_data)); + SaveBool(EEPROM_ADDR_EXPOSURE_CTRL_START, i_data); +} + +/** + @info Save awb + @param bool value + @return none +*/ +void Configuration::SaveAwb(bool i_data) { + Log->AddEvent(LogLevel_Verbose, "Save awb: " + String(i_data)); + SaveBool(EEPROM_ADDR_AWB_ENABLE_START, i_data); +} + +/** + @info Save awb gain value + @param bool value + @return none +*/ +void Configuration::SaveAwbGain(bool i_data) { + Log->AddEvent(LogLevel_Verbose, "Save awb_gain: " + String(i_data)); + Configuration::SaveBool(EEPROM_ADDR_AWB_GAIN_ENABLE_START, i_data); +} + +/** + @info Save awb mode value + @param uint8_t value + @return none +*/ +void Configuration::SaveAwbMode(uint8_t i_data) { + Log->AddEvent(LogLevel_Verbose, "Save awb_mode: " + String(i_data)); + SaveUint8(EEPROM_ADDR_AWB_MODE_ENABLE_START, i_data); +} + +/** + @info Save bpc value + @param bool value + @return none +*/ +void Configuration::SaveBpc(bool i_data) { + Log->AddEvent(LogLevel_Verbose, "Save bpc: " + String(i_data)); + SaveBool(EEPROM_ADDR_BPC_ENABLE_START, i_data); +} + +/** + @info Save wpc value + @param bool value + @return none +*/ +void Configuration::SaveWpc(bool i_data) { + Log->AddEvent(LogLevel_Verbose, "Save wpc: " + String(i_data)); + SaveBool(EEPROM_ADDR_WPC_ENABLE_START, i_data); +} + +/** + @info Save raw gama value + @param bool value + @return none +*/ +void Configuration::SaveRawGama(bool i_data) { + Log->AddEvent(LogLevel_Verbose, "Save raw_gama: " + String(i_data)); + SaveBool(EEPROM_ADDR_RAW_GAMA_ENABLE_START, i_data); +} + +/** + @info save WI-FI ssid to EEPROM + @param string - WI-FI SSID + @return none +*/ +void Configuration::SaveWifiSsid(String i_data) { + Log->AddEvent(LogLevel_Verbose, "Save WI-FI SSID[" + String(i_data.length()) + "]: " + i_data); + SaveString(EEPROM_ADDR_WIFI_SSID_START, EEPROM_ADDR_WIFI_SSID_LENGTH, i_data); +} + +/** + @info save WI-FI password to EEPROM + @param string - WI-FI password + @return none +*/ +void Configuration::SaveWifiPassword(String i_data) { + //Log->AddEvent(LogLevel_Verbose, "Save WI-FI password[" + String(i_data.length()) + "]: " + i_data); /* SENSITIVE DATA! */ + Log->AddEvent(LogLevel_Verbose, "Save WI-FI password[" + String(i_data.length()) + "]"); + SaveString(EEPROM_ADDR_WIFI_PASSWORD_START, EEPROM_ADDR_WIFI_PASSWORD_LENGTH, i_data); +} + +/** + @info save flag for configuration to EEPROM + @param uint8_t - cfg flag + @return none +*/ +void Configuration::SaveWifiCfgFlag(uint8_t i_data) { + Log->AddEvent(LogLevel_Verbose, "Save active wifi cfg flag: " + String(i_data)); + SaveUint8(EEPROM_ADDR_WIFI_ACTIVE_FLAG_START, i_data); +} +/* + @info save username fof BasicAuth to EEPROM + @param string - username + @return none +*/ +void Configuration::SaveBasicAuthUsername(String i_data) { + Log->AddEvent(LogLevel_Verbose, "Save username BasicAuth[" + String(i_data.length()) + "]: " + i_data); + SaveString(EEPROM_ADDR_BASIC_AUTH_USERNAME_START, EEPROM_ADDR_BASIC_AUTH_USERNAME_LENGTH, i_data); +} + + +/** + @info save password fof BasicAuth to EEPROM + @param string - password + @return none +*/ +void Configuration::SaveBasicAuthPassword(String i_data) { + uint8_t len = i_data.length(); + Log->AddEvent(LogLevel_Verbose, "Save password BasicAuth[" + String(len) + "]: "); + SaveString(EEPROM_ADDR_BASIC_AUTH_PASSWORD_START, EEPROM_ADDR_BASIC_AUTH_PASSWORD_LENGTH, i_data); +} + +/** + @info save status enable/disable BasicAuth to EEPROM + @param bool - status + @return none +*/ +void Configuration::SaveBasicAuthFlag(bool i_data) { + Log->AddEvent(LogLevel_Verbose, "Save Enable/disable BasicAuth: " + String(i_data)); + SaveBool(EEPROM_ADDR_BASIC_AUTH_ENABLE_FLAG_START, i_data); +} + +/** + @info save status enable/disable photo flash to EEPROM + @param bool - status + @return none +*/ +void Configuration::SaveCameraFlashEnable(uint8_t i_data) { + Log->AddEvent(LogLevel_Verbose, "Save Enable/disable camera flash: " + String(i_data)); + SaveUint8(EEPROM_ADDR_CAMERA_FLASH_ENABLE_START, i_data); +} + +/** + @info save time for photo flash to EEPROM + @param bool - status + @return none +*/ +void Configuration::SaveCameraFlashTime(uint16_t i_data) { + Log->AddEvent(LogLevel_Verbose, "Save camera flash time: " + String(i_data)); + SaveUint16(EEPROM_ADDR_CAMERA_FLASH_TIME_START, i_data); +} + +/** + @info Save mdns record + @param String - value + @return none +*/ +void Configuration::SaveMdnsRecord(String i_data) { + Log->AddEvent(LogLevel_Verbose, "Save mDNS record[" + String(i_data.length()) + "]: " + String(i_data)); + SaveString(EEPROM_ADDR_MDNS_RECORD_START, EEPROM_ADDR_MDNS_RECORD_LENGTH, i_data); +} + +/** + @info Save aec2 value + @param bool - value + @return none +*/ +void Configuration::SaveAec2(bool i_data) { + Log->AddEvent(LogLevel_Verbose, "Save Enable/disable AEC2: " + String(i_data)); + SaveBool(EEPROM_ADDR_AEC2_START, i_data); +} + +/** + @info Save ae level value + @param int8_t - value + @return none +*/ +void Configuration::SaveAeLevel(int8_t i_data) { + Log->AddEvent(LogLevel_Verbose, "Save ae_level: " + String(i_data)); + SaveBool(EEPROM_ADDR_AE_LEVEL_START, i_data); +} + +/** + @info Save aec value + @param uint16_t - value + @return none +*/ +void Configuration::SaveAecValue(uint16_t i_data) { + Log->AddEvent(LogLevel_Verbose, "Save aec value time: " + String(i_data)); + SaveUint16(EEPROM_ADDR_AEC_VALUE_START, i_data); +} + +/** + @info Save gain ctrl + @param bool - value + @return none +*/ +void Configuration::SaveGainCtrl(bool i_data) { + Log->AddEvent(LogLevel_Verbose, "Save gain_ctrl: " + String(i_data)); + SaveBool(EEPROM_ADDR_GAIN_CTRL_START, i_data); +} + +/** + @info Save agc gain + @param uint8_t - value + @return none +*/ +void Configuration::SaveAgcGain(uint8_t i_data) { + Log->AddEvent(LogLevel_Verbose, "Save agc_gain: " + String(i_data)); + SaveUint8(EEPROM_ADDR_AGC_GAIN_START, i_data); +} + +/** + @info Save log level + @param LogLevel_enum - value + @return none +*/ +void Configuration::SaveLogLevel(LogLevel_enum i_data) { + Log->AddEvent(LogLevel_Verbose, "Save log level: " + String(i_data)); + SaveUint8(EEPROM_ADDR_LOG_LEVEL, i_data); +} + +/** + * @info Save PrusaConnect hostname + * @param String - hostname + * @return none +*/ +void Configuration::SavePrusaConnectHostname(String i_data) { + Log->AddEvent(LogLevel_Verbose, "Save PrusaConnectHostanme[" + String(i_data.length()) + "]: " + i_data); + SaveString(EEPROM_ADDR_HOSTNAME_START, EEPROM_ADDR_HOSTNAME_LENGTH, i_data); +} + +/** + @info load refresh interval from eeprom + @param none + @return uint8_t - refresh interval +*/ +uint8_t Configuration::LoadRefreshInterval() { + uint8_t ret = EEPROM.read(EEPROM_ADDR_REFRESH_INTERVAL_START); + Log->AddEvent(LogLevel_Info, "Refresh interval: " + String(ret)); + + return ret; +} + +/** + @info load token from eeprom + @param none + @return String - token +*/ +String Configuration::LoadToken() { + Log->AddEvent(LogLevel_Info, "Token: ", false); + String ret = LoadString(EEPROM_ADDR_TOKEN_START, EEPROM_ADDR_TOKEN_LENGTH, CONSOLE_VERBOSE_DEBUG); + + return ret; +} + +/** + @info load fingerprint from eeprom + @param none + @return String - fingerprint +*/ +String Configuration::LoadFingerprint() { + Log->AddEvent(LogLevel_Info, "Fingerprint: ", false); + String ret = LoadString(EEPROM_ADDR_FINGERPRINT_START, EEPROM_ADDR_FINGERPRINT_LENGTH, true); + + return ret; +} + +/** + @info load photo quality cfg from eeprom + @param none + @return uint8_t - photo quality +*/ +uint8_t Configuration::LoadPhotoQuality() { + uint8_t ret = EEPROM.read(EEPROM_ADDR_PHOTO_QUALITY_START); + Log->AddEvent(LogLevel_Info, "Photo quality: " + String(ret)); + + return ret; +} + +/** + @info load framesize cfg from eeprom + @param none + @return uint8_t - framesize +*/ +uint8_t Configuration::LoadFrameSize() { + uint8_t ret = EEPROM.read(EEPROM_ADDR_FRAMESIZE_START); + Log->AddEvent(LogLevel_Info, "Framesize: " + String(ret)); + return ret; +} + +/** + @info load Brightness cfg from eeprom + @param none + @return int8_t - brightness +*/ +int8_t Configuration::LoadBrightness() { + int8_t ret = EEPROM.read(EEPROM_ADDR_BRIGHTNESS_START); + Log->AddEvent(LogLevel_Info, "brightness: " + String(ret)); + + return ret; +} + +/** + @info load contrast cfg from eeprom + @param none + @return int8_t - contrast +*/ +int8_t Configuration::LoadContrast() { + int8_t ret = EEPROM.read(EEPROM_ADDR_CONTRAST_START); + Log->AddEvent(LogLevel_Info, "contrast: " + String(ret)); + + return ret; +} + +/** + @info load saturation cfg from eeprom + @param none + @return int8_t - saturation +*/ +int8_t Configuration::LoadSaturation() { + int8_t ret = EEPROM.read(EEPROM_ADDR_SATURATION_START); + Log->AddEvent(LogLevel_Info, "saturation: " + String(ret)); + + return ret; +} + +/** + @info load hmirror cfg from eeprom + @param none + @return bool - hmirror +*/ +bool Configuration::LoadHmirror() { + bool ret = EEPROM.read(EEPROM_ADDR_HMIRROR_START); + Log->AddEvent(LogLevel_Info, "hmirror: " + String(ret)); + + return ret; +} + +/** + @info load vflip cfg from eeprom + @param none + @return bool - vflip +*/ +bool Configuration::LoadVflip() { + bool ret = EEPROM.read(EEPROM_ADDR_VFLIP_START); + Log->AddEvent(LogLevel_Info, "vflip: " + String(ret)); + + return ret; +} + +/** + @info load lens corection cfg from eeprom + @param none + @return bool - lens correction +*/ +bool Configuration::LoadLensCorrect() { + bool ret = EEPROM.read(EEPROM_ADDR_LENSC_START); + Log->AddEvent(LogLevel_Info, "lensc: " + String(ret)); + + return ret; +} + +/** + @info load exposure ctrl cfg from eeprom + @param none + @return bool - exposure ctrl +*/ +bool Configuration::LoadExposureCtrl() { + bool ret = EEPROM.read(EEPROM_ADDR_EXPOSURE_CTRL_START); + Log->AddEvent(LogLevel_Info, "exposure_ctrl: " + String(ret)); + + return ret; +} + +/** + @info Load awb value + @param none + @return bool - awb +*/ +bool Configuration::LoadAwb() { + bool ret = EEPROM.read(EEPROM_ADDR_AWB_ENABLE_START); + Log->AddEvent(LogLevel_Info, "awb: " + String(ret)); + + return ret; +} + +/** + @info Load awb gain value + @param none + @return bool - awb gain +*/ +bool Configuration::LoadAwbGain() { + bool ret = EEPROM.read(EEPROM_ADDR_AWB_GAIN_ENABLE_START); + Log->AddEvent(LogLevel_Info, "awb_gain: " + String(ret)); + + return ret; +} + +/** + @info Load awb mode + @param none + @return uint8_t - awb mode +*/ +uint8_t Configuration::LoadAwbMode() { + uint8_t ret = EEPROM.read(EEPROM_ADDR_AWB_MODE_ENABLE_START); + Log->AddEvent(LogLevel_Info, "awb_mode: " + String(ret)); + + return ret; +} + +/** + @info Load bpc value + @param none + @return bool - bpc +*/ +bool Configuration::LoadBpc() { + bool ret = EEPROM.read(EEPROM_ADDR_BPC_ENABLE_START); + Log->AddEvent(LogLevel_Info, "bpc: " + String(ret)); + + return ret; +} + +/** + @info Load wpc value + @param none + @return bool - wpc +*/ +bool Configuration::LoadWpc() { + bool ret = EEPROM.read(EEPROM_ADDR_WPC_ENABLE_START); + Log->AddEvent(LogLevel_Info, "wpc: " + String(ret)); + + return ret; +} + +/** + @info Load raw gama value + @param none + @return bool - raw gama +*/ +bool Configuration::LoadRawGama() { + bool ret = EEPROM.read(EEPROM_ADDR_RAW_GAMA_ENABLE_START); + Log->AddEvent(LogLevel_Info, "raw_gama: " + String(ret)); + + return ret; +} + +/** + @info load WI-FI ssid cfg from eeprom + @param none + @return String - WI-FI SSID +*/ +String Configuration::LoadWifiSsid() { + Log->AddEvent(LogLevel_Info, "SSID: ", false); + String ret = LoadString(EEPROM_ADDR_WIFI_SSID_START, EEPROM_ADDR_WIFI_SSID_LENGTH, true); + + return ret; +} + +/** + @info load Wi-FI password cfg from eeprom + @param none + @return String - WI-FI password +*/ +String Configuration::LoadWifiPassowrd() { + Log->AddEvent(LogLevel_Info, "WiFi password: ", false); + String ret = LoadString(EEPROM_ADDR_WIFI_PASSWORD_START, EEPROM_ADDR_WIFI_PASSWORD_LENGTH, CONSOLE_VERBOSE_DEBUG); + + return ret; +} + +/** + @info Read username for basic authentification from eeprom + @param none + @return String - username +*/ +String Configuration::LoadBasicAuthUsername() { + Log->AddEvent(LogLevel_Info, "web auth user: ", false); + String ret = LoadString(EEPROM_ADDR_BASIC_AUTH_USERNAME_START, EEPROM_ADDR_BASIC_AUTH_USERNAME_LENGTH, true); + + return ret; +} + +/** + @info Read password for basic authentification from eeprom + @param none + @return String - password +*/ +String Configuration::LoadBasicAuthPassword() { + Log->AddEvent(LogLevel_Info, "web auth pass: ", false); + String ret = LoadString(EEPROM_ADDR_BASIC_AUTH_PASSWORD_START, EEPROM_ADDR_BASIC_AUTH_PASSWORD_LENGTH, CONSOLE_VERBOSE_DEBUG); + + return ret; +} + +/** + @info Load flag for enable/disable basic auth from eeprom + @param none + @return bool - status +*/ +bool Configuration::LoadBasicAuthFlag() { + bool ret = EEPROM.read(EEPROM_ADDR_BASIC_AUTH_ENABLE_FLAG_START); + Log->AddEvent(LogLevel_Info, "web auth enable: " + String(ret)); + + return ret; +} + +/** + @info Load flag for enable/disable flash from eeprom + @param none + @return bool - status +*/ +bool Configuration::LoadCameraFlashEnable() { + bool ret = EEPROM.read(EEPROM_ADDR_CAMERA_FLASH_ENABLE_START); + Log->AddEvent(LogLevel_Info, "Camera flash: " + String(ret)); + + return ret; +} + +/** + @info Load time for photo flash + @param none + @return uint16_t - time +*/ +uint16_t Configuration::LoadCameraFlashTime() { + uint16_t ret = LoadUint16(EEPROM_ADDR_CAMERA_FLASH_TIME_START); + Log->AddEvent(LogLevel_Info, "Camera flash time: " + String(ret)); + + return ret; +} + +/** + @info Load mdns record + @param none + @return String - mDNS record +*/ +String Configuration::LoadMdnsRecord() { + Log->AddEvent(LogLevel_Info, "mDNS: ", false); + String ret = LoadString(EEPROM_ADDR_MDNS_RECORD_START, EEPROM_ADDR_MDNS_RECORD_LENGTH, true); + + return ret; +} + +/** + @info Load aec2 value + @param none + @return bool - value +*/ +bool Configuration::LoadAec2() { + bool ret = EEPROM.read(EEPROM_ADDR_AEC2_START); + Log->AddEvent(LogLevel_Info, "aec2: " + String(ret)); + + return ret; +} + +/** + @info Load ae level value + @param none + @return int8_t - value +*/ +int8_t Configuration::LoadAeLevel() { + int8_t ret = EEPROM.read(EEPROM_ADDR_AE_LEVEL_START); + Log->AddEvent(LogLevel_Info, "ae_level: " + String(ret)); + + return ret; +} + +/** + @info Load aec value + @param none + @return uint16_t - value +*/ +uint16_t Configuration::LoadAecValue() { + uint16_t ret = LoadUint16(EEPROM_ADDR_AEC_VALUE_START); + Log->AddEvent(LogLevel_Info, "aec_value: " + String(ret)); + + return ret; +} + +/** + @info Load fain ctrl value + @param none + @return bool - value +*/ +bool Configuration::LoadGainCtrl() { + bool ret = EEPROM.read(EEPROM_ADDR_GAIN_CTRL_START); + Log->AddEvent(LogLevel_Info, "gain_ctrl: " + String(ret)); + + return ret; +} + +/** + @info Load agc gain value + @param none + @return uint8_t - value +*/ +uint8_t Configuration::LoadAgcGain() { + uint8_t ret = EEPROM.read(EEPROM_ADDR_AGC_GAIN_START); + Log->AddEvent(LogLevel_Info, "agc_gain: " + String(ret)); + + return ret; +} + +String Configuration::LoadPrusaConnectHostname() { + Log->AddEvent(LogLevel_Info, "PrusaConnect hostname: ", false); + String ret = LoadString(EEPROM_ADDR_HOSTNAME_START, EEPROM_ADDR_HOSTNAME_LENGTH, true); + + return ret; +} + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/cfg.h b/ESP32_PrusaConnectCam/cfg.h new file mode 100644 index 0000000..6d36b38 --- /dev/null +++ b/ESP32_PrusaConnectCam/cfg.h @@ -0,0 +1,125 @@ +/** + @file cfg.h + + @brief Library for save and load MCU configuration + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#ifndef _CFG_H_ +#define _CFG_H_ +#include +#include + +#include "Arduino.h" +#include +#include + +#include "mcu_cfg.h" +#include "Camera_cfg.h" +#include "var.h" +#include "log.h" + +class Configuration { +public: + Configuration(Logs*); + ~Configuration(){}; + void Init(); + bool CheckActifeWifiCfgFlag(); + void CheckResetCfg(); + + void SaveRefreshInterval(uint8_t); + void SaveToken(String); + void SaveFingerprint(String); + void SavePhotoQuality(uint8_t); + void SaveFrameSize(uint8_t); + void SaveBrightness(int8_t); + void SaveContrast(int8_t); + void SaveSaturation(int8_t); + void SaveHmirror(bool); + void SaveVflip(bool); + void SaveLensCorrect(bool); + void SaveExposureCtrl(bool); + void SaveAwb(bool); + void SaveAwbGain(bool); + void SaveAwbMode(uint8_t); + void SaveBpc(bool); + void SaveWpc(bool); + void SaveRawGama(bool); + void SaveWifiSsid(String); + void SaveWifiPassword(String); + void SaveWifiCfgFlag(uint8_t); + void SaveBasicAuthUsername(String); + void SaveBasicAuthPassword(String); + void SaveBasicAuthFlag(bool); + void SaveCameraFlashEnable(uint8_t); + void SaveCameraFlashTime(uint16_t); + void SaveMdnsRecord(String); + void SaveAec2(bool); + void SaveAeLevel(int8_t); + void SaveAecValue(uint16_t); + void SaveGainCtrl(bool); + void SaveAgcGain(uint8_t); + void SaveLogLevel(LogLevel_enum); + void SavePrusaConnectHostname(String); + + uint8_t LoadRefreshInterval(); + String LoadToken(); + String LoadFingerprint(); + uint8_t LoadPhotoQuality(); + uint8_t LoadFrameSize(); + int8_t LoadBrightness(); + int8_t LoadContrast(); + int8_t LoadSaturation(); + bool LoadHmirror(); + bool LoadVflip(); + bool LoadLensCorrect(); + bool LoadExposureCtrl(); + bool LoadAwb(); + bool LoadAwbGain(); + uint8_t LoadAwbMode(); + bool LoadBpc(); + bool LoadWpc(); + bool LoadRawGama(); + String LoadWifiSsid(); + String LoadWifiPassowrd(); + String LoadBasicAuthUsername(); + String LoadBasicAuthPassword(); + bool LoadBasicAuthFlag(); + bool LoadCameraFlashEnable(); + uint16_t LoadCameraFlashTime(); + String LoadMdnsRecord(); + bool LoadAec2(); + int8_t LoadAeLevel(); + uint16_t LoadAecValue(); + bool LoadGainCtrl(); + uint8_t LoadAgcGain(); + String LoadPrusaConnectHostname(); + +private: + Logs *Log; ///< Pointer to Logs object + String WiFiMacAddress; ///< WiFi MAC address + + void ReadCfg(); + void DefaultCfg(); + bool CheckFirstMcuStart(); + void SaveFirstMcuStartFlag(uint8_t); + void GetFingerprint(); + + void SaveUint8(uint16_t, uint8_t); + void SaveInt8(uint16_t, int8_t); + void SaveBool(uint16_t, bool); + void SaveUint16(uint16_t, uint16_t); + void SaveString(uint16_t, uint16_t, String); + uint16_t LoadUint16(uint16_t); + String LoadString(uint16_t, uint16_t, bool); +}; + +extern Configuration SystemConfig; ///< Configuration object + +#endif + +/* EOF */ diff --git a/ESP32_PrusaConnectCam/connect.cpp b/ESP32_PrusaConnectCam/connect.cpp new file mode 100644 index 0000000..8349c3d --- /dev/null +++ b/ESP32_PrusaConnectCam/connect.cpp @@ -0,0 +1,462 @@ +/** + @file connnect.cpp + + @brief library for communication with prusa connect backend + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#include "connect.h" + +PrusaConnect Connect(&SystemConfig, &SystemLog, &SystemCamera); + +/** + * @brief Constructor for PrusaConnect class + * + * @param Configuration* - pointer to Configuration class + * @param Logs* - pointer to Logs class + * @param Camera* - pointer to Camera class + */ +PrusaConnect::PrusaConnect(Configuration *i_conf, Logs *i_log, Camera *i_camera) { + config = i_conf; + log = i_log; + camera = i_camera; + BackendAvailability = WaitForFirstConnection; + SendDeviceInformationToBackend = true; +} + +/** + * @brief init library PrusaConnect + * + * @param none + * @return none + */ +void PrusaConnect::Init() { + log->AddEvent(LogLevel_Info, "Init PrusaConnect lib"); + TakePicture(); +} + +/** + * @brief Load configuration from EEPROM + * + * @param none + * @return none + */ +void PrusaConnect::LoadCfgFromEeprom() { + log->AddEvent(LogLevel_Info, "Load PrusaConnect CFG from EEPROM"); + Token = config->LoadToken(); + Fingerprint = config->LoadFingerprint(); + RefreshInterval = config->LoadRefreshInterval(); + PrusaConnectHostname = config->LoadPrusaConnectHostname(); +} + +/** + * @brief take picture + * + * @param none + * @return none + */ +void PrusaConnect::TakePicture() { + camera->CapturePhoto(); +} + +/** + * @brief Sending data to prusa connect backend + * + * @param i_data - data to send + * @param i_content_type - data content type + * @param i_type - type of data for log message + * @param i_url_path - url path for backend + * @param i_fragmentation - flag for enable/disable data fragmentation + * @return true - if data was sent successfully + * @return false - if data was not sent successfully + */ +bool PrusaConnect::SendDataToBackend(String *i_data, String i_content_type, String i_type, String i_url_path, bool i_fragmentation) { + WiFiClientSecure client; + BackendReceivedStatus = ""; + bool ret = false; + log->AddEvent(LogLevel_Info, "Sending " + i_type + " to PrusaConnect"); + + /* check fingerprint and token length */ + if ((Fingerprint.length() > 0) && (Token.length() > 0)) { + client.setCACert(root_CAs); + log->AddEvent(LogLevel_Verbose, "Connecting to server..."); + + /* connecting to server */ + if (!client.connect(PrusaConnectHostname.c_str(), 443)) { + char err_buf[200]; + int last_error = client.lastError(err_buf, sizeof(err_buf)); + int error = client.getWriteError(); + if (BackendAvailability != WaitForFirstConnection) { + BackendAvailability = BackendUnavailable; + } + + BackendReceivedStatus = "Connetion failed to domain! Error: " + String(last_error) + " - " + String(err_buf) + " : " + String(error); + log->AddEvent(LogLevel_Info, BackendReceivedStatus + " ,BA:" + CovertBackendAvailabilitStatusToString(BackendAvailability)); + return false; + + } else { + /* send data to server */ + log->AddEvent(LogLevel_Verbose, "Connected to server!"); + client.println("PUT https://" + PrusaConnectHostname + i_url_path + " HTTP/1.0"); + client.println("Host: " + PrusaConnectHostname); + client.println("User-Agent: ESP32-CAM"); + client.println("Connection: close"); + + client.println("Content-Type: " + i_content_type); + client.println("fingerprint: " + Fingerprint); + client.println("token: " + Token); + client.print("Content-Length: "); + client.println(i_data->length()); + client.println(); + + esp_task_wdt_reset(); + if (true == i_fragmentation) { + log->AddEvent(LogLevel_Verbose, "Send data fragmented"); + for (int index = 0; index < i_data->length(); index = index + PHOTO_FRAGMENT_SIZE) { + client.print(i_data->substring(index, index + PHOTO_FRAGMENT_SIZE)); + log->AddEvent(LogLevel_Verbose, String(index)); + } + } else { + log->AddEvent(LogLevel_Verbose, "Send data"); + client.print(*i_data); + } + + log->AddEvent(LogLevel_Info, "Send done"); + esp_task_wdt_reset(); + + String response = ""; + String fullResponse = ""; + log->AddEvent(LogLevel_Verbose, "Response:"); + while (client.connected()) { + if (client.available()) { + response = client.readStringUntil('\n'); + fullResponse += response; + log->AddEvent(LogLevel_Verbose, response.c_str()); + + if (response.startsWith("HTTP/1.1")) { + int httpCode = response.substring(9, 12).toInt(); + BackendReceivedStatus = i_type; + BackendReceivedStatus += ": "; + BackendReceivedStatus += ProcessHttpResponseCode(httpCode); + if (true == ProcessHttpResponseCodeBool(httpCode)) { + ret = true; + } + } + } + } + log->AddEvent(LogLevel_Verbose, "Full response: " + fullResponse); + + BackendAvailability = BackendAvailable; + client.stop(); + } + } else { + /* err message */ + log->AddEvent(LogLevel_Verbose, "ERROR SEND DATA TO SERVER! INVALID DATA!"); + log->AddEvent(LogLevel_Verbose, "Fingerprint: " + Fingerprint); + log->AddEvent(LogLevel_Verbose, "Token: " + Token); + + if (Fingerprint.length() == 0) { + BackendReceivedStatus = "Missing fingerprint"; + } else if (Token.length() == 0) { + BackendReceivedStatus = "Missing token"; + } + } + + log->AddEvent(LogLevel_Info, "Upload done. Response code: " + BackendReceivedStatus + " ,BA:" + CovertBackendAvailabilitStatusToString(BackendAvailability)); + return ret; +} + +/** + * @brief Send photo to prusa connect backend + * + * @param none + * @return none + */ +void PrusaConnect::SendPhotoToBackend() { + log->AddEvent(LogLevel_Info, "Start sending photo to prusaconnect"); + camera->CopyPhoto(&Photo); + SendDataToBackend(&Photo, "image/jpg", "Photo", HOST_URL_CAM_PATH, true); +} + +/** + * @brief seding device info to prusaconnect backend + * + */ +void PrusaConnect::SendInfoToBackend() { + if (false == SendDeviceInformationToBackend) { + return; + + } else { + log->AddEvent(LogLevel_Info, "Start sending device information to prusaconnect"); + + JsonDocument json_data; + String json_string = ""; + + JsonObject config = json_data["config"].to(); + config["name"] = "ESP32-CAM"; + + JsonObject resolution = config["resolution"].to(); + resolution["width"] = SystemCamera.GetFrameSizeWidth(); + resolution["height"] = SystemCamera.GetFrameSizeHeight(); + + JsonObject network_info = config["network_info"].to(); + network_info["wifi_mac"] = SystemWifiMngt.GetWifiMac(); + network_info["wifi_ipv4"] = SystemWifiMngt.GetStaIp(); + network_info["wifi_ssid"] = SystemWifiMngt.GetStaSsid(); + + serializeJson(json_data, json_string); + log->AddEvent(LogLevel_Info, "Data: " + json_string); + bool response = SendDataToBackend(&json_string, "application/json", "Info", HOST_URL_INFO_PATH, false); + + if (true == response) { + SendDeviceInformationToBackend = false; + } + } +} + +/** + * @brief Take picture and send to backend + * + * @param none + * @return none + */ +void PrusaConnect::TakePictureAndSendToBackend() { + TakePicture(); + SendPhotoToBackend(); +} + +/** + @brief Function for processing http response code from prusa backend + @param int - http response code + @return none +*/ +String PrusaConnect::ProcessHttpResponseCode(int code) { + String ret = ""; + switch (code) { + case 200: + ret = "200 - OK"; + break; + case 201: + ret = "201 - OK entry created"; + break; + case 204: + ret = "204 - Upload OK"; + break; + case 304: + ret = "304 - Response has not been modified"; + break; + case 400: + ret = "400 - Some data received is not valid"; + break; + case 401: + ret = "401 - Missing security toker or it is not valid"; + break; + case 403: + ret = "403 - Security toke is not valid or is outdated"; + break; + case 404: + ret = "404 - Entity not found or invalid auth token"; + break; + case 409: + ret = "409 - Conflict with the state of target resource (user error)"; + break; + case 503: + ret += "503 - Service is unavailable at this moment. Try again later"; + break; + default: + ret = String(code); + ret += " - unknown error code"; + break; + } + + return ret; +} +/** + * @brief Translate http response code to boolean + * + * @param code - http response code + * @return true - if response code is OK + * @return false - if response code is not OK + */ +bool PrusaConnect::ProcessHttpResponseCodeBool(int code) { + bool ret = false; + switch (code) { + case 200: + ret = true; + break; + case 201: + ret = true; + break; + case 204: + ret = true; + break; + case 304: + ret = false; + break; + case 400: + ret = false; + break; + case 401: + ret = false; + break; + case 403: + ret = false; + break; + case 404: + ret = false; + break; + case 409: + ret = false; + break; + case 503: + ret = false; + break; + default: + ret = false; + break; + } + + return ret; +} + +/** + * @brief Update device information + * + * @param none + * @return none + */ +void PrusaConnect::UpdateDeviceInformation() { + SendDeviceInformationToBackend = true; +} + +/** + * @brief Set refresh interval + * + * @param uint8_t i_data - refresh interval + * @return none + */ +void PrusaConnect::SetRefreshInterval(uint8_t i_data) { + RefreshInterval = i_data; + config->SaveRefreshInterval(RefreshInterval); +} + +/** + * @brief Set token + * + * @param String i_data - token + * @return none + */ +void PrusaConnect::SetToken(String i_data) { + Token = i_data; + config->SaveToken(Token); +} + +/** + * @brief Set backend availability status + * + * @param BackendAvailabilitStatus - backend status + * @return none + */ +void PrusaConnect::SetBackendAvailabilitStatus(BackendAvailabilitStatus i_data) { + BackendAvailability = i_data; +} + +/** + * @brief set prusa connect hostname + * + * @param String i_data - hostname + */ +void PrusaConnect::SetPrusaConnectHostname(String i_data) { + PrusaConnectHostname = i_data; + config->SavePrusaConnectHostname(PrusaConnectHostname); +} + +/** + * @brief Get refresh interval + * + * @param none + * @return uint8_t - refresh interval + */ +uint8_t PrusaConnect::GetRefreshInterval() { + return RefreshInterval; +} + +/** + * @brief get backend received status + * + * @param none + * @return String - backend received status + */ +String PrusaConnect::GetBackendReceivedStatus() { + return BackendReceivedStatus; +} + +/** + * @brief get token + * + * @param none + * @return String - token + */ +String PrusaConnect::GetToken() { + return Token; +} + +/** + * @brief get fingerprint + * + * @param none + * @return String - fingerprint + */ +String PrusaConnect::GetFingerprint() { + return Fingerprint; +} + +/** + * @brief get prusa connect hostname + * + * @return String - hostanme + */ +String PrusaConnect::GetPrusaConnectHostname() { + return PrusaConnectHostname; +} + +/** + * @brief Get backend availability status + * + * @param none + * @return BackendAvailabilitStatus - backend status + */ +BackendAvailabilitStatus PrusaConnect::GetBackendAvailabilitStatus() { + return BackendAvailability; +} + +/** + * @brief Convert backend availability status to string + * @param BackendAvailabilitStatus - backend status + * @return String - backend status as string +*/ +String PrusaConnect::CovertBackendAvailabilitStatusToString(BackendAvailabilitStatus i_data) { + String ret = ""; + switch (i_data) { + case WaitForFirstConnection: + ret = "Wait for first connection"; + break; + case BackendAvailable: + ret = "Backend available"; + break; + case BackendUnavailable: + ret = "Backend unavailable"; + break; + default: + ret = "Unknown"; + break; + } + + return ret; +} + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/connect.h b/ESP32_PrusaConnectCam/connect.h new file mode 100644 index 0000000..bc75c40 --- /dev/null +++ b/ESP32_PrusaConnectCam/connect.h @@ -0,0 +1,88 @@ +/** + @file connnect.h + + @brief library for communication with prusa connect backend + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#ifndef _PRUSA_CONNECT_H_ +#define _PRUSA_CONNECT_H_ + +#include +#include +#include +#include "Arduino.h" +#include + +#include "wifi_mngt.h" +#include "mcu_cfg.h" +#include "var.h" +#include "log.h" +#include "camera.h" +#include "cfg.h" +#include "Certificate.h" + +/** + * @brief BackendAvailabilitStatus enum + * status of backend availability + */ +enum BackendAvailabilitStatus { + WaitForFirstConnection = 0, ///< waiting for first connection to backend + BackendAvailable = 1, ///< backend is available + BackendUnavailable = 2, ///< backend is unavailable +}; + +class PrusaConnect { +private: + uint8_t RefreshInterval; ///< interval for sending photo to backend + String BackendReceivedStatus; ///< status of backend response + BackendAvailabilitStatus BackendAvailability; ///< status of backend availability + bool SendDeviceInformationToBackend; ///< flag for sending device information to backend + + String Token; ///< token for backend communication + String Fingerprint; ///< fingerprint for backend communication + String Photo; ///< photo for sending to backend + String PrusaConnectHostname; ///< hostname of prusa connect backend + + Configuration *config; ///< pointer to configuration object + Logs *log; ///< pointer to logs object + Camera *camera; ///< pointer to camera object + + bool SendDataToBackend(String *, String, String, String, bool); + +public: + PrusaConnect(Configuration*, Logs*, Camera*); + ~PrusaConnect(){}; + + void Init(); + void LoadCfgFromEeprom(); + + void TakePicture(); + void SendPhotoToBackend(); + void SendInfoToBackend(); + void TakePictureAndSendToBackend(); + String ProcessHttpResponseCode(int); + bool ProcessHttpResponseCodeBool(int); + void UpdateDeviceInformation(); + + void SetRefreshInterval(uint8_t); + void SetToken(String); + void SetBackendAvailabilitStatus(BackendAvailabilitStatus); + void SetPrusaConnectHostname(String); + + uint8_t GetRefreshInterval(); + String GetBackendReceivedStatus(); + String GetToken(); + String GetFingerprint(); + String GetPrusaConnectHostname(); + BackendAvailabilitStatus GetBackendAvailabilitStatus(); + String CovertBackendAvailabilitStatusToString(BackendAvailabilitStatus); +}; + +extern PrusaConnect Connect; ///< PrusaConnect object + +#endif \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/jquery.h b/ESP32_PrusaConnectCam/jquery.h new file mode 100644 index 0000000..555dd51 --- /dev/null +++ b/ESP32_PrusaConnectCam/jquery.h @@ -0,0 +1,23 @@ +/** + @file jquery.h + + @brief Here is saved jquery library 3.7.0 + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#ifndef _JQUERY_H_ +#define _JQUERY_H_ + +/* https://releases.jquery.com/jquery/ */ +const char jquery_3_7_0_js[] PROGMEM = R"rawliteral( +/*! jQuery v3.7.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.0",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},R=function(){V()},M=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&z(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function X(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&M(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function U(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function z(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",R),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Me(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return R(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return R(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0= LOGS_FILE_MAX_SIZE) { + uint16_t file_count = FileCount(SD_MMC, FilePath, FileName); + Serial.printf("Maximum log file size.\nFile count: %d\n", file_count); + RenameFile(SD_MMC, FilePath + FileName, FilePath + FileName + String(file_count)); + } + + /* added first message to log file after start MCU */ + String msg = "----------------------------------------------------------------\n"; + msg += "Start MCU!\nSW Version: "; + msg += String(SW_VERSION); + msg += " ,Build: "; + msg += String(SW_BUILD); + msg += "\n"; + msg += "Verbose mode: "; + msg += (true == CONSOLE_VERBOSE_DEBUG) ? "true" : "false"; + msg += "\n"; + msg += "Log level: "; + msg += String(LogLevel); + msg += "\n"; + AppendFile(SD_MMC, FilePath + FileName, msg); + + } else { + Serial.println("Micro-SD card not found! Disable logs"); + } +} + +/** + @info set log level + @param LogLevel_enum - log level + @return none +*/ +void Logs::SetLogLevel(LogLevel_enum level) { + LogLevel = level; +} + +/** + @info Add new log event + @param LogLevel_enum - log level + @param String - log message + @param bool - new line + @param bool - date + @return none +*/ +void Logs::AddEvent(LogLevel_enum level, String msg, bool newLine, bool date) { + if (LogLevel >= level) { + String LogMsg = ""; + + if (true == date) { + LogMsg += GetSystemTime(); + LogMsg += " - "; + } + LogMsg += msg; + if (true == newLine) { + LogMsg += "\n"; + } + + AppendFile(SD_MMC, FilePath + FileName, LogMsg); + Serial.print(LogMsg); + } +#if (true == CONSOLE_VERBOSE_DEBUG) + else { + Serial.println(msg); + } +#endif +} + +/** + @info Set file name + @param String - file name + @return none +*/ +void Logs::SetFileName(String i_data) { + FileName = i_data; +} + +/** + @info Set file path + @param String - file path + @return none +*/ +void Logs::SetFilePath(String i_data) { + FilePath = i_data; +} + +/** + @info Set file max size + @param uint16_t - file max size + @return none +*/ +void Logs::SetFileMaxSize(uint16_t i_data) { + FileMaxSize = i_data; +} + +/** + @info Set NTP time synced + @param bool - NTP time synced + @return none +*/ +void Logs::SetNtpTimeSynced(bool i_data) { + NtpTimeSynced = i_data; + AddEvent(LogLevel_Info, "System time: " + GetSystemTime()); +} + +/** + @info Get file name + @param none + @return String - file name +*/ +String Logs::GetFileName() { + return FileName; +} + +/** + @info Get file path + @param none + @return String - file path +*/ +String Logs::GetFilePath() { + return FilePath; +} + +/** + @info Get log level + @param none + @return LogLevel_enum - log level +*/ +LogLevel_enum Logs::GetLogLevel() { + return LogLevel; +} + +/** + @info Get NTP time synced + @param none + @return bool - NTP time synced +*/ +bool Logs::GetNtpTimeSynced() { + return NtpTimeSynced; +} + +/** + @info Get system time + @param none + @return String - time +*/ +String Logs::GetSystemTime() { + String ret = "0000-00-00_00-00-00"; + if (true == NtpTimeSynced) { + struct tm timeinfo; + if (!getLocalTime(&timeinfo)) { +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.println("Failed to obtain time"); +#endif + return ret; + } + + char timeString[20]; + strftime(timeString, sizeof(timeString), "%Y-%m-%d_%H-%M-%S", &timeinfo); +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.println(timeString); +#endif + ret = String(timeString); + } + return ret; +} + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/log.h b/ESP32_PrusaConnectCam/log.h new file mode 100644 index 0000000..c37ca63 --- /dev/null +++ b/ESP32_PrusaConnectCam/log.h @@ -0,0 +1,65 @@ +/** + @file log.h + + @brief log library + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#ifndef _LOG_H_ +#define _LOG_H_ + +#include "Arduino.h" + +#include "mcu_cfg.h" +#include "var.h" +#include "micro_sd.h" + +enum LogLevel_enum { + LogLevel_Error = 0, ///< Error + LogLevel_Warning = 1, ///< Warning + LogLevel_Info = 2, ///< Info + LogLevel_Verbose = 3 ///< Verbose +}; + +class Logs : public MicroSd { +private: + LogLevel_enum LogLevel; ///< LogLevel + String FileName; ///< log File name + String FilePath; ///< log file patch + uint16_t FileMaxSize; ///< log file max size + bool NtpTimeSynced; ///< status NTP time sync + +public: + Logs(); + Logs(String, String); + Logs(LogLevel_enum, String, String); + Logs(String, String, uint16_t); + Logs(LogLevel_enum, String, String, uint16_t); + ~Logs(){}; + + void Init(); + void AddEvent(LogLevel_enum, String, bool = true, bool = true); + void SetLogLevel(LogLevel_enum); + void SetFileName(String); + void SetFilePath(String); + void SetFileMaxSize(uint16_t); + void SetNtpTimeSynced(bool); + + String GetFileName(); + String GetFilePath(); + LogLevel_enum GetLogLevel(); + bool GetNtpTimeSynced(); + +protected: + String GetSystemTime(); +}; + +extern Logs SystemLog; ///< log object + +#endif + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/mcu_cfg.h b/ESP32_PrusaConnectCam/mcu_cfg.h new file mode 100644 index 0000000..00d657a --- /dev/null +++ b/ESP32_PrusaConnectCam/mcu_cfg.h @@ -0,0 +1,252 @@ +/** + @file mcu_cfg.h + + @brief Library configuration MCU + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug + +*/ + +#ifndef _MCU_CFG_H_ +#define _MCU_CFG_H_ + +/* ---------------- BASIC MCU CFG --------------*/ +#define SW_VERSION "1.0.0" ///< SW version +#define SW_BUILD __DATE__ " " __TIME__ ///< build number +#define CONSOLE_VERBOSE_DEBUG false ///< enable/disable verbose debug log level for console +#define DEVICE_HOSTNAME "Prusa-ESP32cam" ///< device hostname + +/* ------------ PRUSA BACKEND CFG --------------*/ +#define HOST_URL_CAM_PATH "/c/snapshot" ///< path for sending photo to prusa connect +#define HOST_URL_INFO_PATH "/c/info" ///< path for sending info to prusa connect +#define REFRESH_INTERVAL_MIN 5 ///< minimum refresh interval for sending photo to prusa connect [s] +#define REFRESH_INTERVAL_MAX 240 ///< maximum refresh interval for sending photo to prusa connect [s] + +/* --------------- FLASH LED CFG ---------------*/ +#define FLASH_GPIO_NUM 4 ///< GPIO pin for light +#define FLASH_OFF_STATUS 0 ///< PWM intensity LED for OFF. 0-2^FLASH_PWM_RESOLUTION = 0-255 +#define FLASH_ON_STATUS 205 ///< PWM intensity LED for ON. limitation to 80%. 2^FLASH_PWM_RESOLUTION * 0.8% = 204 +#define FLASH_PWM_FREQ 2000 ///< frequency of pwm [240MHz / (100 prescale * pwm cycles)] = frequency +#define FLASH_PWM_CHANNEL 0 ///< channel 0 +#define FLASH_PWM_RESOLUTION 8 ///< range 1-20bit. 8bit = 0-255 range + +/* -------------- STATUS LED CFG ----------------*/ +#define STATUS_LED_GPIO_NUM 33 ///< GPIO pin for status LED +#define STATUS_LED_ENABLE true ///< enable/disable status LED +#define STATUS_LED_ON_DURATION 100 ///< time for blink status LED when is module in the ON state [ms] +#define STATUS_LED_WIFI_AP 400 ///< time for blink status LED when is module in the AP mode [ms] +#define STATUS_LED_STA_CONNECTING 800 ///< time for blink status LED when is module connecting to the WiFi network [ms] +#define STATUS_LED_STA_CONNECTED 4000 ///< time for blink status LED when is module connected to the WiFi network [ms] +#define STATUS_LED_ERROR 100 ///< time for blink status LED when is module in the error state [ms] + +/* ------------------- TASKS --------------------*/ +#define TASK_SYSTEM 1000 ///< system task interval [ms] +#define TASK_SDCARD 30000 ///< sd card task interval [ms] +#define TASK_WIFI 30000 ///< wifi reconnect interval. Checking when is signal lost [ms] +#define TASK_SERIAL_CFG 1000 ///< serial cfg task interval [ms] +#define TASK_STREAM_TELEMETRY 30000 ///< stream telemetry task interval [ms] +#define TASK_WIFI_WATCHDOG 20000 ///< wifi watchdog task interval [ms] +#define TASK_PHOTO_SEND 1000 ///< photo send task interval [ms] + +/* --------------- WEB SERVER CFG --------------*/ +#define WEB_SERVER_PORT 80 ///< WEB server port +#define SERIAL_PORT_SPEED 115200 ///< baud rate +#define WDG_TIMEOUT 40 ///< wdg timeout [second] +#define PHOTO_FRAGMENT_SIZE 5000 ///< photo fragmentation size [bytes] +#define LOOP_DELAY 100 ///< loop delay [ms] +#define WIFI_CLIENT_WAIT_CON false ///< wait for connecting to WiFi network +#define DYNMIC_JSON_SIZE 1024 ///< maximum size for dynamic json [bytes] +#define WEB_CACHE_INTERVAL 86400 ///< cache interval for browser [s] 86400s = 24h + +/* --------------- OTA UPDATE CFG --------------*/ +#define OTA_UPDATE_API_SERVER "api.github.com" ///< OTA update server URL +#define OTA_UPDATE_API_URL "/repos/prusa3d/Prusa-Firmware-ESP32-Cam/releases/latest" ///< path to file with OTA update +#define OTA_UPDATE_FW_FILE "ESP32_PrusaConnectCam.ino.bin" ///< OTA update firmware file name + +/* ---------- RESET CFG CONFIGURATION ----------*/ +#define CFG_RESET_PIN 12 ///< GPIO 16 is for reset CFG to default +#define CFG_RESET_TIME_WAIT 10000 ///< wait to 10 000 ms = 10s for reset cfg during grounded CFG_RESET_PIN +#define CFG_RESET_LOOP_DELAY 100 ///< delay in the loop for reset cfg + +/* ---------------- MicroSD Logs ----------------*/ +#define LOGS_FILE_NAME "SysLog.log" ///< syslog file name +#define LOGS_FILE_PATH "/" ///< directory for log files +#define LOGS_FILE_MAX_SIZE 1024 ///< maximum file size in the [kb] + +/* ---------------- AP MODE CFG ----------------*/ +#define STA_AP_MODE_TIMEOUT 300000 ///< how long is AP enable after start, when is module in the STA mode [ms] +#define SERVICE_WIFI_SSID_UID true ///< enable/disable added UID to service SSID name +#define SERVICE_WIFI_SSID "ESP32_camera" ///< service WI-FI SSID name. Maximum length SERVICE_WIFI_SSID + UID = 32 +#define SERVICE_WIFI_PASS "12345678" ///< service WI-FI password +#define SERVICE_WIFI_CHANNEL 10 ///< service WI-FI channel +#define SERVICE_LOCAL_IP "192.168.0.1" ///< service WI-FI module IP address +#define SERVICE_LOCAL_GATEWAY "192.168.0.1" ///< service WI-FI module gateway +#define SERVICE_LOCAL_MASK "255.255.255.0" ///< service WI-FI module mask +#define SERVICE_LOCAL_DNS "192.168.0.1" ///< service WI-FI module DNS + +/* ----------------- WiFi CFG -------------------*/ +#define WIFI_STA_WDG_TIMEOUT 60000 ///< STA watchdog timeout [ms] + +/* ---------------- FACTORY CFG ----------------*/ +#define FACTORY_CFG_PHOTO_REFRESH_INTERVAL 30 ///< in the second +#define FACTORY_CFG_PHOTO_QUALITY 10 ///< 10-63, lower is better +#define FACTORY_CFG_FRAME_SIZE 0 ///< 0 - FRAMESIZE_QVGA, ..., 6 - FRAMESIZE_UXGA. Look function Cfg_TransformFrameSizeDataType +#define FACTORY_CFG_BRIGHTNESS 0 ///< from -2 to 2 +#define FACTORY_CFG_CONTRAST 0 ///< from -2 to 2 +#define FACTORY_CFG_SATURATION 0 ///< from -2 to 2 +#define FACTORY_CFG_H_MIRROR 0 ///< Horizontal mirror. 0 - false, 1 - true +#define FACTORY_CFG_V_FLIP 0 ///< Vertical flip. 0 - false, 1 - true +#define FACTORY_CFG_LENS_CORRECT 1 ///< 0 - false, 1 - true +#define FACTORY_CFG_EXPOSURE_CTRL 1 ///< 0 - false, 1 - true +#define FACTORY_CFG_AWB 1 ///< automatic white balancing 0 - false, 1 - true +#define FACTORY_CFG_AWB_GAIN 1 ///< automatic white balancing gain 0 - false, 1 - true +#define FACTORY_CFG_AWB_MODE 0 ///< automatic white balancing mode (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home) +#define FACTORY_CFG_BPC 1 ///< bad pixel detection +#define FACTORY_CFG_WPC 1 ///< white pixel correction +#define FACTORY_CFG_RAW_GAMA 1 ///< raw gama +#define FACTORY_CFG_WEB_AUTH_USERNAME "admin" ///< user name for login to WEB interface. definition WEB_ENABLE_BASIC_AUTH must be true +#define FACTORY_CFG_WEB_AUTH_PASSWORD "admin" ///< password for login to WEB interface. definition WEB_ENABLE_BASIC_AUTH must be true +#define FACTORY_CFG_WEB_AUTH_ENABLE false ///< enable web auth for login to WEB interface. definition WEB_ENABLE_BASIC_AUTH must be +#define FACTORY_CFG_CAMERA_FLASH_ENABLE false ///< enable camera flash functionality +#define FACTORY_CFG_CAMERA_FLASH_TIME 200 ///< time for camera flash duration time [ms] +#define FACTORY_CFG_MDNS_RECORD_HOST "prusa-esp32cam" ///< mdns record http://MDNS_RECORD_HOST.local +#define FACTORY_CFG_AEC2 0 ///< enable automatic exposition +#define FACTORY_CFG_AE_LEVEL 0 ///< automatic exposition level +#define FACTORY_CFG_AEC_VALUE 300 ///< automatic exposition time +#define FACTORY_CFG_GAIN_CTRL 1 ///< enable automatic gain +#define FACTORY_CFG_AGC_GAIN 0 ///< automatic gain controll gain +#define FACTORY_CFG_HOSTNAME "connect.prusa3d.com" ///< hostname for Prusa Connect + +/* ---------------- CFG FLAGS ------------------*/ +#define CFG_WIFI_SETTINGS_SAVED 0x0A ///< flag saved config +#define CFG_WIFI_SETTINGS_NOT_SAVED 0xFF ///< flag notsaved config +#define CFG_FIRST_MCU_START_ACK 0xFF ///< flag first MCU start ACK -> yes, it's first mcu start +#define CFG_FIRST_MCU_START_NAK 0x0F ///< flag first MCU start NAK -> no, it's not first MCU start +#define SECOND_TO_MILISECOND 1000 ///< constant for convert ms to second + +/* ---------------- EEPROM CFG ------------------*/ +#define EEPROM_ADDR_REFRESH_INTERVAL_START 0 ///< whre is stored first byte from refresh data +#define EEPROM_ADDR_REFRESH_INTERVAL_LENGTH 1 ///< how long is the refresh data variable stored in the eeprom [bytes] + +#define EEPROM_ADDR_FINGERPRINT_START (EEPROM_ADDR_REFRESH_INTERVAL_START + EEPROM_ADDR_REFRESH_INTERVAL_LENGTH) ///< where is stored first byte from refresh interval +#define EEPROM_ADDR_FINGERPRINT_LENGTH 80 ///< how long is refresh interval [bytes] + +#define EEPROM_ADDR_TOKEN_START (EEPROM_ADDR_FINGERPRINT_START + EEPROM_ADDR_FINGERPRINT_LENGTH) ///< where is stored first byte from fingerprint +#define EEPROM_ADDR_TOKEN_LENGTH 40 ///< how long is fingerprint [bytes] + +#define EEPROM_ADDR_FRAMESIZE_START (EEPROM_ADDR_TOKEN_START + EEPROM_ADDR_TOKEN_LENGTH) ///< where is stored token +#define EEPROM_ADDR_FRAMESIZE_LENGTH 1 ///< how long is token + +#define EEPROM_ADDR_BRIGHTNESS_START (EEPROM_ADDR_FRAMESIZE_START + EEPROM_ADDR_FRAMESIZE_LENGTH) ///< where is stored framesize +#define EEPROM_ADDR_BRIGHTNESS_LENGTH 1 ///< how long is framesize + +#define EEPROM_ADDR_CONTRAST_START (EEPROM_ADDR_BRIGHTNESS_START + EEPROM_ADDR_BRIGHTNESS_LENGTH) ///< where is stored brightness +#define EEPROM_ADDR_CONTRAST_LENGTH 1 ///< how long is brightness + +#define EEPROM_ADDR_SATURATION_START (EEPROM_ADDR_CONTRAST_START + EEPROM_ADDR_CONTRAST_LENGTH) ///< where is stored contrast +#define EEPROM_ADDR_SATURATION_LENGTH 1 ///< how long is contrast + +#define EEPROM_ADDR_HMIRROR_START (EEPROM_ADDR_SATURATION_START + EEPROM_ADDR_SATURATION_LENGTH) ///< where is stored saturation +#define EEPROM_ADDR_HMIRROR_LENGTH 1 ///< how long is saturation + +#define EEPROM_ADDR_VFLIP_START (EEPROM_ADDR_HMIRROR_START + EEPROM_ADDR_HMIRROR_LENGTH) ///< where is stored hmirror +#define EEPROM_ADDR_VFLIP_LENGTH 1 ///< how long is hmirror + +#define EEPROM_ADDR_LENSC_START (EEPROM_ADDR_VFLIP_START + EEPROM_ADDR_VFLIP_LENGTH) ///< where is stored vflip +#define EEPROM_ADDR_LENSC_LENGTH 1 ///< how long is vflip + +#define EEPROM_ADDR_EXPOSURE_CTRL_START (EEPROM_ADDR_LENSC_START + EEPROM_ADDR_LENSC_LENGTH) ///< where is stored lens correction +#define EEPROM_ADDR_EXPOSURE_CTRL_LENGTH 1 ///< how long is lens correction + +#define EEPROM_ADDR_PHOTO_QUALITY_START (EEPROM_ADDR_EXPOSURE_CTRL_START + EEPROM_ADDR_EXPOSURE_CTRL_LENGTH) ///< where is stored exposure ctrl +#define EEPROM_ADDR_PHOTO_QUALITY_LENGTH 1 ///< how long is exposure ctrl + +#define EEPROM_ADDR_WIFI_SSID_START (EEPROM_ADDR_PHOTO_QUALITY_START + EEPROM_ADDR_PHOTO_QUALITY_LENGTH) ///< where is stored wi-fi ssid +#define EEPROM_ADDR_WIFI_SSID_LENGTH 33 ///< maximum length for IEEE 802.11 is 32 + 1 for save ssid length + +#define EEPROM_ADDR_WIFI_PASSWORD_START (EEPROM_ADDR_WIFI_SSID_START + EEPROM_ADDR_WIFI_SSID_LENGTH) ///< where is stored wifi password +#define EEPROM_ADDR_WIFI_PASSWORD_LENGTH 64 ///< maximum length for IEEE 802.11 is 63 + 1 for save password length + +#define EEPROM_ADDR_WIFI_ACTIVE_FLAG_START (EEPROM_ADDR_WIFI_PASSWORD_START + EEPROM_ADDR_WIFI_PASSWORD_LENGTH) ///< where is stored information about stored cfg +#define EEPROM_ADDR_WIFI_ACTIVE_FLAG_LENGTH 1 ///< maximum lenght for cfg flag + +#define EEPROM_ADDR_BASIC_AUTH_USERNAME_START (EEPROM_ADDR_WIFI_ACTIVE_FLAG_START + EEPROM_ADDR_WIFI_ACTIVE_FLAG_LENGTH) ///< where is stored username for login with basic auth. +#define EEPROM_ADDR_BASIC_AUTH_USERNAME_LENGTH 11 ///< maximum length for username is 10 byte + 1 byte for save length + +#define EEPROM_ADDR_BASIC_AUTH_PASSWORD_START (EEPROM_ADDR_BASIC_AUTH_USERNAME_START + EEPROM_ADDR_BASIC_AUTH_USERNAME_LENGTH) ///< where is stored password for login with basic auth +#define EEPROM_ADDR_BASIC_AUTH_PASSWORD_LENGTH 21 ///< maximum length for password is 20 byte + 1 byte for save length + +#define EEPROM_ADDR_BASIC_AUTH_ENABLE_FLAG_START (EEPROM_ADDR_BASIC_AUTH_PASSWORD_START + EEPROM_ADDR_BASIC_AUTH_PASSWORD_LENGTH) ///< where is stored flag for enable/disable basic auth from user +#define EEPROM_ADDR_BASIC_AUTH_ENABLE_FLAG_LENGTH 1 ///< how long is flag + +#define EEPROM_ADDR_FIRST_MCU_START_FLAG_START (EEPROM_ADDR_BASIC_AUTH_ENABLE_FLAG_START + EEPROM_ADDR_BASIC_AUTH_ENABLE_FLAG_LENGTH) ///< where is stored flag for first MCU start check +#define EEPROM_ADDR_FIRST_MCU_START_FLAG_LENGTH 1 ///< how long is flag + +#define EEPROM_ADDR_CAMERA_FLASH_ENABLE_START (EEPROM_ADDR_FIRST_MCU_START_FLAG_START + EEPROM_ADDR_FIRST_MCU_START_FLAG_LENGTH) ///< where is stored flag for enable/disable camera flash +#define EEPROM_ADDR_CAMERA_FLASH_ENABLE_LENGTH 1 ///< how long is flag + +#define EEPROM_ADDR_CAMERA_FLASH_TIME_START (EEPROM_ADDR_CAMERA_FLASH_ENABLE_START + EEPROM_ADDR_CAMERA_FLASH_ENABLE_LENGTH) ///< where is stored value camera flash during time +#define EEPROM_ADDR_CAMERA_FLASH_TIME_LENGTH 2 ///< how long is the value + +#define EEPROM_ADDR_MDNS_RECORD_START (EEPROM_ADDR_CAMERA_FLASH_TIME_START + EEPROM_ADDR_CAMERA_FLASH_TIME_LENGTH) +#define EEPROM_ADDR_MDNS_RECORD_LENGTH 41 + +#define EEPROM_ADDR_AWB_ENABLE_START (EEPROM_ADDR_MDNS_RECORD_START + EEPROM_ADDR_MDNS_RECORD_LENGTH) +#define EEPROM_ADDR_AWB_ENABLE_LENGTH 1 + +#define EEPROM_ADDR_AWB_GAIN_ENABLE_START (EEPROM_ADDR_AWB_ENABLE_START + EEPROM_ADDR_AWB_ENABLE_LENGTH) +#define EEPROM_ADDR_AWB_GAIN_ENABLE_LENGTH 1 + +#define EEPROM_ADDR_AWB_MODE_ENABLE_START (EEPROM_ADDR_AWB_GAIN_ENABLE_START + EEPROM_ADDR_AWB_GAIN_ENABLE_LENGTH) +#define EEPROM_ADDR_AWB_MODE_ENABLE_LENGTH 1 + +#define EEPROM_ADDR_BPC_ENABLE_START (EEPROM_ADDR_AWB_MODE_ENABLE_START + EEPROM_ADDR_AWB_MODE_ENABLE_LENGTH) +#define EEPROM_ADDR_BPC_ENABLE_LENGTH 1 + +#define EEPROM_ADDR_WPC_ENABLE_START (EEPROM_ADDR_BPC_ENABLE_START + EEPROM_ADDR_BPC_ENABLE_LENGTH) +#define EEPROM_ADDR_WPC_ENABLE_LENGTH 1 + +#define EEPROM_ADDR_RAW_GAMA_ENABLE_START (EEPROM_ADDR_WPC_ENABLE_START + EEPROM_ADDR_WPC_ENABLE_LENGTH) +#define EEPROM_ADDR_RAW_GAMA_ENABLE_LENGTH 1 + +#define EEPROM_ADDR_AEC2_START (EEPROM_ADDR_RAW_GAMA_ENABLE_START + EEPROM_ADDR_RAW_GAMA_ENABLE_LENGTH) +#define EEPROM_ADDR_AEC2_LENGTH 1 + +#define EEPROM_ADDR_AE_LEVEL_START (EEPROM_ADDR_AEC2_START + EEPROM_ADDR_AEC2_LENGTH) +#define EEPROM_ADDR_AE_LEVEL_LENGTH 1 + +#define EEPROM_ADDR_AEC_VALUE_START (EEPROM_ADDR_AE_LEVEL_START + EEPROM_ADDR_AE_LEVEL_LENGTH) +#define EEPROM_ADDR_AEC_VALUE_LENGTH 2 + +#define EEPROM_ADDR_GAIN_CTRL_START (EEPROM_ADDR_AEC_VALUE_START + EEPROM_ADDR_AEC_VALUE_LENGTH) +#define EEPROM_ADDR_GAIN_CTRL_LENGTH 1 + +#define EEPROM_ADDR_AGC_GAIN_START (EEPROM_ADDR_GAIN_CTRL_START + EEPROM_ADDR_GAIN_CTRL_LENGTH) +#define EEPROM_ADDR_AGC_GAIN_LENGTH 1 + +#define EEPROM_ADDR_LOG_LEVEL (EEPROM_ADDR_AGC_GAIN_START + EEPROM_ADDR_AGC_GAIN_LENGTH) +#define EEPROM_ADDR_LOG_LEVEL_LENGTH 1 + +#define EEPROM_ADDR_HOSTNAME_START (EEPROM_ADDR_LOG_LEVEL + EEPROM_ADDR_LOG_LEVEL_LENGTH) +#define EEPROM_ADDR_HOSTNAME_LENGTH 51 + +#define EEPROM_SIZE (EEPROM_ADDR_REFRESH_INTERVAL_LENGTH + EEPROM_ADDR_FINGERPRINT_LENGTH + EEPROM_ADDR_TOKEN_LENGTH + \ + EEPROM_ADDR_FRAMESIZE_LENGTH + EEPROM_ADDR_BRIGHTNESS_LENGTH + EEPROM_ADDR_CONTRAST_LENGTH + \ + EEPROM_ADDR_SATURATION_LENGTH + EEPROM_ADDR_HMIRROR_LENGTH + EEPROM_ADDR_VFLIP_LENGTH + \ + EEPROM_ADDR_LENSC_LENGTH + EEPROM_ADDR_EXPOSURE_CTRL_LENGTH + EEPROM_ADDR_PHOTO_QUALITY_LENGTH + \ + EEPROM_ADDR_PHOTO_QUALITY_LENGTH + EEPROM_ADDR_WIFI_SSID_LENGTH + EEPROM_ADDR_WIFI_PASSWORD_LENGTH + \ + EEPROM_ADDR_WIFI_ACTIVE_FLAG_LENGTH + EEPROM_ADDR_BASIC_AUTH_USERNAME_LENGTH + EEPROM_ADDR_BASIC_AUTH_PASSWORD_LENGTH + \ + EEPROM_ADDR_BASIC_AUTH_ENABLE_FLAG_LENGTH + EEPROM_ADDR_FIRST_MCU_START_FLAG_LENGTH + \ + EEPROM_ADDR_CAMERA_FLASH_ENABLE_LENGTH + EEPROM_ADDR_CAMERA_FLASH_TIME_LENGTH + \ + EEPROM_ADDR_MDNS_RECORD_LENGTH + EEPROM_ADDR_AWB_ENABLE_LENGTH + EEPROM_ADDR_AWB_GAIN_ENABLE_LENGTH + \ + EEPROM_ADDR_AWB_MODE_ENABLE_LENGTH + EEPROM_ADDR_BPC_ENABLE_LENGTH + EEPROM_ADDR_WPC_ENABLE_LENGTH + \ + EEPROM_ADDR_RAW_GAMA_ENABLE_LENGTH + EEPROM_ADDR_AEC2_LENGTH + EEPROM_ADDR_AE_LEVEL_LENGTH + \ + EEPROM_ADDR_AEC_VALUE_LENGTH + EEPROM_ADDR_GAIN_CTRL_LENGTH + EEPROM_ADDR_AGC_GAIN_LENGTH + EEPROM_ADDR_LOG_LEVEL_LENGTH + \ + EEPROM_ADDR_HOSTNAME_LENGTH ) ///< how many bits do we need for eeprom memory + +#endif + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/micro_sd.cpp b/ESP32_PrusaConnectCam/micro_sd.cpp new file mode 100644 index 0000000..0fc867d --- /dev/null +++ b/ESP32_PrusaConnectCam/micro_sd.cpp @@ -0,0 +1,419 @@ +/** + @file micro_sd.cpp + + @brief library for communication with micro-SD card + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#include "micro_sd.h" + +/** + @brief Constructor + @param none + @return none +*/ +MicroSd::MicroSd() { + CardDetected = false; + CardSize = 0; + DetectAfterBoot = false; +} + +/** + @brief Reinit micro SD card + @param none + @return none +*/ +void MicroSd::ReinitCard() { + Serial.println("Reinit micro SD card!"); + Serial.println("Deinit micro SD card"); + SD_MMC.end(); + delay(50); + Serial.println("Init micro SD card"); + InitSdCard(); +} + +/** + @brief Init SD card. And check, if is SD card inserted + @param none + @return none +*/ +void MicroSd::InitSdCard() { + /* Start INIT Micro SD card */ + Serial.println("Start init micro-SD Card"); + + /* set SD card to 1-line/1-bit mode. GPIO 4 is used for LED and for microSD card. But communication is slower. */ + /* https://github.com/espressif/arduino-esp32/blob/master/libraries/SD_MMC/src/SD_MMC.h */ + if (!SD_MMC.begin("/sdcard", true)) { + Serial.println("SD Card Mount Failed"); + CardDetected = false; + CardSize = 0; + //DetectAfterBoot = false; + return; + } + + /* check microSD card and card type */ + uint8_t cardType = SD_MMC.cardType(); + if (cardType == CARD_NONE) { + Serial.println("No SD_MMC card attached"); + CardDetected = false; + CardSize = 0; + //DetectAfterBoot = false; + return; + } + + /* print card type */ + Serial.print("Found card. Card Type: "); + if (cardType == CARD_MMC) { + Serial.print("MMC"); + } else if (cardType == CARD_SD) { + Serial.print("SDSC"); + } else if (cardType == CARD_SDHC) { + Serial.print("SDHC"); + } else { + Serial.print("UNKNOWN"); + } + + /* calculation card size */ + CardSize = SD_MMC.cardSize() / (1024 * 1024); + Serial.printf(", Card Size: %d MB\n", CardSize); + CardDetected = true; + DetectAfterBoot = true; +} + +/** + @brief List directory on the micro SD card + @param fs::FS - card + @param String - Directory name + @param uint8_t - levels + @return none +*/ +void MicroSd::ListDir(fs::FS &fs, String DirName, uint8_t levels) { + if (true == CardDetected) { + Serial.printf("Listing directory: %s\n", DirName.c_str()); + + File root = fs.open(DirName.c_str()); + if (!root) { + Serial.println("Failed to open directory"); + return; + } + if (!root.isDirectory()) { + Serial.println("Not a directory"); + return; + } + + File file = root.openNextFile(); + while (file) { + if (file.isDirectory()) { + Serial.print(" DIR : "); + Serial.println(file.name()); + if (levels) { + ListDir(fs, file.path(), levels - 1); + } + } else { + Serial.print(" FILE: "); + Serial.print(file.name()); + Serial.print(" SIZE: "); + Serial.println(file.size()); + } + file = root.openNextFile(); + } + } +} + +/** + @brief List directory on the micro SD card + @param fs::FS - card + @param String - dir name + @return bool - status +*/ +bool MicroSd::CreateDir(fs::FS &fs, String path) { + bool status = false; + if (true == CardDetected) { +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.printf("Creating Dir: %s... ", path.c_str()); +#endif + + if (fs.mkdir(path.c_str())) { + status = true; + } + +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.println((status == true) ? "Created" : "Failed"); +#endif + } + return status; +} + +/** + @brief remove directory on the micro SD card + @param fs::FS - card + @param String - dir name + @return bool - status +*/ +bool MicroSd::RemoveDir(fs::FS &fs, String path) { + bool status = false; + if (true == CardDetected) { +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.printf("Removing Dir: %s... ", path.c_str()); +#endif + + if (fs.rmdir(path.c_str())) { + status = true; + } + +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.println((status == true) ? "Removed" : "Failed"); +#endif + } + return status; +} + +/** + @brief Read file and print data to console + @param fs::FS - card + @param String - file name + @return none +*/ +void MicroSd::ReadFileConsole(fs::FS &fs, String path) { + if (true == CardDetected) { + Serial.printf("Reading file: %s\n", path.c_str()); + + File file = fs.open(path.c_str()); + if (!file) { + Serial.println("Failed to open file for reading"); + return; + } + + Serial.print("Read from file: "); + while (file.available()) { + Serial.write(file.read()); + } + } +} + +/** + @brief Write message to file + @param fs::FS - card + @param String - file name + @param String - message + @return bool - status +*/ +bool MicroSd::WriteFile(fs::FS &fs, String path, String message) { + bool status = false; + if (true == CardDetected) { +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.printf("Writing file: %s... ", path.c_str()); +#endif + + File file = fs.open(path.c_str(), FILE_WRITE); + if (!file) { +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.printf("Failed to open file for writing"); +#endif + } else { + if (file.print(message.c_str())) { + status = true; + } + +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.println((status == true) ? "File written" : "Write Failed"); +#endif + } + } + return status; +} + +/** + @brief Added text to end of file + @param fs::FS - card + @param String - file name + @param String - message + @return bool - status +*/ +bool MicroSd::AppendFile(fs::FS &fs, String path, String message) { + bool status = false; + + if (true == CardDetected) { +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.printf("Appending to file: %s... ", path.c_str()); +#endif + + File file = fs.open(path.c_str(), FILE_APPEND); + if (!file) { +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.println("Failed to open file for appending"); +#endif + CardDetected = false; + } else { + if (file.print(message.c_str())) { + status = true; + } + +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.println((status == true) ? "Message appended" : "Append Failed"); +#endif + } + } + return status; +} + +/** + @brief Rename file on the SD card + @param fs::FS - card + @param String - origin file name + @param String - new file name + @return bool - status +*/ +bool MicroSd::RenameFile(fs::FS &fs, String path1, String path2) { + bool status = false; + if (true == CardDetected) { +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.printf("Renaming file %s to %s... ", path1.c_str(), path2.c_str()); +#endif + if (fs.rename(path1.c_str(), path2.c_str())) { + status = true; + } + +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.println((status == true) ? "File renamed" : "Rename Failed"); +#endif + } + + return status; +} + +/** + @brief Delete file on the SD card + @param fs::FS - card + @param String - file name + @return bool - status +*/ +bool MicroSd::DeleteFile(fs::FS &fs, String path) { + bool status = false; + if (true == CardDetected) { +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.printf("Deleting file: %s... ", path.c_str()); +#endif + if (fs.remove(path.c_str())) { + status = true; + } + +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.println((status == true) ? "File deleted" : "Delete Failed"); +#endif + } + + return status; +} + +/** + @brief Get file size in the kb + @param fs::FS - card + @param String - file name + @return uint32_t - size +*/ +uint32_t MicroSd::GetFileSize(fs::FS &fs, String path) { + uint32_t ret = 0; + if (true == CardDetected) { +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.printf("Getting file size: %s... ", path.c_str()); +#endif + File file = fs.open(path.c_str(), FILE_APPEND); + if (!file) { +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.println("Failed to open file for appending"); +#endif + return 0; + } + + ret = file.size() / 1024; /* convert from bytes to kb */ +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.printf(" File size: %d\n ", ret); +#endif + } + + return ret; /* kb*/ +} + +/** + @brief Check file count with partial match + @param fs::FS - card + @param String - dir name + @param String - file name + @return int16_t - count +*/ +uint16_t MicroSd::FileCount(fs::FS &fs, String DirName, String FileName) { + uint16_t FileCount = 0; + if (true == CardDetected) { +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.printf("File name count: %s\n", DirName.c_str()); +#endif + + File root = fs.open(DirName.c_str()); + if (!root) { + Serial.println("Failed to open directory"); + return 0; + } + if (!root.isDirectory()) { + Serial.println("Not a directory"); + return 0; + } + + File file = root.openNextFile(); + while (file) { + if (!file.isDirectory()) { +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.print(" FILE: "); + Serial.print(file.name()); + Serial.print(" SIZE: "); + Serial.print(file.size()); +#endif + if (String(file.name()).indexOf(FileName) != -1) { + FileCount++; +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.print(" - MATCH"); +#endif + } +#if (true == CONSOLE_VERBOSE_DEBUG) + Serial.println(""); +#endif + } + file = root.openNextFile(); + } + } + + return FileCount; +} + +/** + @brief Get card detected status + @param none + @return bool - status +*/ +bool MicroSd::GetCardDetectedStatus() { + return CardDetected; +} + +/** + @brief Get card size + @param none + @return uint16_t - size +*/ +uint16_t MicroSd::GetCardSize() { + return CardSize; +} + +/** + @brief Get card detect after boot + @param none + @return bool - status +*/ +bool MicroSd::GetCardDetectAfterBoot() { + return DetectAfterBoot; +} + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/micro_sd.h b/ESP32_PrusaConnectCam/micro_sd.h new file mode 100644 index 0000000..96bf8ce --- /dev/null +++ b/ESP32_PrusaConnectCam/micro_sd.h @@ -0,0 +1,65 @@ +/** + @file micro_sd.h + + @brief library for communication with micro-SD card + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug + + WARNING: ALL data pins must be pulled up to 3.3V with an external 10k Ohm resistor! Note to ESP32 pin 2 (D0): Add a 1K Ohm pull-up resistor to 3.3V after flashing + SD Card | ESP32 + D2 12 + D3 13 + CMD 15 + VSS GND + VDD 3.3V + CLK 14 + VSS GND + D0 2 (add 1K pull up after flashing) + D1 4 +*/ + +#ifndef _MICRO_SD_H_ +#define _MICRO_SD_H_ + +#include "Arduino.h" +#include "FS.h" +#include "SD_MMC.h" + +#include "mcu_cfg.h" +#include "var.h" + +class MicroSd { +private: + bool CardDetected; ///< Card detected status + uint16_t CardSize; ///< Card size + bool DetectAfterBoot; ///< Card detect after boot + +public: + MicroSd(); + ~MicroSd(){}; + + void InitSdCard(); + void ReinitCard(); + + void ListDir(fs::FS &, String, uint8_t); + bool CreateDir(fs::FS &, String); + bool RemoveDir(fs::FS &, String); + void ReadFileConsole(fs::FS &, String); + bool WriteFile(fs::FS &, String, String); + bool AppendFile(fs::FS &, String, String); + bool RenameFile(fs::FS &, String, String); + bool DeleteFile(fs::FS &, String); + uint32_t GetFileSize(fs::FS &, String); + uint16_t FileCount(fs::FS &, String, String); + + bool GetCardDetectedStatus(); + uint16_t GetCardSize(); + bool GetCardDetectAfterBoot(); +}; + +#endif + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/serial_cfg.cpp b/ESP32_PrusaConnectCam/serial_cfg.cpp new file mode 100644 index 0000000..129ccce --- /dev/null +++ b/ESP32_PrusaConnectCam/serial_cfg.cpp @@ -0,0 +1,134 @@ +/** + @file serial_cfg.cpp + + @brief + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#include "serial_cfg.h" + +SerialCfg SystemSerialCfg(&SystemConfig, &SystemLog, &SystemWifiMngt, &Connect); + +/** + @brief Constructor + @param Configuration * - pointer to Configuration object + @param Logs * - pointer to Logs object + @param WiFiMngt * - pointer to WiFiMngt object + @param PrusaConnect * - pointer to PrusaConnect object + @return none +*/ +SerialCfg::SerialCfg(Configuration *i_conf, Logs *i_log, WiFiMngt *i_wifi, PrusaConnect *i_connect) { + config = i_conf; + log = i_log; + wifim = i_wifi; + connect = i_connect; +} + +/** + @brief Initialize serial port + @param none + @return none +*/ +void SerialCfg::ProcessIncommingData() { + + static String inputBuffer = ""; + + while (Serial.available() > 0) { + char incomingChar = Serial.read(); + + if (incomingChar == '\n') { + // Ak nájdeme nový riadok, spracujeme prikaz + ParseIncommingData(inputBuffer); + inputBuffer = ""; // Vynulujeme buffer pre ďalší príkaz + } else { + // Inak pridáme znak do bufferu + inputBuffer += incomingChar; + } + } +} + +/** + @brief Parse incomming data from serial port + @param String command - incomming command + @return none +*/ +void SerialCfg::ParseIncommingData(String command) { + /* check command */ + + if (command.startsWith("setwifissid:") && command.endsWith(";")) { + /* remove prefix "setwifissid:" and end of command symbol ";" */ + wifi_ssid = command.substring(12, command.length() - 1); + log->AddEvent(LogLevel_Info, "--> Console set WiFi SSID: " + wifi_ssid); + wifim->SetStaSsid(wifi_ssid); + + } else if (command.startsWith("setwifipass:") && command.endsWith(";")) { + /* remove prefix "setwifipass:" and end of command symbol ";" */ + wifi_pass = command.substring(12, command.length() - 1); + log->AddEvent(LogLevel_Info, "--> Console set WiFi password: " + wifi_pass); + wifim->SetStaPassword(wifi_pass); + + } else if (command.startsWith("setauthtoken:") && command.endsWith(";")) { + /* remove prefix "setauthtoken:" and end of command symbol ";" */ + auth_token = command.substring(13, command.length() - 1); + log->AddEvent(LogLevel_Info, "--> Console set auth TOKEN for backend: " + auth_token); + connect->SetToken(auth_token); + + } else if (command.startsWith("wificonnect") && command.endsWith(";")) { + log->AddEvent(LogLevel_Info, "--> Console connecting to wifi..."); + wifim->ConnectToSta(); + + } else if (command.startsWith("getwifimode") && command.endsWith(";")) { + log->AddEvent(LogLevel_Info, "--> Console print WiFi mode..."); + Serial.print("wifimode:" + wifim->GetWiFiMode() + ";"); + + } else if (command.startsWith("getwifistastatus") && command.endsWith(";")) { + log->AddEvent(LogLevel_Info, "--> Console print STA status..."); + Serial.print("wifistastatus:" + wifim->GetStaStatus() + ";"); + + } else if (command.startsWith("getwifistaip") && command.endsWith(";")) { + log->AddEvent(LogLevel_Info, "--> Console print STA IP..."); + Serial.print("wifistaip:" + wifim->GetStaIp() + ";"); + + } else if (command.startsWith("getserviceapssid") && command.endsWith(";")) { + log->AddEvent(LogLevel_Info, "--> Console print service WiFi AP SSID..."); + Serial.print("getserviceapssid:" + wifim->GetServiceApSsid() + ";"); + + } else if (command.startsWith("mcureboot") && command.endsWith(";")) { + log->AddEvent(LogLevel_Warning, "--> Reboot MCU!"); + ESP.restart(); + + } else if (command.startsWith("commandslist") && command.endsWith(";")) { + log->AddEvent(LogLevel_Warning, "--> Available commands"); + PrintAvailableCommands(); + + } else { + log->AddEvent(LogLevel_Warning, "--> Unknown command: " + command + "!"); + PrintAvailableCommands(); + return; + } +} + +/** + @brief Print available commands + @param none + @return none +*/ +void SerialCfg::PrintAvailableCommands() { + Serial.println("Available commands: "); + Serial.println("setwifissid:SSID; - set WiFi SSID"); + Serial.println("setwifipass:PASS; - set WiFi password"); + Serial.println("setauthtoken:TOKEN; - set auth TOKEN for backend"); + Serial.println("wificonnect; - connect to WiFi network"); + Serial.println("getwifimode; - get WiFi mode (AP/STA)"); + Serial.println("getwifistastatus; - get STA status (connected/disconnected)"); + Serial.println("getwifistaip; - get STA IP address"); + Serial.println("getserviceapssid;- get service WiFi AP SSID"); + Serial.println("mcureboot; - reboot MCU"); + Serial.println("commandslist; - print available commands"); +} + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/serial_cfg.h b/ESP32_PrusaConnectCam/serial_cfg.h new file mode 100644 index 0000000..475013a --- /dev/null +++ b/ESP32_PrusaConnectCam/serial_cfg.h @@ -0,0 +1,51 @@ +/** + @file wifi_mngt.h + + @brief Library for configuration module via serial port + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#ifndef _SERIAL_CFG_H +#define _SERIAL_CFG_H + +#include "Arduino.h" + +#include "mcu_cfg.h" +#include "var.h" +#include "log.h" +#include "wifi_mngt.h" +#include "cfg.h" +#include "connect.h" + +class WiFiMngt; +class PrusaConnect; + +class SerialCfg { +private: + Configuration *config; ///< pointer to Configuration object + Logs *log; ///< pointer to Logs object + WiFiMngt *wifim; ///< pointer to WiFiMngt object + PrusaConnect *connect; ///< pointer to PrusaConnect object + + String wifi_ssid; ///< wifi ssid + String wifi_pass; ///< wifi password + String auth_token; ///< auth token + +public: + SerialCfg(Configuration*, Logs*, WiFiMngt*, PrusaConnect*); + ~SerialCfg(){}; + + void ProcessIncommingData(); + void ParseIncommingData(String); + void PrintAvailableCommands(); +}; + +extern SerialCfg SystemSerialCfg; ///< SerialCfg object + +#endif + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/server.cpp b/ESP32_PrusaConnectCam/server.cpp new file mode 100644 index 0000000..a6b3b95 --- /dev/null +++ b/ESP32_PrusaConnectCam/server.cpp @@ -0,0 +1,1074 @@ +/** + @file server.cpp + + @brief Library for WEB server + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug + +*/ + +#include "server.h" +#include "Certificate.h" + +AsyncWebServer server(WEB_SERVER_PORT); + +/** + @brief Load configuration from EEPROM + @param none + @return none +*/ +void Server_LoadCfg() { + WebBasicAuth.EnableAuth = SystemConfig.LoadBasicAuthFlag(); + WebBasicAuth.UserName = SystemConfig.LoadBasicAuthUsername(); + WebBasicAuth.Password = SystemConfig.LoadBasicAuthPassword(); +} + +/** + @brief Init WEB server + @param none + @return none +*/ +void Server_InitWebServer() { + SystemLog.AddEvent(LogLevel_Info, "Starting init WEB server"); + + /* route for get last capture photo */ + server.on("/saved-photo.jpg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: get photo"); + if (Server_CheckBasicAuth(request) == false) + return; + + request->send_P(200, "image/jpg", SystemCamera.GetPhotoFb()->buf, SystemCamera.GetPhotoFb()->len); + }); + + /* route to jquery */ + server.on("/jquery-3.7.0.min.js", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get jquery-3.7.0.min.js"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "application/javascript", jquery_3_7_0_js); + }); + + Server_InitWebServer_JsonData(); + Server_InitWebServer_WebPages(); + Server_InitWebServer_Icons(); + Server_InitWebServer_Actions(); + Server_InitWebServer_Sets(); + Server_InitWebServer_Update(); + Server_InitWebServer_Stream(); + + /* route for not found page */ + server.onNotFound(Server_handleNotFound); + + /* start WEB server */ + server.begin(); +} + +/** + @brief Init WEB server json data + @param none + @return none +*/ +void Server_InitWebServer_JsonData() { + /* route for json with cfg parameters */ + server.on("/json_input", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: get json_input"); + if (Server_CheckBasicAuth(request) == false) + return; + request->send_P(200, F("text/plain"), Server_GetJsonData().c_str()); + }); + + /* route for json with wifi networks */ + server.on("/json_wifi", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: get json_wifi"); + if (Server_CheckBasicAuth(request) == false) + return; + request->send_P(200, F("text/plain"), SystemWifiMngt.GetAvailableWifiNetworks().c_str()); + }); + + /* route for san wi-fi networks */ + server.on("/wifi_scan", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: scan WI-FI networks"); + if (Server_CheckBasicAuth(request) == false) + return; + request->send_P(200, F("text/html"), MSG_SCANNING); + SystemWifiMngt.ScanWiFiNetwork(); + }); + + /* route for firmware update processing status */ + server.on("/UpdateProcessing", HTTP_GET, [](AsyncWebServerRequest* request) { + if (Server_CheckBasicAuth(request) == false) + return; + + JsonDocument doc_json; + doc_json["processed_bytes"] = String(FirmwareUpdate.TransferedBytes); + doc_json["processed_percent"] = String(FirmwareUpdate.PercentProcess); + doc_json["updating"] = FirmwareUpdate.Processing; + doc_json["message"] = FirmwareUpdate.UpdatingStatus; + String string_json = ""; + serializeJson(doc_json, string_json); + + request->send(200, "application/json", string_json); + }); +} + +/** + @brief Init WEB server web pages + @param none + @return none +*/ +void Server_InitWebServer_WebPages() { + /* Route for root / web page */ + server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get index.html"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "text/html", index_html); + }); + + /* Route for styles */ + server.on("/styles.css", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get styles.css"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "text/css", styles_css); + }); + + /* Route for java scripts */ + server.on("/scripts.js", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get scripts.js"); + if (Server_CheckBasicAuth(request) == false) + return; + request->send_P(200, "application/javascript", scripts_js); + }); + + + /* Route for config web page */ + server.on("/page_config.html", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get page_config.html"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "text/html", page_config_html); + }); + + /* Route for wifi web page */ + server.on("/page_wifi.html", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get page_wifi.html"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "text/html", page_wifi_html); + }); + + /* Route for auth web page */ + server.on("/page_auth.html", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get page_auth.html"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "text/html", page_auth_html); + }); + + /* Route for system web page */ + server.on("/page_system.html", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get page_system.html"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "text/html", page_system_html); + }); + + /* route to license page */ + server.on("/license.html", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get license.html"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "text/html", license_html); + }); + + /* route to gtac page */ + server.on("/gtac.html", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get gtac.html"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "text/html", gtac_html); + }); + + /* route to privacy policy page */ + server.on("/privacypolicy.html", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get privacypolicy.html"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "text/html", privacypolicy_html); + }); + + /* route to cookie page */ + server.on("/cookie.html", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get cookie.html"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "text/html", cookies_html); + }); + + /* route to logs page */ + server.on("/get_logs", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get get_logs.html"); + if (Server_CheckBasicAuth(request) == false) + return; + + if (true == SystemLog.GetCardDetectedStatus()) { + request->send(SD_MMC, SystemLog.GetFilePath() + SystemLog.GetFileName(), "text/plain"); + } else { + request->send_P(404, "text/plain", "Micro SD card not found with FAT32 partition!"); + } + }); +} + +/** + @brief Init WEB server icons + @param none + @return none +*/ +void Server_InitWebServer_Icons() { + /* route to logo */ + server.on("/esp32_cam.svg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get esp32_cam.svg"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "image/svg+xml", esp32_cam_logo_svg); + }); + + /* route to github icon */ + server.on("/github-icon.svg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get github-icon.svg"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "image/svg+xml", github_icon_svg); + }); + + /* route to light on icon */ + server.on("/light-on-icon.svg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get light-icon.svg"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "image/svg+xml", light_icon_on_svg); + }); + + /* route to light off icon */ + server.on("/light-off-icon.svg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get light-icon.svg"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "image/svg+xml", light_icon_off_svg); + }); + + /* route to refresh icon */ + server.on("/refresh-icon.svg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get refresh-icon.svg"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "image/svg+xml", refresh_icon_svg); + }); + + /* route to reboot icon */ + server.on("/reboot-icon.svg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get reboot-icon.svg"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "image/svg+xml", reboot_icon_svg); + }); + + /* route to wifi icon */ + server.on("/wifi-icon-1.svg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get wifi-icon-1.svg"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "image/svg+xml", wifi_icon_1_svg); + }); + + /* route to wifi icon */ + server.on("/wifi-icon-2.svg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get wifi-icon-2.svg"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "image/svg+xml", wifi_icon_2_svg); + }); + + /* route to wifi icon */ + server.on("/wifi-icon-3.svg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get wifi-icon-3.svg"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "image/svg+xml", wifi_icon_3_svg); + }); + + /* route to wifi icon */ + server.on("/wifi-icon-4.svg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get wifi-icon-4.svg"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "image/svg+xml", wifi_icon_4_svg); + }); + + /* route to wifi icon */ + server.on("/wifi-icon-0.svg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get wifi-icon-0.svg"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "image/svg+xml", wifi_icon_0_svg); + }); + + /* route to eye icon */ + server.on("/eye.svg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get eye.svg"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "image/svg+xml", eye_svg); + }); + + /* route to eye-slash icon */ + server.on("/eye-slash.svg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get eye-slash.svg"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "image/svg+xml", eye_slash_svg); + }); + + /* route to favicon */ + server.on("/favicon.svg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: Get favicon.svg"); + if (Server_CheckBasicAuth(request) == false) + return; + + Server_handleCacheRequest(request, "image/svg+xml", favicon_svg); + }); +} + +/** + @brief Init WEB server actions + @param none + @return none +*/ +void Server_InitWebServer_Actions() { + /*route for capture photo */ + server.on("/action_capture", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: /action_capture Take photo"); + if (Server_CheckBasicAuth(request) == false) + return; + SystemCamera.CapturePhoto(); + request->send_P(200, "text/plain", "Take Photo"); + }); + + /* route for send photo to prusa backend */ + server.on("/action_send", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: /action_send send photo to cloud"); + if (Server_CheckBasicAuth(request) == false) + return; + Connect.SendPhotoToBackend(); + request->send_P(200, "text/plain", "Send Photo"); + }); + + /* route for change LED status */ + server.on("/action_led", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: /action_led Change LED status"); + if (Server_CheckBasicAuth(request) == false) + return; + + SystemCamera.SetFlashStatus(!SystemCamera.GetFlashStatus()); + SystemCamera.SetCameraFlashEnable(false); + + request->send_P(200, "text/plain", "Change LED status"); + }); + + /* reboot MCU */ + server.on("/action_reboot", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: /action_reboo reboot MCU!"); + if (Server_CheckBasicAuth(request) == false) + return; + request->send_P(200, F("text/html"), MSG_REBOOT_MCU); + delay(100); /* wait for sending data */ + ESP.restart(); + }); +} + +/** + @brief Init WEB server sets + @param none + @return none +*/ +void Server_InitWebServer_Sets() { + /* route to set integer value */ + server.on("/set_int", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: /set_int"); + if (Server_CheckBasicAuth(request) == false) + return; + + bool response = false; + String response_msg = ""; + + /* set refresh interval */ + if (request->hasParam("refresh")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set refresh interval"); + uint8_t value = request->getParam("refresh")->value().toInt(); + if ((value >= REFRESH_INTERVAL_MIN) && (value <= REFRESH_INTERVAL_MAX)) { + Connect.SetRefreshInterval(value); + response_msg = MSG_SAVE_OK; + } else { + response_msg = "ERROR! Bad value. Minimum is "; + response_msg += String(REFRESH_INTERVAL_MIN); + response_msg += ", maximum "; + response_msg += String(REFRESH_INTERVAL_MAX); + response_msg += " second"; + } + response = true; + } + + /* set saturation */ + if (request->hasParam("saturation")) { + SystemLog.AddEvent(LogLevel_Verbose, "set saturation"); + SystemCamera.SetSaturation(request->getParam("saturation")->value().toInt()); + response_msg = MSG_SAVE_OK; + response = true; + } + + /* set contrast */ + if (request->hasParam("contrast")) { + SystemLog.AddEvent(LogLevel_Verbose, "set contrast"); + SystemCamera.SetContrast(request->getParam("contrast")->value().toInt()); + response_msg = MSG_SAVE_OK; + response = true; + } + + /* set brightness */ + if (request->hasParam("brightness")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set brightness"); + SystemCamera.SetBrightness(request->getParam("brightness")->value().toInt()); + response_msg = MSG_SAVE_OK; + response = true; + } + + /* set frame size */ + if (request->hasParam("framesize")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set framesize"); + SystemCamera.SetFrameSize(request->getParam("framesize")->value().toInt()); + response_msg = MSG_SAVE_OK; + response = true; + } + + /* set photo quality */ + if (request->hasParam("photo_quality")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set photo_quality"); + SystemCamera.SetPhotoQuality(73 - request->getParam("photo_quality")->value().toInt()); + response_msg = MSG_SAVE_OK; + response = true; + } + + /* set flash time */ + if (request->hasParam("flash_time")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set flash_time"); + SystemCamera.SetCameraFlashTime(request->getParam("flash_time")->value().toInt()); + response_msg = MSG_SAVE_OK; + response = true; + } + + /* set white balancing mode */ + if (request->hasParam("wb_mode")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set wb_mode"); + SystemCamera.SetAwbMode(request->getParam("wb_mode")->value().toInt()); + response_msg = MSG_SAVE_OK; + response = true; + } + + /* set auto exposition level */ + if (request->hasParam("ae_level")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set ae_level"); + SystemCamera.SetAeLevel(request->getParam("ae_level")->value().toInt()); + response_msg = MSG_SAVE_OK; + response = true; + } + + /* set auto exposition controll value */ + if (request->hasParam("aec_value")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set aec_value"); + SystemCamera.SetAecValue(request->getParam("aec_value")->value().toInt()); + response_msg = MSG_SAVE_OK; + response = true; + } + + /* set auto gain correction value */ + if (request->hasParam("agc_gain")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set agc_gain"); + SystemCamera.SetAgcGain(request->getParam("agc_gain")->value().toInt()); + response_msg = MSG_SAVE_OK; + response = true; + } + + /* set log level /set_int?log_level=2 */ + if (request->hasParam("log_level")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set log_level"); + LogLevel_enum level = (LogLevel_enum)request->getParam("log_level")->value().toInt(); + if ((level >= LogLevel_Error) && (level <= LogLevel_Verbose)) { + SystemConfig.SaveLogLevel(level); + SystemLog.SetLogLevel(level); + response_msg = MSG_SAVE_OK; + } else { + response_msg = MSG_SAVE_NOTOK; + } + + response = true; + } + + if (true == response) { + request->send_P(200, F("text/html"), response_msg.c_str()); + } + }); + + /* route to set bool value */ + server.on("/set_bool", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: /set_bool"); + if (Server_CheckBasicAuth(request) == false) + return; + + bool response = false; + + /* check cfg for hmirror */ + if (request->hasParam("hmirror")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set hmirror"); + SystemCamera.SetHMirror(Server_TransfeStringToBool(request->getParam("hmirror")->value())); + response = true; + } + + /* set vertical flip */ + if (request->hasParam("vflip")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set hmirror"); + SystemCamera.SetVFlip(Server_TransfeStringToBool(request->getParam("vflip")->value())); + response = true; + } + + /* set lens correction */ + if (request->hasParam("lenc")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set lensc"); + SystemCamera.SetLensC(Server_TransfeStringToBool(request->getParam("lenc")->value())); + response = true; + } + + /* set exposure controll */ + if (request->hasParam("exposure_ctrl")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set exposure ctrl"); + SystemCamera.SetExposureCtrl(Server_TransfeStringToBool(request->getParam("exposure_ctrl")->value())); + response = true; + } + + /* set auto white balancing */ + if (request->hasParam("awb")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set awb"); + SystemCamera.SetAwb(Server_TransfeStringToBool(request->getParam("awb")->value())); + response = true; + } + + /* set auto white balancing gain */ + if (request->hasParam("awb_gain")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set awb_gain"); + SystemCamera.SetAwbGain(Server_TransfeStringToBool(request->getParam("awb_gain")->value())); + response = true; + } + + /* set bad pixel correction */ + if (request->hasParam("bpc")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set bpc"); + SystemCamera.SetBpc(Server_TransfeStringToBool(request->getParam("bpc")->value())); + response = true; + } + + /* set white pixel correction */ + if (request->hasParam("wpc")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set wpc"); + SystemCamera.SetWpc(Server_TransfeStringToBool(request->getParam("wpc")->value())); + response = true; + } + + /* set raw gama correction */ + if (request->hasParam("raw_gama")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set raw_gama"); + SystemCamera.SetRawGama(Server_TransfeStringToBool(request->getParam("raw_gama")->value())); + response = true; + } + + /* set automatic exposure correction */ + if (request->hasParam("aec2")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set aec2"); + SystemCamera.SetAec2(Server_TransfeStringToBool(request->getParam("aec2")->value())); + response = true; + } + + /* set gain controll */ + if (request->hasParam("gain_ctrl")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set gain_ctrl"); + SystemCamera.SetGainCtrl(Server_TransfeStringToBool(request->getParam("gain_ctrl")->value())); + response = true; + } + + /* set flash */ + if (request->hasParam("flash")) { + SystemLog.AddEvent(LogLevel_Verbose, "Set flash"); + SystemCamera.SetCameraFlashEnable(Server_TransfeStringToBool(request->getParam("flash")->value())); + SystemCamera.SetFlashStatus(false); + response = true; + } + + if (true == response) { + request->send_P(200, F("text/html"), MSG_SAVE_OK); + } + }); + + /* route for set token for authentification to prusa backend*/ + server.on("/set_token", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: /set_token"); + if (Server_CheckBasicAuth(request) == false) + return; + request->send_P(200, F("text/html"), MSG_SAVE_OK); + + if (request->hasParam("token")) { + Connect.SetToken(request->getParam("token")->value()); + } + }); + + /* route for set prusa connect hostname */ + server.on("/set_hostname", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: /set_hostname"); + if (Server_CheckBasicAuth(request) == false) + return; + request->send_P(200, F("text/html"), MSG_SAVE_OK); + + if (request->hasParam("hostname")) { + Connect.SetPrusaConnectHostname(request->getParam("hostname")->value()); + } + }); + + /* route for set WI-FI credentials */ + server.on("/wifi_cfg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: set WI-FI credentials"); + if (Server_CheckBasicAuth(request) == false) + return; + + String TmpPassword = ""; + String TmpSsid = ""; + /* get SSID */ + if (request->hasParam("wifi_ssid")) { + TmpSsid = request->getParam("wifi_ssid")->value(); + } + + /* get password */ + if (request->hasParam("wifi_pass")) { + TmpPassword = request->getParam("wifi_pass")->value(); + } + + /* check min and max length WI-FI ssid and password */ + if (((TmpPassword.length() > 0) && (TmpSsid.length() > 0)) && ((TmpPassword.length() < EEPROM_ADDR_WIFI_PASSWORD_LENGTH) && (TmpSsid.length() < EEPROM_ADDR_WIFI_SSID_LENGTH))) { + /* send OK response */ + request->send_P(200, F("text/html"), MSG_SAVE_OK_WIFI); + + /* save ssid and password */ + SystemWifiMngt.SetStaCredentials(TmpSsid,TmpPassword); + SystemWifiMngt.WiFiStaConnect(); + + } else { + request->send_P(200, F("text/html"), MSG_SAVE_NOTOK); + } + }); + + /* route for set basic auth */ + server.on("/basicauth_cfg", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: set basic auth user name and password"); + bool ret = false; + String ret_msg = ""; + + if (Server_CheckBasicAuth(request) == false) + return; + + /* get username */ + if (request->hasParam("auth_username")) { + WebBasicAuth.UserName = request->getParam("auth_username")->value(); + + /* check min and max length */ + if ((WebBasicAuth.UserName.length() > 0) && (WebBasicAuth.UserName.length() < EEPROM_ADDR_BASIC_AUTH_USERNAME_LENGTH)) { + SystemConfig.SaveBasicAuthUsername(WebBasicAuth.UserName); + ret = true; + } else { + ret = false; + ret_msg = "Maximum username length: "; + ret_msg += String(EEPROM_ADDR_BASIC_AUTH_USERNAME_LENGTH); + } + } + + /* get password */ + if (request->hasParam("auth_password")) { + WebBasicAuth.Password = request->getParam("auth_password")->value(); + + /* check min and max length */ + if ((WebBasicAuth.Password.length() > 0) && (WebBasicAuth.Password.length() < EEPROM_ADDR_BASIC_AUTH_PASSWORD_LENGTH)) { + SystemConfig.SaveBasicAuthPassword(WebBasicAuth.Password); + ret = true; + } else { + ret = false; + ret_msg = "Maximum password length: "; + ret_msg += String(EEPROM_ADDR_BASIC_AUTH_PASSWORD_LENGTH); + } + } + + /* get enable / disable we bauth */ + if (request->hasParam("basicauth_enable")) { + WebBasicAuth.EnableAuth = Server_TransfeStringToBool(request->getParam("basicauth_enable")->value()); + SystemConfig.SaveBasicAuthFlag(WebBasicAuth.EnableAuth); + ret = true; + } + + /* send OK response */ + if (true == ret) { + request->send_P(200, F("text/html"), MSG_SAVE_OK); + + } else { + String msg = MSG_SAVE_NOTOK; + msg += " "; + msg += ret_msg; + request->send_P(200, F("text/html"), msg.c_str()); + } + }); + + /* route for set firmware size */ + server.on("/set_firmware_size", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Info, "WEB server: /set_firmware_size"); + if (Server_CheckBasicAuth(request) == false) + return; + request->send_P(200, F("text/html"), MSG_SAVE_OK); + + /* check cfg for flash */ + if (request->hasParam("size")) { + SystemLog.AddEvent(LogLevel_Info, String(request->getParam("size")->value().toInt())); + FirmwareUpdate.FirmwareSize = request->getParam("size")->value().toInt(); + } + }); + + /* route for set firmware size */ + server.on("/set_mdns", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: /set_mdns"); + if (Server_CheckBasicAuth(request) == false) + return; + + /* check cfg for mdns */ + if (request->hasParam("mdns")) { + String tmp = request->getParam("mdns")->value(); + if (tmp.length() < EEPROM_ADDR_MDNS_RECORD_LENGTH) { + request->send_P(200, F("text/html"), MSG_SAVE_OK_REBOOT); + SystemWifiMngt.SetMdns(tmp); + + } else { + String msg = "Error save mDNS. Maximum length: " + String(EEPROM_ADDR_MDNS_RECORD_LENGTH); + request->send_P(200, F("text/html"), msg.c_str()); + } + } + }); +} + +/** + @brief Init WEB FW update + @param none + @return none +*/ +void Server_InitWebServer_Update() { + System_UpdateInit(); + + /* route for firmware update with file */ + server.on( + "/upload", HTTP_POST, [](AsyncWebServerRequest* request) { + String string_json = ""; + JsonDocument doc_json; + + String responseMessage = (Update.hasError()) ? SYSTEM_MSG_UPDATE_FAIL : SYSTEM_MSG_UPDATE_DONE; + doc_json["message"] = responseMessage; + FirmwareUpdate.UpdatingStatus = responseMessage; + + /* get error string */ + if (Update.hasError()) { + String errorMessage = Update.errorString(); + doc_json["errorMessage"] = errorMessage; + FirmwareUpdate.UpdatingStatus += errorMessage; + FirmwareUpdate.Processing = false; + } + + /* make and send json msg */ + serializeJson(doc_json, string_json); + AsyncWebServerResponse* response = request->beginResponse(200, "application/json", string_json); + response->addHeader("Connection", "close"); + request->send(response); + }, + [](AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) { + if (!index) { + FirmwareUpdate.Processing = true; + SystemLog.AddEvent(LogLevel_Info, "Start FW update from file: " + filename); + FirmwareUpdate.UpdatingStatus = String(SYSTEM_MSG_UPDATE_PROCESS); + if (!Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASH)) { + Update.printError(Serial); + } + } + + if (Update.write(data, len) != len) { + Update.printError(Serial); + } + + if (final) { + FirmwareUpdate.Processing = false; + if (Update.end(true)) { + FirmwareUpdate.UpdatingStatus = String(SYSTEM_MSG_UPDATE_DONE); + SystemLog.AddEvent(LogLevel_Info, "Update FW from file done. Reboot MCU"); + } else { + Update.printError(Serial); + SystemLog.AddEvent(LogLevel_Error, String(SYSTEM_MSG_UPDATE_FAIL)); + } + } + } + ); + + /* route for start web OTA update from server */ + server.on("/web_ota_update", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Info, "WEB server: /web_ota_update"); + if (Server_CheckBasicAuth(request) == false) + return; + request->send_P(200, F("text/html"), MSG_UPDATE_START); + FirmwareUpdate.Processing = true; + + /* check flag */ + if (request->hasParam("update")) { + bool ret = Server_TransfeStringToBool(request->getParam("update")->value()); + if (ret == true) { + FirmwareUpdate.StartOtaUpdate = true; + } else { + FirmwareUpdate.StartOtaUpdate = false; + } + } + }); + + /* get OTA FW version on the server */ + server.on("/check_web_ota_update", HTTP_GET, [](AsyncWebServerRequest* request) { + SystemLog.AddEvent(LogLevel_Verbose, "WEB server: /check_web_ota_update"); + if (Server_CheckBasicAuth(request) == false) + return; + + System_CheckNewVersion(); + request->send_P(200, F("text/html"), FirmwareUpdate.CheckNewVersionFwStatus.c_str()); + }); +} + +void Server_InitWebServer_Stream() { + server.on("/stream.mjpg", HTTP_GET, Server_streamJpg); +} + +/** + * @brief Handle cache request + * + * @param AsyncWebServerRequest* - http request + * @param const char* - content type + * @param const char* - data + */ +void Server_handleCacheRequest(AsyncWebServerRequest* request, const char* contentType, const char* data) { + AsyncWebServerResponse *response = request->beginResponse_P(200, contentType, data); + response->addHeader("Cache-Control", "public, max-age=" + String(WEB_CACHE_INTERVAL)); + request->send(response); +} + +/** + @brief if the page was not found on ESP, then print which page is not there + @param AsyncWebServerRequest* - request + @return none +*/ +void Server_handleNotFound(AsyncWebServerRequest* request) { + String message = "URL not Found\n\n"; + + message += "URI: "; + message += request->url(); + message += "\nMethod: "; + message += (request->method() == HTTP_GET) ? "GET" : "POST"; + message += "\nArguments: "; + message += request->args(); + message += "\n"; + + for (uint8_t i = 0; i < request->args(); i++) { + message += " " + request->argName(i) + ": " + request->arg(i) + "\n"; + } + + request->send(404, "text/plain", message); +} + +/** + @brief make json data for WEB page on the ESP32 + @param none + @return String - json data +*/ +String Server_GetJsonData() { + String uptime = ""; + String string_json = ""; + JsonDocument doc_json; + Server_GetModuleUptime(uptime); + + doc_json["token"] = Connect.GetToken(); + doc_json["fingerprint"] = Connect.GetFingerprint(); + doc_json["refreshInterval"] = String(Connect.GetRefreshInterval()); + doc_json["photoquality"] = String(73 - SystemCamera.GetPhotoQuality()); + doc_json["framesize"] = String(SystemCamera.GetFrameSize()); + doc_json["brightness"] = String(SystemCamera.GetBrightness()); + doc_json["contrast"] = String(SystemCamera.GetContrast()); + doc_json["saturation"] = String(SystemCamera.GetSaturation()); + doc_json["hmirror"] = (SystemCamera.GetHMirror() == true) ? "true" : ""; + doc_json["vflip"] = (SystemCamera.GetVFlip() == true) ? "true" : ""; + doc_json["lensc"] = (SystemCamera.GetLensC() == true) ? "true" : ""; + doc_json["exposure_ctrl"] = (SystemCamera.GetExposureCtrl() == true) ? "true" : ""; + doc_json["awb"] = (SystemCamera.GetAwb() == true) ? "true" : ""; + doc_json["awb_gain"] = (SystemCamera.GetAwbGain() == true) ? "true" : ""; + doc_json["wb_mode"] = String(SystemCamera.GetAwbMode()); + doc_json["bpc"] = (SystemCamera.GetBpc() == true) ? "true" : ""; + doc_json["wpc"] = (SystemCamera.GetWpc() == true) ? "true" : ""; + doc_json["raw_gama"] = (SystemCamera.GetRawGama() == true) ? "true" : ""; + doc_json["aec2"] = (SystemCamera.GetAec2() == true) ? "true" : ""; + doc_json["ae_level"] = SystemCamera.GetAeLevel(); + doc_json["aec_value"] = SystemCamera.GetAecValue(); + doc_json["gain_ctrl"] = (SystemCamera.GetGainCtrl() == true) ? "true" : ""; + doc_json["agc_gain"] = SystemCamera.GetAgcGaint(); + doc_json["led"] = (SystemCamera.GetFlashStatus() == true) ? "true" : ""; + doc_json["flash"] = (SystemCamera.GetCameraFlashEnable() == true) ? "true" : ""; + doc_json["flash_time"] = SystemCamera.GetCameraFlashTime(); + doc_json["ssid"] = SystemWifiMngt.GetStaSsid(); + doc_json["bssid"] = SystemWifiMngt.GetStaBssid(); + doc_json["rssi"] = String(WiFi.RSSI()); + doc_json["rssi_percentage"] = String(SystemWifiMngt.Rssi2Percent(WiFi.RSSI())); + doc_json["tx_power"] = SystemWifiMngt.TranslateTxPower(WiFi.getTxPower()); + doc_json["ip"] = WiFi.localIP().toString(); + doc_json["wifi_mode"] = SystemWifiMngt.GetWiFiMode(); + doc_json["mdns"] = SystemWifiMngt.GetMdns(); + doc_json["service_ap_ssid"] = SystemWifiMngt.GetServiceApSsid(); + doc_json["auth"] = (WebBasicAuth.EnableAuth == true) ? "true" : ""; + doc_json["auth_username"] = WebBasicAuth.UserName; + doc_json["last_upload_status"] = Connect.GetBackendReceivedStatus(); + doc_json["wifi_network_status"] = SystemWifiMngt.GetStaStatus(); + doc_json["log_level"] = String(SystemLog.GetLogLevel()); + doc_json["uptime"] = uptime; + doc_json["user_name"] = WebBasicAuth.UserName; + doc_json["hostname"] = Connect.GetPrusaConnectHostname(); + doc_json["sw_build"] = SW_BUILD; + doc_json["sw_ver"] = SW_VERSION; + doc_json["sw_new_ver"] = FirmwareUpdate.NewVersionFw; + + serializeJson(doc_json, string_json); + SystemLog.AddEvent(LogLevel_Verbose, string_json); + return string_json; +} + +/** + @brief If basic auth is enabled, then check if the user is logged in + @param AsyncWebServerRequest - request + @return bool - status +*/ +bool Server_CheckBasicAuth(AsyncWebServerRequest* request) { + if ((!request->authenticate(WebBasicAuth.UserName.c_str(), WebBasicAuth.Password.c_str())) && (true == WebBasicAuth.EnableAuth)) { + SystemLog.AddEvent(LogLevel_Verbose, "Unauthorized! Sending longin request"); + request->requestAuthentication(); + return false; + } + + return true; +} + +/** + @brief Stream JPG image from camera + @param AsyncWebServerRequest - request + @return void +*/ +void Server_streamJpg(AsyncWebServerRequest *request) { + AsyncJpegStreamResponse *response = new AsyncJpegStreamResponse(&SystemCamera, &SystemLog); + if (!response) { + request->send(501); + return; + } + response->addHeader("Access-Control-Allow-Origin", "*"); + request->send(response); +} + +/** + @brief Get module uptime + @param String - uptime value + @return void +*/ +void Server_GetModuleUptime(String& readableTime) { + unsigned long currentMillis; + unsigned long seconds; + unsigned long minutes; + unsigned long hours; + unsigned long days; + + currentMillis = millis(); + seconds = currentMillis / 1000; + minutes = seconds / 60; + hours = minutes / 60; + days = hours / 24; + currentMillis %= 1000; + seconds %= 60; + minutes %= 60; + hours %= 24; + + readableTime = String(days) + " days, "; + + if (hours < 10) { + readableTime += "0"; + } + readableTime += String(hours) + ":"; + + if (minutes < 10) { + readableTime += "0"; + } + readableTime += String(minutes) + ":"; + + if (seconds < 10) { + readableTime += "0"; + } + readableTime += String(seconds); +} + +/** + @brief Convert string bool variable to bool + @param String - data + @return bool - status +*/ +bool Server_TransfeStringToBool(String data) { + bool ret = false; + if (data.indexOf("true") >= 0) { + ret = true; + } else if (data.indexOf("false") >= 0) { + ret = false; + } + + return ret; +} + +/* EOF */ diff --git a/ESP32_PrusaConnectCam/server.h b/ESP32_PrusaConnectCam/server.h new file mode 100644 index 0000000..fb6ff9d --- /dev/null +++ b/ESP32_PrusaConnectCam/server.h @@ -0,0 +1,65 @@ +/** + @file server.h + + @brief Library for WEB server + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug + +*/ + +#ifndef _SERVER_H_ +#define _SERVER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "WebPage.h" +#include "WebPage_Icons.h" +#include "mcu_cfg.h" +#include "var.h" +#include "camera.h" +#include "cfg.h" +#include "jquery.h" +#include "system.h" +#include "log.h" +#include "connect.h" +#include "wifi_mngt.h" +#include "stream.h" + +extern AsyncWebServer server; ///< global variable for web server + +void Server_LoadCfg(); +void Server_InitWebServer(); +void Server_InitWebServer_JsonData(); +void Server_InitWebServer_WebPages(); +void Server_InitWebServer_Icons(); +void Server_InitWebServer_Actions(); +void Server_InitWebServer_Sets(); +void Server_InitWebServer_Update(); +void Server_InitWebServer_Stream(); + +void Server_handleCacheRequest(AsyncWebServerRequest*, const char*, const char*); +void Server_handleNotFound(AsyncWebServerRequest *); +String Server_GetJsonData(); +bool Server_CheckBasicAuth(AsyncWebServerRequest *); + +void Server_streamJpg(AsyncWebServerRequest *); + +void Server_GetModuleUptime(String &); +bool Server_TransfeStringToBool(String); + +#endif + +/* EOF */ diff --git a/ESP32_PrusaConnectCam/stream.cpp b/ESP32_PrusaConnectCam/stream.cpp new file mode 100644 index 0000000..6e9a122 --- /dev/null +++ b/ESP32_PrusaConnectCam/stream.cpp @@ -0,0 +1,290 @@ +/** + @file stream.cpp + + @brief stream library. + + Inspiration is from git repository https://gist.github.com/me-no-dev/d34fba51a8f059ac559bf62002e61aa3 + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ +#include "stream.h" + +#define PART_BOUNDARY "123456789000000000000987654321" ///< Must be unique for each stream +static const char *STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY; ///< content type for stream +static const char *STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n"; ///< boundary for stream +static const char *STREAM_PART = "Content-Type: %s\r\nContent-Length: %u\r\n\r\n"; ///< part for stream +static const char *JPG_CONTENT_TYPE = "image/jpeg"; ///< content type for jpg + +/** + * @brief Construct a new Async Buffer Response:: Async Buffer Response object + * + * @param buf + * @param len + * @param contentType + */ +AsyncBufferResponse::AsyncBufferResponse(uint8_t *buf, size_t len, const char *contentType) { + _buf = buf; + _len = len; + _callback = nullptr; + _code = 200; + _contentLength = _len; + _contentType = contentType; + _index = 0; +} + +/** + * @brief Destroy the Async Buffer Response:: Async Buffer Response object + * + */ +AsyncBufferResponse::~AsyncBufferResponse() { + if (_buf != nullptr) { + free(_buf); + } +} + +/** + * @brief Check if source is valid + * + * @return bool + */ +bool AsyncBufferResponse::_sourceValid() const { + return _buf != nullptr; +} + +/** + * @brief Fill buffer + * + * @param buf + * @param maxLen + * @return size_t + */ +size_t AsyncBufferResponse::_fillBuffer(uint8_t *buf, size_t maxLen) { + size_t ret = _content(buf, maxLen, _index); + if (ret != RESPONSE_TRY_AGAIN) { + _index += ret; + } + return ret; +} + +/** + * @brief Content + * + * @param buffer + * @param maxLen + * @param index + * @return size_t + */ +size_t AsyncBufferResponse::_content(uint8_t *buffer, size_t maxLen, size_t index) { + memcpy(buffer, _buf + index, maxLen); + if ((index + maxLen) == _len) { + free(_buf); + _buf = nullptr; + } + return maxLen; +} + +/** + * @brief Construct a new Async Frame Response:: Async Frame Response object + * + * @param frame + * @param contentType + */ +AsyncFrameResponse::AsyncFrameResponse(camera_fb_t *frame, const char *contentType) { + _callback = nullptr; + _code = 200; + _contentLength = frame->len; + _contentType = contentType; + _index = 0; + fb = frame; +} + +/** + * @brief Destroy the Async Frame Response:: Async Frame Response object + * + */ +AsyncFrameResponse::~AsyncFrameResponse() { + if (fb != nullptr) { + esp_camera_fb_return(fb); + } +} + +/** + * @brief Check if source is valid + * + * @return bool + */ +bool AsyncFrameResponse::_sourceValid() const { + return fb != nullptr; +} + +/** + * @brief Fill buffer + * + * @param buf + * @param maxLen + * @return size_t + */ +size_t AsyncFrameResponse::_fillBuffer(uint8_t *buf, size_t maxLen) { + size_t ret = _content(buf, maxLen, _index); + if (ret != RESPONSE_TRY_AGAIN) { + _index += ret; + } + return ret; +} + +/** + * @brief Content + * + * @param buffer + * @param maxLen + * @param index + * @return size_t + */ +size_t AsyncFrameResponse::_content(uint8_t *buffer, size_t maxLen, size_t index) { + memcpy(buffer, fb->buf + index, maxLen); + if ((index + maxLen) == fb->len) { + esp_camera_fb_return(fb); + fb = nullptr; + } + return maxLen; +} + +/** + * @brief Construct a new Async Jpeg Stream Response:: Async Jpeg Stream Response object + * + * @param i_cam + * @param i_log + */ +AsyncJpegStreamResponse::AsyncJpegStreamResponse(Camera *i_cam, Logs *i_log) { + _callback = nullptr; + _code = 200; + _contentLength = 0; + _contentType = STREAM_CONTENT_TYPE; + _sendContentLength = false; + _chunked = true; + _index = 0; + lastAsyncRequest = 0; + memset(&_frame, 0, sizeof(camera_frame_t)); + camera = i_cam; + log = i_log; + camera->SetStreamStatus(true); +} + +/** + * @brief Destroy the Async Jpeg Stream Response:: Async Jpeg Stream Response object + * + */ +AsyncJpegStreamResponse::~AsyncJpegStreamResponse() { + camera->SetStreamStatus(false); + camera->CaptureReturnFrameBuffer(); +} + +/** + * @brief Check if source is valid + * + * @return bool + */ +bool AsyncJpegStreamResponse::_sourceValid() const { + return true; +} + +/** + * @brief Fill buffer + * + * @param buf + * @param maxLen + * @return size_t + */ +size_t AsyncJpegStreamResponse::_fillBuffer(uint8_t *buf, size_t maxLen) { + size_t ret = _content(buf, maxLen, _index); + if (ret != RESPONSE_TRY_AGAIN) { + _index += ret; + } + return ret; +} + +/** + * @brief Content - get picture from camera and send it to client + * + * @param buffer + * @param maxLen + * @param index + * @return size_t + */ +size_t AsyncJpegStreamResponse::_content(uint8_t *buffer, size_t maxLen, size_t index) { + + if (!_frame.fb || _frame.index == _frame.fb->len) { + delay(1); + + if (index && _frame.fb) { + uint64_t end = (uint64_t)micros(); + int fp = (end - lastAsyncRequest) / 1000; + float fps = 1000.0 / fp; + char buf[50] = { '\0' }; + camera->StreamSetFrameSize(_frame.fb->len / 1024); + camera->StreamSetFrameFps(fps); + //sprintf(buf, "Size: %uKB, Time: %ums (%.1f fps)", _frame.fb->len / 1024, fp, fps); + sprintf(buf, "Size: %uKB, FPS: %.1f", _frame.fb->len / 1024, fps); + Serial.println(buf); + lastAsyncRequest = end; + camera->CaptureReturnFrameBuffer(); + _frame.fb = NULL; + } + + /* check space for headers */ + if (maxLen < (strlen(STREAM_BOUNDARY) + strlen(STREAM_PART) + strlen(JPG_CONTENT_TYPE) + 8)) { + log->AddEvent(LogLevel_Error, "Stream Not space for headers"); + return RESPONSE_TRY_AGAIN; + } + + /* get frame */ + _frame.index = 0; + camera->CaptureStream(&_dframe); + _frame.fb = &_dframe; + + if (_frame.fb == NULL) { + log->AddEvent(LogLevel_Error, "Stream capture frame failed"); + return 0; + } + + /* send boundary */ + size_t blen = 0; + if (index) { + blen = strlen(STREAM_BOUNDARY); + memcpy(buffer, STREAM_BOUNDARY, blen); + buffer += blen; + } + + /* send header */ + size_t hlen = sprintf((char *)buffer, STREAM_PART, JPG_CONTENT_TYPE, _frame.fb->len); + buffer += hlen; + + /* send frame */ + hlen = maxLen - hlen - blen; + if (hlen > _frame.fb->len) { + maxLen -= hlen - _frame.fb->len; + hlen = _frame.fb->len; + } + + memcpy(buffer, _frame.fb->buf, hlen); + _frame.index += hlen; + delay(1); + return maxLen; + } + + /* check next send data */ + size_t available = _frame.fb->len - _frame.index; + if (maxLen > available) { + maxLen = available; + } + + delay(1); + memcpy(buffer, _frame.fb->buf + _frame.index, maxLen); + _frame.index += maxLen; + return maxLen; +} + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/stream.h b/ESP32_PrusaConnectCam/stream.h new file mode 100644 index 0000000..2885248 --- /dev/null +++ b/ESP32_PrusaConnectCam/stream.h @@ -0,0 +1,75 @@ +/** + @file stream.h + + @brief stream library + + Inspiration is from git repository https://gist.github.com/me-no-dev/d34fba51a8f059ac559bf62002e61aa3 + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#ifndef _STREAM_H_ +#define _STREAM_H_ + +#include "ESPAsyncWebSrv.h" + +#include "Arduino.h" +#include "mcu_cfg.h" +#include "var.h" +#include "log.h" +#include "camera.h" + +typedef struct { + camera_fb_t *fb; ///< pointer to frame buffer + size_t index; ///< index of frame +} camera_frame_t; ///< camera frame structure + +class AsyncBufferResponse : public AsyncAbstractResponse { +private: + uint8_t *_buf; ///< pointer to frame buffer + size_t _len; ///< length of frame buffer + size_t _index; ///< index of frame + +public: + AsyncBufferResponse(uint8_t *, size_t, const char *); + ~AsyncBufferResponse(); + bool _sourceValid() const; + virtual size_t _fillBuffer(uint8_t *, size_t) override; + size_t _content(uint8_t *, size_t, size_t); +}; + +class AsyncFrameResponse : public AsyncAbstractResponse { +private: + camera_fb_t *fb; ///< pointer to frame buffer + size_t _index; ///< index of frame + +public: + AsyncFrameResponse(camera_fb_t *, const char *); + ~AsyncFrameResponse(); + bool _sourceValid() const; + virtual size_t _fillBuffer(uint8_t *, size_t) override; + size_t _content(uint8_t *, size_t, size_t); +}; + +class AsyncJpegStreamResponse : public AsyncAbstractResponse { +private: + camera_frame_t _frame; ///< camera frame + size_t _index; ///< index of frame + uint64_t lastAsyncRequest; ///< last async request + Camera *camera; ///< pointer to camera + Logs *log; ///< pointer to logs + camera_fb_t _dframe; ///< camera frame + +public: + AsyncJpegStreamResponse(Camera *, Logs *); + ~AsyncJpegStreamResponse(); + bool _sourceValid() const; + virtual size_t _fillBuffer(uint8_t *, size_t ) override; + size_t _content(uint8_t *, size_t , size_t ); +}; + +#endif +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/sys_led.cpp b/ESP32_PrusaConnectCam/sys_led.cpp new file mode 100644 index 0000000..998f987 --- /dev/null +++ b/ESP32_PrusaConnectCam/sys_led.cpp @@ -0,0 +1,103 @@ +/** + @file sys_led.cpp + + @brief Library for system LED control + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#include "sys_led.h" + +sys_led system_led(STATUS_LED_GPIO_NUM, STATUS_LED_ON_DURATION); + +/** + * @brief Construct a new sys led::sys led object + * + * @param uint8_t - pin number for system LED + * @param uint32_t - duration of LED on + */ +sys_led::sys_led(uint8_t i_pin, uint32_t i_on_duration) { + pin = i_pin; + time = 100; + ledOnDuration = i_on_duration; +} + +/** + * @brief Construct a new sys led::sys led object + * + * @param uint8_t - pin number for system LED + * @param uint32_t - duration of LED on + * @param Logs * - pointer to log class + */ +sys_led::sys_led(uint8_t i_pin, uint32_t i_on_duration, Logs *i_log) { + pin = i_pin; + time = 100; + log = i_log; + ledOnDuration = i_on_duration; +} + +/** + * @brief Init system LED + * + */ +void sys_led::init() { + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); +} + +/** + * @brief Toggle system LED + * + */ +void sys_led::toggle() { + digitalWrite(pin, !digitalRead(pin)); +} + +/** + * @brief Set system LED + * + * @param bool - state of LED + */ +void sys_led::set(bool state) { + digitalWrite(pin, state); +} + +/** + * @brief Get system LED + * + * @return bool - state of LED + */ +bool sys_led::get() { + return digitalRead(pin); +} + +/** + * @brief Set timer for system LED + * + * @param uint32_t - time in ms + */ +void sys_led::setTimer(uint32_t i_time) { + time = i_time; +} + +/** + * @brief Get timer for next start task for system LED + * + * @return uint32_t - time in ms + */ +uint32_t sys_led::getTimer() { + uint32_t tmp = 0; + + if (digitalRead(pin) == LOW) { + tmp = ledOnDuration; + } else { + tmp = time; + } + + return tmp; +} + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/sys_led.h b/ESP32_PrusaConnectCam/sys_led.h new file mode 100644 index 0000000..02735e9 --- /dev/null +++ b/ESP32_PrusaConnectCam/sys_led.h @@ -0,0 +1,43 @@ +/** + @file sys_led.h + + @brief Library for system LED control + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#ifndef _SYS_LED_H_ +#define _SYS_LED_H_ + +#include "log.h" +#include "mcu_cfg.h" +#include "arduino.h" + +class sys_led { +private: + uint8_t pin; ///< pin number for system LED + uint32_t time; ///< speed blinking time system LED + uint32_t ledOnDuration; ///< duration of LED on + Logs *log; ///< pointer to log class + +public: + sys_led(uint8_t, uint32_t); + sys_led(uint8_t, uint32_t, Logs *); + ~sys_led(){}; + + void init(); + void toggle(); + void set(bool); + bool get(); + void setTimer(uint32_t); + uint32_t getTimer(); +}; + +extern sys_led system_led; + +#endif + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/system.cpp b/ESP32_PrusaConnectCam/system.cpp new file mode 100644 index 0000000..ccaae5b --- /dev/null +++ b/ESP32_PrusaConnectCam/system.cpp @@ -0,0 +1,644 @@ +/** + @file system.cpp + + @brief system library + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#include "system.h" + +/** + @brief Function for init system library + @param none + @return none +*/ +void System_Init() { + SystemLog.AddEvent(LogLevel_Info, "Init system lib"); + /* show last reset status */ + String reason_simple = System_printMcuResetReasonSimple(); + SystemLog.AddEvent(LogLevel_Warning, "CPU reset reason: " + reason_simple); + + String reason_core0 = System_PrintMcuResetReason(rtc_get_reset_reason(0)); + String reason_core1 = System_PrintMcuResetReason(rtc_get_reset_reason(1)); + SystemLog.AddEvent(LogLevel_Warning, "CPU0 reset reason: " + reason_core0); + SystemLog.AddEvent(LogLevel_Warning, "CPU1 reset reason: " + reason_core1); + + SystemLog.AddEvent(LogLevel_Info, "MCU Temperature: " + String(temperatureRead()) + " *C"); + SystemLog.AddEvent(LogLevel_Info, "Internal Total heap: " + String(ESP.getHeapSize()) + " ,internal Free Heap: " + String(ESP.getFreeHeap())); + SystemLog.AddEvent(LogLevel_Info, "SPIRam Total heap: " + String(ESP.getPsramSize()) + " ,SPIRam Free Heap: " + String(ESP.getFreePsram())); + SystemLog.AddEvent(LogLevel_Info, "ChipRevision: " + String(ESP.getChipRevision()) + " ,Cpu Freq: " + String(ESP.getCpuFreqMHz()) + " ,SDK Version: " + String(ESP.getSdkVersion())); + SystemLog.AddEvent(LogLevel_Info, "Flash Size: " + String(ESP.getFlashChipSize()) + " ,Flash Speed " + String(ESP.getFlashChipSpeed())); + System_CheckIfPsramIsUsed(); +} + +/** + @brief Function for load configuration from EEPROM + @param none + @return none +*/ +void System_LoadCfg() { + +} + +/** + @brief Function for check if PSRAM is used + @param none + @return none +*/ +void System_CheckIfPsramIsUsed() { + if (psramFound()) { + SystemLog.AddEvent(LogLevel_Info, "PSRAM is used."); + void *ptr = malloc(100); + + if (ptr != NULL) { + if (esp_ptr_external_ram(ptr)) { + SystemLog.AddEvent(LogLevel_Info, "malloc/new is using SPIRAM"); + } else { + SystemLog.AddEvent(LogLevel_Info, "malloc/new is not using SPIRAM"); + } + free(ptr); + } else { + SystemLog.AddEvent(LogLevel_Info, "Failed to allocate memory"); + } + } else { + SystemLog.AddEvent(LogLevel_Info, "PSRAM is not used."); + } +} + +/** + @brief Function for init update functions from file + @param none + @return none +*/ +void System_UpdateInit() { + Update.onProgress([](int progress, size_t total) { + /* update from file */ + SystemCamera.SetFlashStatus(true); + uint8_t updateProgress = (progress * 100) / FirmwareUpdate.FirmwareSize; + SystemLog.AddEvent(LogLevel_Info, "Updating: " + String(FirmwareUpdate.FirmwareSize) + "/" + String(progress) + " -> " + String(updateProgress) + "%"); + FirmwareUpdate.PercentProcess = updateProgress; + FirmwareUpdate.TransferedBytes = progress; + delay(10); + SystemCamera.SetFlashStatus(false); + }); +} + +/** + @brief Main function for system lib + @param none + @return none +*/ +void System_Main() { + /* check new FW version */ + if (false == FirmwareUpdate.CheckNewVersionAfterBoot) { + System_CheckNewVersion(); + } + + /* task for download and flash FW from server */ + System_OtaCloudUpdate(); +} + +/** + @brief Function for check FW version on the WEB server + + https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28 + + @param none + @return none +*/ +void System_CheckNewVersion() { + if (WL_CONNECTED == WiFi.status()) { + SystemLog.AddEvent(LogLevel_Info, "Check new FW version from OTA"); + FirmwareUpdate.CheckNewVersionAfterBoot = true; + WiFiClientSecure client; + client.setCACert(root_CAs_ota); + FirmwareUpdate.CheckNewVersionFwStatus = "N/A"; + FirmwareUpdate.NewVersionFw = "Unknown"; + FirmwareUpdate.OtaUpdateFwUrl = ""; + FirmwareUpdate.OtaUpdateFwAvailable = false; + + /* connect to server and get json */ + if (!client.connect("api.github.com", 443)) { + FirmwareUpdate.CheckNewVersionFwStatus = "Failed connect to OTA server!"; + SystemLog.AddEvent(LogLevel_Info, FirmwareUpdate.CheckNewVersionFwStatus); + + } else { + SystemLog.AddEvent(LogLevel_Verbose, "Connected to server!"); + client.println("GET https://" + String(OTA_UPDATE_API_SERVER) + String(OTA_UPDATE_API_URL) + " HTTP/1.0"); + client.println("Host: " + String(OTA_UPDATE_API_SERVER)); + client.println("User-Agent: " + String(DEVICE_HOSTNAME)); + client.println("Connection: close"); + client.println(); + + while (client.connected()) { + String line = client.readStringUntil('\n'); + if (line == "\r") { + SystemLog.AddEvent(LogLevel_Verbose, "headers received: " + line); + break; + } + } + + /* wait for data, and read it */ + delay(350); + String Data = ""; + while (client.connected()) { + while (client.available()) { + Data += (char)client.read(); + } + } + + SystemLog.AddEvent(LogLevel_Verbose, Data); + client.stop(); + + /* json analyzed */ + JsonDocument jsonDoc; + DeserializationError error = deserializeJson(jsonDoc, Data); + + if (error) { + FirmwareUpdate.CheckNewVersionFwStatus = "Failed to parse JSON from OTA server!"; + SystemLog.AddEvent(LogLevel_Warning, FirmwareUpdate.CheckNewVersionFwStatus); + SystemLog.AddEvent(LogLevel_Warning, Data); + + } else { + const char *firmwareVersion = jsonDoc["tag_name"]; + if (firmwareVersion) { + FirmwareUpdate.CheckNewVersionFwStatus = "Download successful"; + FirmwareUpdate.NewVersionFw = firmwareVersion; + SystemLog.AddEvent(LogLevel_Info, "Available OTA firmware: " + FirmwareUpdate.NewVersionFw); + + /* get assets */ + JsonArray assets = jsonDoc["assets"]; + int assetsCount = assets.size(); + SystemLog.AddEvent(LogLevel_Info, "Assets count: " + String(assetsCount)); + for(int i = 0; i < assetsCount; i++) { + JsonObject asset = assets[i]; + const char* name = asset["name"]; + SystemLog.AddEvent(LogLevel_Info, "Assets[" + String(i) + "]: " + String(name)); + + /* get FW file and URL */ + if (strcmp(name, OTA_UPDATE_FW_FILE) == 0) { + /* get download URL */ + const char* download_url = asset["browser_download_url"]; + FirmwareUpdate.OtaUpdateFwUrl = download_url; + SystemLog.AddEvent(LogLevel_Info, "Found FW file: " + String(name) + " URL: " + FirmwareUpdate.OtaUpdateFwUrl); + FirmwareUpdate.OtaUpdateFwAvailable = true; + } + } + } else { + FirmwareUpdate.CheckNewVersionFwStatus = "JSON key 'tag_name' from OTA server not found!"; + SystemLog.AddEvent(LogLevel_Warning, FirmwareUpdate.CheckNewVersionFwStatus); + } + } + } + } +} + +/** + @brief Function for check start OTA update from WEB server + @param none + @return none +*/ +void System_OtaCloudUpdate() { + if (true == FirmwareUpdate.StartOtaUpdate) { + FirmwareUpdate.Processing = true; + FirmwareUpdate.StartOtaUpdate = false; + FirmwareUpdate.UpdatingStatus = "Sync NTP time..."; + SystemWifiMngt.SyncNtpTime(); + FirmwareUpdate.UpdatingStatus = "Start updating"; + System_OtaUpdateStart(); + } +} + +/** + @brief Function for downloading FW and flash new FW to the memmory + + https://github.com/espressif/arduino-esp32/blob/master/libraries/HTTPUpdate/src/HTTPUpdate.h + + @param none + @return bool - true if update is done +*/ +bool System_OtaUpdateStart() { + bool b_ret = false; + WiFiClientSecure client; + + /* check if new FW version is available */ + if (FirmwareUpdate.OtaUpdateFwAvailable == false) { + FirmwareUpdate.UpdatingStatus = SYSTEM_MSG_UPDATE_NO_FW; + SystemLog.AddEvent(LogLevel_Info, FirmwareUpdate.UpdatingStatus); + FirmwareUpdate.Processing = false; + + return b_ret; + } + /* reset wdg */ + esp_task_wdt_reset(); + + /* set certificat for secure connection, set timeout and follow redirect */ + client.setCACert(root_CAs_ota); + // client.setInsecure(); + client.setTimeout(12000 / SECOND_TO_MILISECOND); /* timeout argument is defined in seconds for setTimeout */ + httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); /* HTTPC_FORCE_FOLLOW_REDIRECTS */ + + /* set callback function */ + httpUpdate.onStart(System_OtaUpdateStartCB); + httpUpdate.onEnd(System_OtaUpdateEndCB); + httpUpdate.onError(System_OtaUpdateErrorCB); + httpUpdate.onProgress(System_OtaUpdateProgressCB); + + /* mcu configuration */ + httpUpdate.rebootOnUpdate(false); + FirmwareUpdate.UpdatingStatus = SYSTEM_MSG_UPDATE_PROCESS; + httpUpdate.setLedPin(4, HIGH); + + SystemLog.AddEvent(LogLevel_Info, "Start OTA update URL: " + FirmwareUpdate.OtaUpdateFwUrl + ";"); + + /* start update */ + t_httpUpdate_return ret = httpUpdate.update(client, FirmwareUpdate.OtaUpdateFwUrl.c_str()); + + /* check result */ + switch (ret) { + case HTTP_UPDATE_FAILED: + b_ret = true; + FirmwareUpdate.UpdatingStatus = String(SYSTEM_MSG_UPDATE_FAIL) + "[" + String(httpUpdate.getLastError()) + "]: " + httpUpdate.getLastErrorString(); + break; + + case HTTP_UPDATE_NO_UPDATES: + FirmwareUpdate.UpdatingStatus = "No updates"; + break; + + case HTTP_UPDATE_OK: + FirmwareUpdate.UpdatingStatus = SYSTEM_MSG_UPDATE_DONE; + break; + } + FirmwareUpdate.Processing = false; + SystemLog.AddEvent(LogLevel_Info, "OTA update DONE. " + FirmwareUpdate.UpdatingStatus); + + return b_ret; +} + +/** + @brief CB function OTA update process + @param int - current bytes + @param int - total bytes + @return none +*/ +void System_OtaUpdateProgressCB(int cur, int total) { + /* OTA update from server */ + FirmwareUpdate.FirmwareSize = total; + uint8_t updateProgress = (cur * 100) / FirmwareUpdate.FirmwareSize; + FirmwareUpdate.PercentProcess = updateProgress; + FirmwareUpdate.TransferedBytes = cur; + SystemLog.AddEvent(LogLevel_Info, "Downloaded: " + String(cur) + "/" + String(FirmwareUpdate.FirmwareSize) + " -> " + String(FirmwareUpdate.PercentProcess) + "%"); +} + +/** + @brief CB function start OTA update + @param none + @return none +*/ +void System_OtaUpdateStartCB() { + SystemLog.AddEvent(LogLevel_Info, "Start OTA update"); +} + +/** + @brief CB function start OTA update + @param none + @return none +*/ +void System_OtaUpdateEndCB() { + SystemLog.AddEvent(LogLevel_Info, "OTA update done"); +} + +/** + @brief CB function error OTA update + @param int - error code + @return none +*/ +void System_OtaUpdateErrorCB(int error) { + SystemLog.AddEvent(LogLevel_Info, "OTA update error: " + String(error)); +} + +/** + @brief Function for print reset reason + @param int - reset reason + @return String - reset reason +*/ +String System_PrintMcuResetReason(int reason) { + String ret = ""; + switch (reason) { + case 1: /**<1, Vbat power on reset*/ + ret = "POWERON_RESET"; + break; + case 3: /**<3, Software reset digital core*/ + ret = "SW_RESET"; + break; + case 4: /**<4, Legacy watch dog reset digital core*/ + ret = "OWDT_RESET"; + break; + case 5: /**<5, Deep Sleep reset digital core*/ + ret = "DEEPSLEEP_RESET"; + break; + case 6: /**<6, Reset by SLC module, reset digital core*/ + ret = "SDIO_RESET"; + break; + case 7: /**<7, Timer Group0 Watch dog reset digital core*/ + ret = "TG0WDT_SYS_RESET"; + break; + case 8: /**<8, Timer Group1 Watch dog reset digital core*/ + ret = "TG1WDT_SYS_RESET"; + break; + case 9: /**<9, RTC Watch dog Reset digital core*/ + ret = "RTCWDT_SYS_RESET"; + break; + case 10: /**<10, Instrusion tested to reset CPU*/ + ret = "INTRUSION_RESET"; + break; + case 11: /**<11, Time Group reset CPU*/ + ret = "TGWDT_CPU_RESET"; + break; + case 12: /**<12, Software reset CPU*/ + ret = "SW_CPU_RESET"; + break; + case 13: /**<13, RTC Watch dog Reset CPU*/ + ret = "RTCWDT_CPU_RESET"; + break; + case 14: /**<14, for APP CPU, reseted by PRO CPU*/ + ret = "EXT_CPU_RESET"; + break; + case 15: /**<15, Reset when the vdd voltage is not stable*/ + ret = "RTCWDT_BROWN_OUT_RESET"; + break; + case 16: /**<16, RTC Watch dog reset digital core and rtc module*/ + ret = "RTCWDT_RTC_RESET"; + break; + default: + ret = "NO_MEAN"; + } + + return ret; +} + +/** + @brief Function for print reset reason + @param none + @return String - reset reason +*/ +String System_printMcuResetReasonSimple() { + String ret = ""; + esp_reset_reason_t reason = esp_reset_reason(); + + switch (reason) { + case ESP_RST_UNKNOWN: + ret = "Reset reason can not be determined"; + break; + case ESP_RST_POWERON: + ret = "Reset due to power-on event"; + break; + case ESP_RST_EXT: + ret = "Reset by external pin (not applicable for ESP32)"; + break; + case ESP_RST_SW: + ret = "Software reset via esp_restart"; + break; + case ESP_RST_PANIC: + ret = "Software reset due to exception/panic"; + break; + case ESP_RST_INT_WDT: + ret = "Reset (software or hardware) due to interrupt watchdog"; + break; + case ESP_RST_TASK_WDT: + ret = "Reset due to task watchdog"; + break; + case ESP_RST_WDT: + ret = "Reset due to other watchdogs"; + break; + case ESP_RST_DEEPSLEEP: + ret = "Reset after exiting deep sleep mode"; + break; + case ESP_RST_BROWNOUT: + ret = "Brownout reset (software or hardware)"; + break; + case ESP_RST_SDIO: + ret = "Reset over SDIO"; + break; + default: + ret = "N/A"; + break; + } + + return ret; +} + +/** + @brief Function for WiFi management system task + @param void *pvParameters + @return none +*/ +void System_TaskWifiManagement(void *pvParameters) { + SystemLog.AddEvent(LogLevel_Info, "Task Wifi Management. core: " + String(xPortGetCoreID())); + TickType_t xLastWakeTime = xTaskGetTickCount(); + + while (1) { + /* wifi management. Enable/disable AP_STA mode and STA mode*/ + SystemWifiMngt.WifiManagement(); + + /* wifi reconnect after signal lost */ + SystemWifiMngt.WiFiReconnect(); + + SystemLog.AddEvent(LogLevel_Info, "Free RAM: " + String(ESP.getFreeHeap()) + " bytes"); + SystemLog.AddEvent(LogLevel_Info, "Free SPIRAM: " + String(ESP.getFreePsram()) + " bytes"); + SystemLog.AddEvent(LogLevel_Info, "Temperature: " + String(temperatureRead()) + " *C"); + SystemLog.AddEvent(LogLevel_Verbose, "WiFiManagement task. Stack free size: " + String(uxTaskGetStackHighWaterMark(NULL)) + " bytes"); + + /* reset wdg */ + esp_task_wdt_reset(); + + /* next start task */ + vTaskDelayUntil(&xLastWakeTime, TASK_WIFI / portTICK_PERIOD_MS); + } +} + +/** + * @brief Function for main system task + * + * @param void *pvParameters + * @return none + */ +void System_TaskMain(void *pvParameters) { + SystemLog.AddEvent(LogLevel_Info, "System task. core: " + String(xPortGetCoreID())); + TickType_t xLastWakeTime = xTaskGetTickCount(); + + while (1) { + /* for ota update */ + esp_task_wdt_reset(); + System_Main(); + SystemLog.AddEvent(LogLevel_Verbose, "System task. Stack free size: " + String(uxTaskGetStackHighWaterMark(NULL)) + " bytes"); + + /* reset wdg */ + esp_task_wdt_reset(); + + /* next start task */ + vTaskDelayUntil(&xLastWakeTime, TASK_SYSTEM / portTICK_PERIOD_MS); + } +} + +/** + * @brief Function for capture and send photo task + * + * @param void *pvParameters + * @return none + */ +void System_TaskCaptureAndSendPhoto(void *pvParameters) { + SystemLog.AddEvent(LogLevel_Info, "Task photo processing. core: " + String(xPortGetCoreID())); + TickType_t xLastWakeTime = xTaskGetTickCount(); + uint16_t SendingIntervalCounter = 0; ///< counter for sending interval + + while (1) { + if (Connect.GetRefreshInterval() <= SendingIntervalCounter) { + SendingIntervalCounter = 0; + /* send network information to backend */ + if ((WL_CONNECTED == WiFi.status()) && (false == FirmwareUpdate.Processing)) { + esp_task_wdt_reset(); + Connect.SendInfoToBackend(); + } + + /* send photo to backend*/ + if ((WL_CONNECTED == WiFi.status()) && (false == FirmwareUpdate.Processing)) { + esp_task_wdt_reset(); + Connect.TakePictureAndSendToBackend(); + } + + } else { + /* update counter */ + SendingIntervalCounter++; + } + + SystemLog.AddEvent(LogLevel_Verbose, "Photo processing task. Stack free size: " + String(uxTaskGetStackHighWaterMark(NULL)) + " bytes"); + + /* reset wdg */ + esp_task_wdt_reset(); + + /* next start task */ + vTaskDelayUntil(&xLastWakeTime, TASK_PHOTO_SEND / portTICK_PERIOD_MS); + } +} + +/** + * @brief Function for micro SD card check task + * + * @param void *pvParameters + * @return none + */ +void System_TaskSdCardCheck(void *pvParameters) { + SystemLog.AddEvent(LogLevel_Info, "MicroSdCard check task. core: " + String(xPortGetCoreID())); + TickType_t xLastWakeTime = xTaskGetTickCount(); + + while (1) { + esp_task_wdt_reset(); + /* check micro SD card */ + if ((true == SystemLog.GetCardDetectAfterBoot()) && (false == SystemLog.GetCardDetectedStatus())) { + SystemLog.ReinitCard(); + SystemLog.AddEvent(LogLevel_Warning, "Reinit micro SD card done!"); + } + SystemLog.AddEvent(LogLevel_Verbose, "MicroSdCard task. Stack free size: " + String(uxTaskGetStackHighWaterMark(NULL)) + " bytes"); + + /* reset wdg */ + esp_task_wdt_reset(); + + /* next start task */ + vTaskDelayUntil(&xLastWakeTime, TASK_SDCARD / portTICK_PERIOD_MS); + } +} + +/** + * @brief Function for serial configuration task + * + * @param void *pvParameters + * @return none + */ +void System_TaskSerialCfg(void *pvParameters) { + SystemLog.AddEvent(LogLevel_Info, "SerialCg task. core: " + String(xPortGetCoreID())); + TickType_t xLastWakeTime = xTaskGetTickCount(); + + while (1) { + esp_task_wdt_reset(); + SystemSerialCfg.ProcessIncommingData(); + SystemLog.AddEvent(LogLevel_Verbose, "SerialCfg task. Stack free size: " + String(uxTaskGetStackHighWaterMark(NULL)) + " bytes"); + + /* reset wdg */ + esp_task_wdt_reset(); + + /* next start task */ + vTaskDelayUntil(&xLastWakeTime, TASK_SERIAL_CFG / portTICK_PERIOD_MS); + } +} + +/** + * @brief Function for stream telemetry task + * + * @param void *pvParameters + * @return none + */ +void System_TaskStreamTelemetry(void *pvParameters) { + SystemLog.AddEvent(LogLevel_Info, "StreamTelemetry task. core: " + String(xPortGetCoreID())); + TickType_t xLastWakeTime = xTaskGetTickCount(); + + while (1) { + esp_task_wdt_reset(); + SystemLog.AddEvent(LogLevel_Verbose, "StreamTelemetry task. Stack free size: " + String(uxTaskGetStackHighWaterMark(NULL)) + " bytes"); + if (SystemCamera.GetStreamStatus()) { + char buf[80] = { '\0' }; + sprintf(buf, "Stream, average data in %dsec. FPS: %.1f, Size: %uKB", (TASK_STREAM_TELEMETRY / SECOND_TO_MILISECOND), SystemCamera.StreamGetFrameAverageFps(), SystemCamera.StreamGetFrameAverageSize()); + SystemLog.AddEvent(LogLevel_Info, buf); + SystemCamera.StreamClearFrameData(); + } + + /* reset wdg */ + esp_task_wdt_reset(); + + /* next start task */ + vTaskDelayUntil(&xLastWakeTime, TASK_STREAM_TELEMETRY / portTICK_PERIOD_MS); + } +} + +/** + * @brief Function for system led task + * + * @param void *pvParameters + * @return none + */ +void System_TaskSysLed(void *pvParameters) { + SystemLog.AddEvent(LogLevel_Info, "SystemLed task. core: " + String(xPortGetCoreID())); + TickType_t xLastWakeTime = xTaskGetTickCount(); + + while (1) { + system_led.toggle(); + /* reset wdg */ + esp_task_wdt_reset(); + + /* next start task */ + vTaskDelayUntil(&xLastWakeTime, system_led.getTimer() / portTICK_PERIOD_MS); + } +} + +void System_TaskWiFiWatchdog(void *pvParameters) { + SystemLog.AddEvent(LogLevel_Info, "WiFiWatchdog task. core: " + String(xPortGetCoreID())); + TickType_t xLastWakeTime = xTaskGetTickCount(); + + while (1) { + esp_task_wdt_reset(); + SystemWifiMngt.WiFiWatchdog(); + SystemLog.AddEvent(LogLevel_Verbose, "WiFiWatchdog task. Stack free size: " + String(uxTaskGetStackHighWaterMark(NULL)) + " bytes"); + + /* reset wdg */ + esp_task_wdt_reset(); + + /* next start task */ + vTaskDelayUntil(&xLastWakeTime, TASK_WIFI_WATCHDOG / portTICK_PERIOD_MS); + } +} + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/system.h b/ESP32_PrusaConnectCam/system.h new file mode 100644 index 0000000..dc359c5 --- /dev/null +++ b/ESP32_PrusaConnectCam/system.h @@ -0,0 +1,68 @@ +/** + @file system.h + + @brief system library + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#ifndef _SYSTEM_H_ +#define _SYSTEM_H_ + +#include +#include +#include "Arduino.h" +#include +#include +#include +#include +#include +#include "esp32/rom/rtc.h" +#include + +#include "mcu_cfg.h" +#include "var.h" +#include "cfg.h" +#include "log.h" +#include "Certificate.h" +#include "wifi_mngt.h" +#include "connect.h" +#include "serial_cfg.h" +#include "sys_led.h" + +#define SYSTEM_MSG_UPDATE_DONE "FW update successfully done! Please reboot the MCU." +#define SYSTEM_MSG_UPDATE_FAIL "FW update failed! Please reboot MCU, and try again." +#define SYSTEM_MSG_UPDATE_PROCESS "FW update in progress" +#define SYSTEM_MSG_UPDATE_NO_FW "No new FW version available!" + +void System_Init(); +void System_LoadCfg(); +void System_CheckIfPsramIsUsed(); +void System_Main(); +void System_UpdateInit(); + +void System_CheckNewVersion(); +void System_OtaCloudUpdate(); +bool System_OtaUpdateStart(); +void System_OtaUpdateProgressCB(int, int); +void System_OtaUpdateErrorCB(int); +void System_OtaUpdateEndCB(); +void System_OtaUpdateStartCB(); + +String System_PrintMcuResetReason(int); +String System_printMcuResetReasonSimple(); + +void System_TaskWifiManagement(void *); +void System_TaskMain(void *); +void System_TaskCaptureAndSendPhoto(void *); +void System_TaskSdCardCheck(void *); +void System_TaskSerialCfg(void *); +void System_TaskStreamTelemetry(void *); +void System_TaskSysLed(void *); +void System_TaskWiFiWatchdog(void *); + +#endif +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/var.cpp b/ESP32_PrusaConnectCam/var.cpp new file mode 100644 index 0000000..55f2648 --- /dev/null +++ b/ESP32_PrusaConnectCam/var.cpp @@ -0,0 +1,26 @@ +/** + @file variable.cpp + + @brief Library with global variables + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#include "var.h" + +WebBasicAuth_struct WebBasicAuth = { false, "", "" }; +struct FirmwareUpdate_struct FirmwareUpdate = { "Ready", false, 0, 0, 0, false, false, "", "", "", false }; + +TaskHandle_t Task_CapturePhotoAndSend; +TaskHandle_t Task_WiFiManagement; +TaskHandle_t Task_SystemMain; +TaskHandle_t Task_SdCardCheck; +TaskHandle_t Task_SerialCfg; +TaskHandle_t Task_StreamTelemetry; +TaskHandle_t Task_SysLed; +TaskHandle_t Task_WiFiWatchdog; + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/var.h b/ESP32_PrusaConnectCam/var.h new file mode 100644 index 0000000..37ff40b --- /dev/null +++ b/ESP32_PrusaConnectCam/var.h @@ -0,0 +1,54 @@ +/** + @file variable.h + + @brief Library with global variables + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#ifndef _VARIABLE_H_ +#define _VARIABLE_H_ + +#include "Arduino.h" +#include "mcu_cfg.h" + +struct WebBasicAuth_struct { + bool EnableAuth; ///< user definition for enable/disable basic auth + String UserName; ///< login name for basic auth + String Password; ///< password for basic auth +}; + +struct FirmwareUpdate_struct { + String UpdatingStatus; ///< Updateing status + bool Processing; ///< status abour processing firmware update + uint8_t PercentProcess; ///< processed firmware update + int TransferedBytes; ///< transfered bytes + + int FirmwareSize; ///< uploaded firmware size + + bool StartOtaUpdate; ///< Start OTA update process + bool CheckNewVersionAfterBoot; ///< Check new version OTA update after MCU boot + String NewVersionFw; ///< New FW version + String CheckNewVersionFwStatus; ///< connection status from checking new OTA update version + String OtaUpdateFwUrl; ///< URL for OTA update + bool OtaUpdateFwAvailable; ///< flag for available new FW version +}; + +extern struct WebBasicAuth_struct WebBasicAuth; ///< structure with configuration for basic auth +extern struct FirmwareUpdate_struct FirmwareUpdate; ///< firmware update status and process + +extern TaskHandle_t Task_CapturePhotoAndSend; ///< task handle for capture photo and send +extern TaskHandle_t Task_WiFiManagement; ///< task handle for wifi management +extern TaskHandle_t Task_SystemMain; ///< task handle for system main +extern TaskHandle_t Task_SdCardCheck; ///< task handle for sd card check +extern TaskHandle_t Task_SerialCfg; ///< task handle for serial configuration +extern TaskHandle_t Task_StreamTelemetry; ///< task handle for stream telemetry +extern TaskHandle_t Task_SysLed; ///< task handle for system led +extern TaskHandle_t Task_WiFiWatchdog; ///< task handle for wifi watchdog + +#endif + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/wifi_mngt.cpp b/ESP32_PrusaConnectCam/wifi_mngt.cpp new file mode 100644 index 0000000..98398bf --- /dev/null +++ b/ESP32_PrusaConnectCam/wifi_mngt.cpp @@ -0,0 +1,919 @@ +/** + @file wifi_mngt.cpp + + @brief + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#include "wifi_mngt.h" + +WiFiMngt SystemWifiMngt(&SystemConfig, &SystemLog, &SystemCamera); + +/** + @brief Constructor for WiFiMngt class + @param Configuration* - pointer to Configuration class + @param Logs* - pointer to Logs class + @param Camera* - pointer to Camera class + @return none +*/ +WiFiMngt::WiFiMngt(Configuration *i_conf, Logs *i_log, Camera *i_cam) { + config = i_conf; + log = i_log; + cam = i_cam; + + Service_LocalIp.fromString(String(SERVICE_LOCAL_IP)); + Service_Gateway.fromString(String(SERVICE_LOCAL_GATEWAY)); + Service_Subnet.fromString(String(SERVICE_LOCAL_MASK)); + Service_Dns.fromString(String(SERVICE_LOCAL_DNS)); + + NtpFirstSync = false; + StartStaWdg = false; +} + +/** + @brief Loaf cfg from EEPROM + @param none + @return none +*/ +void WiFiMngt::LoadCfgFromEeprom() { + WifiSsid = config->LoadWifiSsid(); + WifiPassword = config->LoadWifiPassowrd(); + mDNS_record = config->LoadMdnsRecord(); +} + +/** + @brief function for init wifi module + @param none + @return none +*/ +void WiFiMngt::Init() { + /* check WI-FI mode */ + system_led.setTimer(STATUS_LED_WIFI_AP); + ServiceMode = true; + log->AddEvent(LogLevel_Info, "WiFi MAC: " + WiFi.macAddress()); + + /* Set Wi-Fi networks */ + SetWifiEvents(); + CreateApSsid(); + log->AddEvent(LogLevel_Warning, "Set WiFi AP mode"); + WiFi.mode(WIFI_AP_STA); + esp_wifi_set_ps(WIFI_PS_NONE); + WiFi.softAPConfig(Service_LocalIp, Service_Gateway, Service_Subnet); + WiFi.softAP(SericeApSsid.c_str(), SERVICE_WIFI_PASS, SERVICE_WIFI_CHANNEL); + WiFi.setHostname(DEVICE_HOSTNAME); + WiFiMode = "AP + Client"; + log->AddEvent(LogLevel_Info, "Service IP Address: http://" + WiFi.softAPIP().toString()); + FirstConnected = false; + //WiFi.setTxPower(WIFI_POWER_18_5dBm); + + if (config->CheckActifeWifiCfgFlag() == true) { + if (true == CheckAvailableWifiNetwork(WifiSsid)) { + WiFiStaConnect(); + log->AddEvent(LogLevel_Warning, "Connecting to WiFi: " + WifiSsid); + +#if (WIFI_CLIENT_WAIT_CON == true) + while (WiFi.status() != WL_CONNECTED) { + delay(1000); + log->AddEvent(LogLevel_Verbose, "."); + } + WifiCfg.FirstConnected = true; + + /* Print ESP32 Local IP Address */ + log->AddEvent(LogLevel_Info, "WiFi network IP Address: http://" + WiFi.localIP().toString()); +#endif + } else { + log->AddEvent(LogLevel_Warning, "Wifi unavailable. Skip connecting to WiFi: " + WifiSsid); + } + } + TaskAp_previousMillis = millis(); + + /* Init MDNS record */ + log->AddEvent(LogLevel_Info, "Starting mDNS record: http://" + mDNS_record + ".local"); + if (!MDNS.begin(mDNS_record)) { + log->AddEvent(LogLevel_Error, "Error starting mDNS"); + } else { + log->AddEvent(LogLevel_Info, "Starting mDNS OK"); + } + MDNS.addService("http", "tcp", 80); +} + +/** + @brief function for management wifi network + @param none + @return none +*/ +void WiFiMngt::WifiManagement() { + /* check disable service AP */ + unsigned long currentMillis = millis(); + if (currentMillis - TaskAp_previousMillis >= STA_AP_MODE_TIMEOUT) { + TaskAp_previousMillis = currentMillis; + if ((true == config->CheckActifeWifiCfgFlag()) && (true == ServiceMode) && (WL_CONNECTED == WiFi.status()) + && (false == FirmwareUpdate.Processing) && (false == cam->GetStreamStatus())) { + if (WiFi.softAPgetStationNum() == 0) { + log->AddEvent(LogLevel_Info, "Disable service AP mode"); + WiFi.mode(WIFI_STA); + esp_wifi_set_ps(WIFI_PS_NONE); + WiFiStaConnect(); + //WiFi.begin(WifiSsid, WifiPassword); + WiFiMode = "Client"; + +#if (WIFI_CLIENT_WAIT_CON == true) + while (WiFi.status() != WL_CONNECTED) { + delay(1000); + log->AddEvent(LogLevel_Verbose, "."); + } + log->AddEvent(LogLevel_Verbose, "Connected to WiFi"); +#endif + ServiceMode = false; + WiFi.softAPdisconnect(true); + } else { + log->AddEvent(LogLevel_Info, "Client [" + String(WiFi.softAPgetStationNum()) + "] still is connected to Wi-Fi AP!"); + } + } + } + + /* check first NTP time sync after boot */ + if ((true == config->CheckActifeWifiCfgFlag()) && (false == NtpFirstSync) && (WL_CONNECTED == WiFi.status())) { + SyncNtpTime(); + } +} + +/** + @brief CB function for reconnect to wifi network + @param none + @return none +*/ +void WiFiMngt::WiFiReconnect() { + if ((WiFi.status() != WL_CONNECTED) && (FirstConnected == true)) { + log->AddEvent(LogLevel_Warning, "Reconnecting to WiFi. STA"); + WiFi.disconnect(); + log->AddEvent(LogLevel_Warning, "Disconnect from WiFi"); + WiFi.reconnect(); + log->AddEvent(LogLevel_Warning, "Reconnecting to WiFi. STA"); + } else if (WiFi.status() == WL_CONNECTED) { + char cstr[150]; + sprintf(cstr, "Wifi connected. SSID: %s, BSSID: %s, RSSI: %d dBm, IP: %s, TX power: %s", WiFi.SSID().c_str(), WiFi.BSSIDstr().c_str(), WiFi.RSSI(), WiFi.localIP().toString().c_str(), TranslateTxPower(WiFi.getTxPower()).c_str()); //print 3 digits + log->AddEvent(LogLevel_Info, "WiFi status: " + String(cstr)); + } + + if (Connect.GetBackendAvailabilitStatus() == BackendUnavailable) { + log->AddEvent(LogLevel_Warning, "Reconnecting to WiFi. STA. Problem with connecting to backend!"); + WiFi.disconnect(); + WiFi.reconnect(); + Connect.SetBackendAvailabilitStatus(WaitForFirstConnection); + } +} + +/** + @brief Function for sets WiFi CB calls + @param none + @return none +*/ +void WiFiMngt::SetWifiEvents() { + WiFi.onEvent(WiFiMngt_WiFiEventScanDone, WiFiEvent_t::ARDUINO_EVENT_WIFI_SCAN_DONE); + WiFi.onEvent(WiFiMngt_WiFiEventStationStart, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_START); + WiFi.onEvent(WiFiMngt_WiFiEventStationStop, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_STOP); + WiFi.onEvent(WiFiMngt_WiFiEventStationConnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_CONNECTED); + WiFi.onEvent(WiFiMngt_WiFiEventStationDisconnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED); + WiFi.onEvent(WiFiMngt_WiFiEventGotIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP); + WiFi.onEvent(WiFiMngt_WiFiEventLostIP, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_LOST_IP); + WiFi.onEvent(WiFiMngt_WiFiEventApStart, WiFiEvent_t::ARDUINO_EVENT_WIFI_AP_START); + WiFi.onEvent(WiFiMngt_WiFiEventApStop, WiFiEvent_t::ARDUINO_EVENT_WIFI_AP_STOP); + WiFi.onEvent(WiFiMngt_WiFiEventApStaConnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_AP_STACONNECTED); + WiFi.onEvent(WiFiMngt_WiFiEventApStaDisconnected, WiFiEvent_t::ARDUINO_EVENT_WIFI_AP_STADISCONNECTED); + WiFi.onEvent(WiFiMngt_WiFiEventApStaIpAssigned, WiFiEvent_t::ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED); + WiFi.onEvent(WiFiMngt_WiFiEventApStaProbeReqRecved, WiFiEvent_t::ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED); +} + +/** + @brief Function for connect to STA + @param none + @return none + @note https://espressif-docs.readthedocs-hosted.com/projects/arduino-esp32/en/latest/api/wifi.html#sta-connection +*/ +void WiFiMngt::WiFiStaConnect() { + if (config->CheckActifeWifiCfgFlag() == true) { + system_led.setTimer(STATUS_LED_STA_CONNECTING); + if (false == WiFiStaMultipleNetwork) { + WiFi.begin(WifiSsid, WifiPassword); + log->AddEvent(LogLevel_Info, "Connecting to STA SSID"); + } else if (true == WiFiStaMultipleNetwork) { + WiFi.begin(WifiSsid, WifiPassword, 0, WiFiStaNetworkBssid); + log->AddEvent(LogLevel_Info, "Connecting to STA BSSID"); + } + WiFi.setAutoReconnect(true); + } +} + +/** + @brief Function for set time from NTP server + @param none + @return none +*/ +void WiFiMngt::SyncNtpTime() { + if (WL_CONNECTED == WiFi.status()) { + /* configure NTP server and timezone to UTC */ + configTime(0, 0, "pool.ntp.org", "time.nist.gov"); // UTC + log->AddEvent(LogLevel_Info, "Waiting for NTP time sync: "); + log->SetNtpTimeSynced(false); + + /* wait maximum 10s for time sync */ + unsigned long startMillis = millis(); + struct tm timeinfo; + while ((millis() - startMillis) < 10000) { + if (getLocalTime(&timeinfo)) { + log->SetNtpTimeSynced(true); + break; + } + yield(); + delay(500); + } + + /* report sync status */ + if (true == log->GetNtpTimeSynced()) { + log->AddEvent(LogLevel_Info, "Sync NTP time done. Set UTC timezone"); + NtpFirstSync = true; + } else { + log->AddEvent(LogLevel_Info, "Sync NTP time fail"); + } + } +} + +/** + @brief Function for scan wifi network + @param none + @return none +*/ +void WiFiMngt::ScanWiFiNetwork() { + ScanWifiNetwork(""); +} + +/** + @brief Function for scan wifi network and check available wifi network + @param String - searched wifi network + @return uint8_t - count of found wifi networks with same SSID +*/ +uint8_t WiFiMngt::ScanWifiNetwork(String ssid) { + log->AddEvent(LogLevel_Info, "Scan WI-FI networks"); + log->AddEvent(LogLevel_Info, "Check available WI-FI network: " + ssid); + uint8_t ret = 0; ///< total wifi network count + int bestSignal = -100; ///< wifi network with best signal (when is available multiple networks with same SSID) + uint8_t bssid[6] = { 0 }; + + /* scan WI-FI networks */ + int n = WiFi.scanNetworks(); + log->AddEvent(LogLevel_Verbose, "Scan done"); + JsonDocument doc_json; + JsonArray wifiArray = doc_json.to(); + WifiScanJson = ""; + + /* make json with each found WI-FI networks */ + if (n == 0) { + log->AddEvent(LogLevel_Info, "No networks found!"); + } else { + log->AddEvent(LogLevel_Info, String(n) + " networks found"); + log->AddEvent(LogLevel_Info, "Nr | SSID | RSSI | CH | BSSID | Encryption"); + + for (int i = 0; i < n; ++i) { + /* check available wifi network */ + if (WiFi.SSID(i) == ssid) { + ret++; + + if (WiFi.RSSI(i) > bestSignal) { + bestSignal = WiFi.RSSI(i); + uint8_t *tmp = WiFi.BSSID(i); + memcpy(bssid, tmp, 6); + } + } + + /* make json with available wifi networks */ + JsonObject wifi_arr = wifiArray.add(); + wifi_arr["ssid"] = WiFi.SSID(i); + wifi_arr["bssid"] = WiFi.BSSIDstr(i); + wifi_arr["rssi"] = String(WiFi.RSSI(i)); + wifi_arr["rssi_percentage"] = String(Rssi2Percent(WiFi.RSSI(i))); + wifi_arr["channel"] = String(WiFi.channel(i)); + wifi_arr["encryption"] = TranslateWiFiEncrypion(WiFi.encryptionType(i)); + + // Print SSID and RSSI for each network found + char formattedString[100] = { '\0' }; + sprintf(formattedString, "%2d | %-32.32s | %4d | %2d | %-17s | %s", i + 1, + WiFi.SSID(i).c_str(), WiFi.RSSI(i), WiFi.channel(i), WiFi.BSSIDstr(i).c_str(), TranslateWiFiEncrypion(WiFi.encryptionType(i)).c_str()); + log->AddEvent(LogLevel_Info, formattedString); + } + } + serializeJson(doc_json, WifiScanJson); + + // Delete the scan result to free memory for code below. + WiFi.scanDelete(); + log->AddEvent(LogLevel_Verbose, WifiScanJson); + + /* print status */ + if (1 <= ret) { + log->AddEvent(LogLevel_Info, "SSID: " + ssid + " found, " + String(ret) + "x"); + if (1 < ret) { + memcpy(WiFiStaNetworkBssid, bssid, 6); + WiFiStaMultipleNetwork = true; + char mac[18] = { 0 }; + sprintf(mac, "%02X:%02X:%02X:%02X:%02X:%02X", WiFiStaNetworkBssid[0], WiFiStaNetworkBssid[1], WiFiStaNetworkBssid[2], WiFiStaNetworkBssid[3], WiFiStaNetworkBssid[4], WiFiStaNetworkBssid[5]); + log->AddEvent(LogLevel_Info, "WiFi roaming found! Connecting to " + String(mac) + " -> " + String(bestSignal) + "dBm, " + String(WiFiStaMultipleNetwork)); + } + } else { + log->AddEvent(LogLevel_Info, "SSID: " + ssid + " not found"); + } + + return ret; +} + +/** + @brief Function for check available wifi network + @param String - ssid + @return bool - true if wifi network is available +*/ +bool WiFiMngt::CheckAvailableWifiNetwork(String i_data) { + uint8_t tmp = ScanWifiNetwork(i_data); + bool ret = false; + if (tmp >= 1) { + ret = true; + } + + return ret; +} + +/** + @brief Function for create AP SSID name + @param none + @return none +*/ +void WiFiMngt::CreateApSsid() { + String name = SERVICE_WIFI_SSID; +#if (true == SERVICE_WIFI_SSID_UID) + name += "_"; + /* copy just last 3 numbers from ID */ + for (size_t i = UniqueIDsize - 3; i < UniqueIDsize; i++) { + name += String(UniqueID[i]); + if (i < (UniqueIDsize - 1)) + name += "."; + } +#endif + + SericeApSsid = name; + log->AddEvent(LogLevel_Info, "Service AP SSID: " + SericeApSsid); +} + +/** + * @brief WiFi watchdog for check connection to STA + * + */ +void WiFiMngt::WiFiWatchdog() { + + /* when is enabled wifi configuration, and is not connected to wifi network, and is available at least one wifi network */ + if ((true == config->CheckActifeWifiCfgFlag()) && (WL_CONNECTED != WiFi.status()) && (true == GetFirstConnection())) { + log->AddEvent(LogLevel_Warning, "WiFi WDG. STA connection lost."); + unsigned long currentMillis = millis(); + + if (false == StartStaWdg) { + if (ScanWifiNetwork(WifiSsid) >= 1) { + StartStaWdg = true; + TaskWdg_previousMillis = currentMillis; + log->AddEvent(LogLevel_Warning, "WiFi STA connection lost. Start watchdog timer!"); + } + } + + if ((true == StartStaWdg) && (currentMillis - TaskWdg_previousMillis >= WIFI_STA_WDG_TIMEOUT)) { + log->AddEvent(LogLevel_Warning, "WiFi STA connection lost. WDG timer expired. Restart MCU!"); + /* restart MCU, or disconnect and connect to WiFi again ? From my point of view, and testing, restart MCU is better */ + ESP.restart(); + } + } else if (true == StartStaWdg) { + log->AddEvent(LogLevel_Info, "WiFi STA connection OK. Stop watchdog timer!"); + StartStaWdg = false; + TaskWdg_previousMillis = millis(); + } +} + +/** + @brief Convert WiFi signal RSSI to percent + @param int - rssi + @return int - percent +*/ +int WiFiMngt::Rssi2Percent(int rssi) { + int signalStrength = 0; + if (0 == rssi) { + signalStrength = 0; + } else { + if (rssi <= -100) + signalStrength = 0; + else if (rssi >= -50) + signalStrength = 100; + else + signalStrength = 2 * (rssi + 100); + } + return signalStrength; +} + +/** + @brief function for translate from wifi_power_t to string TX power + @param wifi_power_t - value + @return String - value +*/ +String WiFiMngt::TranslateTxPower(wifi_power_t data) { + String ret = ""; + switch (data) { + case WIFI_POWER_MINUS_1dBm: + ret = "-1dBm"; + break; + case WIFI_POWER_2dBm: + ret = "2dBm"; + break; + case WIFI_POWER_5dBm: + ret = "5dBm"; + break; + case WIFI_POWER_7dBm: + ret = "7dBm"; + break; + case WIFI_POWER_8_5dBm: + ret = "8.5dBm"; + break; + case WIFI_POWER_11dBm: + ret = "11dBm"; + break; + case WIFI_POWER_13dBm: + ret = "13dBm"; + break; + case WIFI_POWER_15dBm: + ret = "15dBm"; + break; + case WIFI_POWER_17dBm: + ret = "17dBm"; + break; + case WIFI_POWER_18_5dBm: + ret = "18.5dBm"; + break; + case WIFI_POWER_19dBm: + ret = "19dBm"; + break; + case WIFI_POWER_19_5dBm: + ret = "19.5dBm"; + break; + } + + return ret; +} + +/** + @brief function for translate from wl_status_t to string + @param wl_status_t - value + @return String - value + @note https://github.com/esp8266/Arduino/blob/0c897c37a6eab3eab34147219617945a32a9b155/libraries/ESP8266WiFi/src/include/wl_definitions.h#L50 +*/ +String WiFiMngt::TranslateWiFiStatus(wl_status_t i_wifi_status) { + String ret = ""; + switch (i_wifi_status) { + case WL_IDLE_STATUS: + ret = "Idle"; + break; + case WL_NO_SSID_AVAIL: + ret = "No SSID available"; + break; + case WL_SCAN_COMPLETED: + ret = "Scan completed"; + break; + case WL_CONNECTED: + ret = "Connected"; + break; + case WL_CONNECT_FAILED: + ret = "Connect failed"; + break; + case WL_CONNECTION_LOST: + ret = "Connection lost"; + break; + case WL_DISCONNECTED: + ret = "Disconnected"; + break; + case WL_NO_SHIELD: + ret = "No WiFi shield"; + break; + default: + ret = "Unkcnown status"; + ret += String(i_wifi_status); + } + return ret; +} + +/** + @brief function for translate from wifi_auth_mode_t to string + @param wifi_auth_mode_t - value + @return String - value +*/ +String WiFiMngt::TranslateWiFiEncrypion(wifi_auth_mode_t i_data) { + String ret = ""; + switch (i_data) { + case WIFI_AUTH_OPEN: + ret = "open"; + break; + case WIFI_AUTH_WEP: + ret = "WEP"; + break; + case WIFI_AUTH_WPA_PSK: + ret = "WPA"; + break; + case WIFI_AUTH_WPA2_PSK: + ret = "WPA2"; + break; + case WIFI_AUTH_WPA_WPA2_PSK: + ret = "WPA+WPA2"; + break; + case WIFI_AUTH_WPA2_ENTERPRISE: + ret = "WPA2-EAP"; + break; + case WIFI_AUTH_WPA3_PSK: + ret = "WPA3"; + break; + case WIFI_AUTH_WPA2_WPA3_PSK: + ret = "WPA2+WPA3"; + break; + case WIFI_AUTH_WAPI_PSK: + ret = "WAPI"; + break; + default: + ret = "unknown"; + } + + return ret; +} + +/** + @brief function for get service AP SSID name + @param none + @return String - value +*/ +String WiFiMngt::GetServiceApSsid() { + return SericeApSsid; +} + +/** + @brief function for get STA SSID name + @param none + @return String - value +*/ +String WiFiMngt::GetStaSsid() { + return WifiSsid; +} + +/** + @brief function for get STA BSSID + @param none + @return String - value +*/ +String WiFiMngt::GetStaBssid() { + return WiFi.BSSIDstr(); +} + +/** + @brief function for get STA password + @param none + @return String - value +*/ +String WiFiMngt::GetStaPassword() { + return WifiPassword; +} + +/** + @brief function for get STA status + @param none + @return String - value +*/ +String WiFiMngt::GetStaStatus() { + return TranslateWiFiStatus(WiFi.status()); +} + +/** + @brief function for get STA IP + @param none + @return String - value +*/ +String WiFiMngt::GetStaIp() { + return WiFi.localIP().toString(); +} + +/** + @brief function for get available wifi networks + @param none + @return String - value +*/ +String WiFiMngt::GetAvailableWifiNetworks() { + return WifiScanJson; +} + +/** + @brief function for get Wi-Fi mode + @param none + @return String - value +*/ +String WiFiMngt::GetWiFiMode() { + return WiFiMode; +} + +/** + * @brief function for get Wi-Fi MAC address + * + * @return String - value + */ +String WiFiMngt::GetWifiMac() { + return String(WiFi.macAddress()); +} + +/** + @brief function for get mDNS record + @param none + @return String - value +*/ +String WiFiMngt::GetMdns() { + return mDNS_record; +} + +/** + @brief function for get first time NTP sync status + @param none + @return bool - value +*/ +bool WiFiMngt::GetNtpFirstTimeSync() { + return NtpFirstSync; +} + +/** + * @brief get first connection status + * + * @return bool - status + */ +bool WiFiMngt::GetFirstConnection() { + return FirstConnected; +} + +/** + @brief function for set STA credentials + @param String - ssid + @param String - password + @return none +*/ +void WiFiMngt::SetStaCredentials(String i_ssid, String i_pass) { + WifiSsid = i_ssid; + config->SaveWifiSsid(WifiSsid); + + WifiPassword = i_pass; + config->SaveWifiPassword(WifiPassword); + + config->SaveWifiCfgFlag(CFG_WIFI_SETTINGS_SAVED); +} + +/** + @brief function for set STA SSID + @param String - STA SSID name + @return none +*/ +void WiFiMngt::SetStaSsid(String i_ssid) { + WifiSsid = i_ssid; + config->SaveWifiSsid(WifiSsid); +} + +/** + @brief function for set STA password + @param String - STA password + @return none +*/ +void WiFiMngt::SetStaPassword(String i_pass) { + WifiPassword = i_pass; + config->SaveWifiPassword(WifiPassword); +} + +/** + @brief function for connect to STA + @param none + @return none +*/ +void WiFiMngt::ConnectToSta() { + config->SaveWifiCfgFlag(CFG_WIFI_SETTINGS_SAVED); +} + +/** + @brief function for get active wifi configuration flag + @param none + @return bool - value +*/ +bool WiFiMngt::GetkActifeWifiCfgFlag() { + return config->CheckActifeWifiCfgFlag(); +} + +/** + @brief function for set mDNS record + @param String - mDNS record + @return none +*/ +void WiFiMngt::SetMdns(String i_data) { + mDNS_record = i_data; + config->SaveMdnsRecord(mDNS_record); +} + +/** + @brief function for set first connection flag + @param bool - data + @return none +*/ +void WiFiMngt::SetFirstConnection(bool i_data) { + FirstConnected = i_data; +} + +/* ----------------------- Static function ----------------------- */ + +/** + @brief CB function event about scan wifi networks done + @param WiFiEvent_t + @param WiFiEventInfo_t + @return none +*/ +void WiFiMngt_WiFiEventScanDone(WiFiEvent_t event, WiFiEventInfo_t info) { + SystemLog.AddEvent(LogLevel_Info, "WiFi networks scan done"); +} + +/** + @brief CB function for event start station + @param WiFiEvent_t + @param WiFiEventInfo_t + @return none +*/ +void WiFiMngt_WiFiEventStationStart(WiFiEvent_t event, WiFiEventInfo_t info) { + SystemLog.AddEvent(LogLevel_Info, "WiFi STA start"); +} + +/** + @brief CB function for event stop station + @param WiFiEvent_t + @param WiFiEventInfo_t + @return none +*/ +void WiFiMngt_WiFiEventStationStop(WiFiEvent_t event, WiFiEventInfo_t info) { + SystemLog.AddEvent(LogLevel_Info, "WiFi STA stop"); +} + +/** + @brief CB function for event got information about check station connected + @param WiFiEvent_t + @param WiFiEventInfo_t + @return none +*/ +void WiFiMngt_WiFiEventStationConnected(WiFiEvent_t event, WiFiEventInfo_t info) { + SystemLog.AddEvent(LogLevel_Info, "WiFi connected to STA"); +} + +/** + @brief CB function for got IP after connecting to wifi network + @param WiFiEvent_t + @param WiFiEventInfo_t + @return none +*/ +void WiFiMngt_WiFiEventGotIP(WiFiEvent_t event, WiFiEventInfo_t info) { + system_led.setTimer(STATUS_LED_STA_CONNECTED); + SystemLog.AddEvent(LogLevel_Info, "WiFi Got IP address: " + WiFi.localIP().toString()); + SystemLog.AddEvent(LogLevel_Info, "WiFi Got mask: " + WiFi.subnetMask().toString()); + SystemLog.AddEvent(LogLevel_Info, "WiFi Got gateway: " + WiFi.gatewayIP().toString()); + SystemLog.AddEvent(LogLevel_Info, "WiFi Got DNS 1: " + WiFi.dnsIP(0).toString()); + SystemLog.AddEvent(LogLevel_Info, "WiFi Got DNS 2: " + WiFi.dnsIP(1).toString()); + SystemWifiMngt.SetFirstConnection(true); + + /* update device information */ + Connect.UpdateDeviceInformation(); + + /* check first NTP time sync after boot */ + if (false == SystemWifiMngt.GetNtpFirstTimeSync()) { + SystemWifiMngt.SyncNtpTime(); + } +} + +/** + @brief CB function for lost IP after connecting to wifi network + @param WiFiEvent_t + @param WiFiEventInfo_t + @return none +*/ +void WiFiMngt_WiFiEventLostIP(WiFiEvent_t event, WiFiEventInfo_t info) { + SystemLog.AddEvent(LogLevel_Info, "WiFi lost IP address"); +} + +/** + @brief CB function for got information about disconnecting from wifi network + + REASON_UNSPECIFIED = 1, + REASON_AUTH_EXPIRE = 2, + REASON_AUTH_LEAVE = 3, + REASON_ASSOC_EXPIRE = 4, + REASON_ASSOC_TOOMANY = 5, + REASON_NOT_AUTHED = 6, + REASON_NOT_ASSOCED = 7, + REASON_ASSOC_LEAVE = 8, + REASON_ASSOC_NOT_AUTHED = 9, + REASON_DISASSOC_PWRCAP_BAD = 10, + REASON_DISASSOC_SUPCHAN_BAD = 11, + REASON_IE_INVALID = 13, + REASON_MIC_FAILURE = 14, + REASON_4WAY_HANDSHAKE_TIMEOUT = 15, + REASON_GROUP_KEY_UPDATE_TIMEOUT = 16, + REASON_IE_IN_4WAY_DIFFERS = 17, + REASON_GROUP_CIPHER_INVALID = 18, + REASON_PAIRWISE_CIPHER_INVALID = 19, + REASON_AKMP_INVALID = 20, + REASON_UNSUPP_RSN_IE_VERSION = 21, + REASON_INVALID_RSN_IE_CAP = 22, + REASON_802_1X_AUTH_FAILED = 23, + REASON_CIPHER_SUITE_REJECTED = 24, + REASON_BEACON_TIMEOUT = 200, + REASON_NO_AP_FOUND = 201, + REASON_AUTH_FAIL = 202, + REASON_ASSOC_FAIL = 203, + REASON_HANDSHAKE_TIMEOUT = 204, + + @param WiFiEvent_t + @param WiFiEventInfo_t + @return none + @note https://github.com/espressif/arduino-esp32/blob/04963009eedfbc1e0ea2e1378ae69e7cebda6fd6/tools/sdk/include/esp32/esp_event_legacy.h +*/ +void WiFiMngt_WiFiEventStationDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) { + SystemLog.AddEvent(LogLevel_Warning, String("WiFi disconnected from access point. Reason: ") + String(info.wifi_sta_disconnected.reason)); + system_led.setTimer(STATUS_LED_ERROR); +} + +/** + @brief CB function for start AP + @param WiFiEvent_t + @param WiFiEventInfo_t + @return none +*/ +void WiFiMngt_WiFiEventApStart(WiFiEvent_t event, WiFiEventInfo_t info) { + SystemLog.AddEvent(LogLevel_Info, "WiFi AP start"); +} + +/** + @brief CB function for stop AP + @param WiFiEvent_t + @param WiFiEventInfo_t + @return none +*/ +void WiFiMngt_WiFiEventApStop(WiFiEvent_t event, WiFiEventInfo_t info) { + SystemLog.AddEvent(LogLevel_Info, "WiFi AP stop"); +} + +/** + @brief CB function for AP STA connected + @param WiFiEvent_t + @param WiFiEventInfo_t + @return none +*/ +void WiFiMngt_WiFiEventApStaConnected(WiFiEvent_t event, WiFiEventInfo_t info) { + uint8_t mac[6] = { 0 }; + char msg[100] = { '\0' }; + memcpy(mac, info.wifi_ap_staconnected.mac, 6); + sprintf(msg, "WiFi AP STA. station connected to AP, MAC: %02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + SystemLog.AddEvent(LogLevel_Info, msg); +} + +/** + @brief CB function for AP STA disconnected + @param WiFiEvent_t + @param WiFiEventInfo_t + @return none +*/ +void WiFiMngt_WiFiEventApStaDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) { + uint8_t mac[6] = { 0 }; + char msg[100] = { '\0' }; + memcpy(mac, info.wifi_ap_stadisconnected.mac, 6); + // String(info.wifi_ap_stadisconnected.reason) + sprintf(msg, "WiFi AP STA. station dicconnected from AP, MAC: %02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + SystemLog.AddEvent(LogLevel_Info, msg); +} + +/** + @brief CB function for AP STA IP assigned + @param WiFiEvent_t + @param WiFiEventInfo_t + @return none +*/ +void WiFiMngt_WiFiEventApStaIpAssigned(WiFiEvent_t event, WiFiEventInfo_t info) { + IPAddress stationIP = info.wifi_ap_staipassigned.ip.addr; + SystemLog.AddEvent(LogLevel_Info, "WiFi AP STA. IP assigned to connected station. IP: " + stationIP.toString()); +} + +/** + @brief CB function for AP STA probe request received + @param WiFiEvent_t + @param WiFiEventInfo_t + @return none +*/ +void WiFiMngt_WiFiEventApStaProbeReqRecved(WiFiEvent_t event, WiFiEventInfo_t info) { + SystemLog.AddEvent(LogLevel_Info, "WiFi AP STA receive probe request packet in soft-AP interface"); +} + +/* EOF */ \ No newline at end of file diff --git a/ESP32_PrusaConnectCam/wifi_mngt.h b/ESP32_PrusaConnectCam/wifi_mngt.h new file mode 100644 index 0000000..c5136e7 --- /dev/null +++ b/ESP32_PrusaConnectCam/wifi_mngt.h @@ -0,0 +1,123 @@ +/** + @file wifi_mngt.h + + @brief + + @author Miroslav Pivovarsky + Contact: miroslav.pivovarsky@gmail.com + + @bug: no know bug +*/ + +#ifndef _WIFI_MNGT_H_ +#define _WIFI_MNGT_H_ + +#include +#include "Arduino.h" +#include +#include +#include +#include "esp32-hal-cpu.h" + +#include "mcu_cfg.h" +#include "var.h" +#include "log.h" +#include "system.h" +#include "camera.h" +#include "sys_led.h" + +/* https://github.com/espressif/arduino-esp32/blob/51cb927712e512664a0a0f7b1219fdc18e11b857/libraries/WiFi/src/WiFiGeneric.h#L52 */ +/* https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/network/esp_wifi.html#_CPPv429wifi_event_sta_disconnected_t */ +void WiFiMngt_WiFiEventScanDone(WiFiEvent_t, WiFiEventInfo_t); +void WiFiMngt_WiFiEventStationStart(WiFiEvent_t, WiFiEventInfo_t); +void WiFiMngt_WiFiEventStationStop(WiFiEvent_t, WiFiEventInfo_t); +void WiFiMngt_WiFiEventStationConnected(WiFiEvent_t, WiFiEventInfo_t); +void WiFiMngt_WiFiEventStationDisconnected(WiFiEvent_t, WiFiEventInfo_t); +void WiFiMngt_WiFiEventGotIP(WiFiEvent_t, WiFiEventInfo_t); +void WiFiMngt_WiFiEventLostIP(WiFiEvent_t, WiFiEventInfo_t); +void WiFiMngt_WiFiEventApStart(WiFiEvent_t , WiFiEventInfo_t); +void WiFiMngt_WiFiEventApStop(WiFiEvent_t , WiFiEventInfo_t); +void WiFiMngt_WiFiEventApStaConnected(WiFiEvent_t , WiFiEventInfo_t); +void WiFiMngt_WiFiEventApStaDisconnected(WiFiEvent_t , WiFiEventInfo_t); +void WiFiMngt_WiFiEventApStaIpAssigned(WiFiEvent_t , WiFiEventInfo_t); +void WiFiMngt_WiFiEventApStaProbeReqRecved(WiFiEvent_t , WiFiEventInfo_t); + +class WiFiMngt { +private: + String WifiSsid; ///< WI-FI SSID + String WifiPassword; ///< WI-FI password + bool ServiceMode; ///< flag for enable service AP mode after MCU start during STA_AP_MODE_TIMEOUT ms + String WiFiMode; ///< Wi-Fi mode (AP, STA, STA_AP) + String SericeApSsid; ///< Service AP SSID + bool FirstConnected; ///< flag about first connecting to WiFi network status + bool NtpFirstSync; ///< flag about first NTP sync status + + uint8_t WiFiStaNetworkBssid[6]; ///< BSSID of the network + + bool WiFiStaMultipleNetwork; ///< flag about multiple STA networks + bool StartStaWdg; ///< flag about start STA watchdog + + IPAddress Service_LocalIp; ///< Service IP when si module in the AP mode + IPAddress Service_Gateway; ///< Service gateway when si module in the AP mode + IPAddress Service_Subnet; ///< Service mask when si module in the AP mode + IPAddress Service_Dns; ///< Service DNS when is module in the AP mode + + unsigned long TaskAp_previousMillis; ///< previous time for task AP + unsigned long TaskWdg_previousMillis; ///< previous time for task STA watchdog + + String mDNS_record; ///< mDNS record + String WifiScanJson; ///< global variable with wifi networks + + Configuration *config; ///< pointer to configuration class + Logs *log; ///< pointer to log class + Camera *cam; ///< pointer to camera class + +public: + WiFiMngt(Configuration*, Logs*, Camera*); + ~WiFiMngt() {}; + + void LoadCfgFromEeprom(); + void Init(); + void WifiManagement(); + void WiFiReconnect(); + void SetWifiEvents(); + void WiFiStaConnect(); + void SyncNtpTime(); + + void ScanWiFiNetwork(); + uint8_t ScanWifiNetwork(String); + bool CheckAvailableWifiNetwork(String); + int Rssi2Percent(int); + String TranslateTxPower(wifi_power_t); + String TranslateWiFiStatus(wl_status_t); + String TranslateWiFiEncrypion(wifi_auth_mode_t ); + void CreateApSsid(); + void WiFiWatchdog(); + + String GetServiceApSsid(); + String GetStaSsid(); + String GetStaBssid(); + String GetStaPassword(); + String GetStaStatus(); + String GetStaIp(); + String GetAvailableWifiNetworks(); + String GetWiFiMode(); + String GetWifiMac(); + String GetMdns(); + bool GetkActifeWifiCfgFlag(); + bool GetNtpFirstTimeSync(); + bool GetFirstConnection(); + + void SetStaCredentials(String, String); + void SetStaSsid(String); + void SetStaPassword(String); + void ConnectToSta(); + void SetMdns(String); + void SetFirstConnection(bool); +}; + +extern WiFiMngt SystemWifiMngt; ///< global variable for wifi management + +#endif + +/* EOF */ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0027ee6 --- /dev/null +++ b/README.md @@ -0,0 +1,255 @@ +# PrusaConnect ESP32-CAM + +This repository includes source code and firmware releases for the ESP32-cam module programmed in the Arduino IDE + +This project uses other libraries. It is necessary to install them in the arduino IDE. +- App [Arduino IDE 2.3.2](https://www.arduino.cc/en/software) +- MCU support [ESP32 2.0.15](https://github.com/espressif/arduino-esp32) +- Library [ESPAsyncWebSrv 1.2.7](https://github.com/dvarrel/ESPAsyncWebSrv) +- Library [AsyncTCP 1.1.4](https://github.com/dvarrel/AsyncTCP) +- Library [ArduinoJson 7.0.4](https://github.com/bblanchon/ArduinoJson) +- Library [UniqueID 1.3.0](https://github.com/ricaun/ArduinoUniqueID) + +What we need for functionality +- ESP32-CAM AI-thinker board with OV2640 camera module [ here ](#esp32) +- Module board version [here](#different_mcu) +- Install the necessary libraries in the Arduino IDE [ here ](#arduino_lib) +- Arduino IDE configuration [ here ](#arduino_cfg) +- How to flash firmware to ESP32-cam and connect to PrusaConnect [ here ](https://help.prusa3d.com/preview/guide/esp32-cam-for-prusa-connect_673528) +- How to flash binnary files to ESP32-cam board from Linux/MAC/Windows [ here ](#flash_fw) +- How to reset configuration to factory settings [here](#factory_cfg) +- Status LED [ here ](#status_led) +- Schematic main board is [here](#schematic) +- Issue with FLASH LED on the main board [here](#led_issue) +- External WiFi antena [here](#ext_wifi) +- Power supply [here](#power_supply) +- Debug logs [here](#logs) +- Serial console configuration [here](#serial_cfg) + + +## ESP32-CAM AI-thinker board +It's a few dolars board with **ESP32** MCU and Camera. It's neccesary to buy a board with **camera module OV2640**. The board is sold without a programmer by default. It is possible to program it using the FTDI USB to UART converter, or purchase an official programmer for the board. We recommend purchasing a official programmer. It can save a lot of trouble with connecting and programming the board. There are currently [2 different board version](#different_mcu) but only one is compatible with the official programmer. + + + +In the next picture we can see **ESP32-CAM** board and programator for board. + + + +It's neccesary use a camera version **OV2640**. If using a different camera, then it may be necessary to modify the camera's pinout, or some camera settings may not work correctly. We recommend using a camera module with a viewing angle of 120° or 160°. + +These are currently known or tested camera modules: + +| Camera chip | FOV | Resolution | Tested | Works | Description | +|-------------|------|------------|--------|-------|------------------------------------------| +| OV2640 | 30° | 2MP | No | N/A | | +| OV2640 | 44° | 2MP | No | N/A | | +| OV2640 | 66° | 2MP | Yes | Yes | Recomended. Standard camera module | +| OV2640 | 120° | 2MP | Yes | Yes | Recomended | +| OV2640 | 160° | 2MP | Yes | Yes | Recomended | +| OV2640 | 200° | 2MP | No | N/A | | +| OV2640 | 222° | 2MP | No | N/A | | +| OV2640IR | 160° | 2MP | Yes | Yes | | +| OV8225N | 66° | 2MP | Yes | Yes | | +| OV3360 | 66° | 3MP | Yes | Yes | | +| OV5640-AF | 72° | 5MP | Yes | Yes | Overheating, slow photo loading | + + +## Different MCU version +There are currently 2 versions of the board, but only one version is possible programming via CH340. The blue rectangle shows the differences between the HW versions. + + + +The red arrow points to a pin that differs between these boards. In version 1, this pin is used for MCU RESET (GND/R). In version 2, this pin serves as ground (GND). Version 1 can be programmed via CH340, whereas version 2 cannot be programmed via CH340. For version 2, we tested programming via FT232RL or CP2102, and the programming process worked successfully. + + +## Necessary libraries in the Arduino IDE + +Software compilation was done in Arduino IDE 2.3.2. To ensure proper functionality, it is necessary to install support for ESP32 boards into Arduino IDE, as well as several other libraries + +At the first step we need to install support for **ESP32 board**. + +**File** -> **Preferences** -> **Additional boards managers URLs** + + ``` + https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + ``` + +then go to **Tools** -> **Board** -> **Boards Manager...** and install module **ESP32** by **Espressif Systems**, version **2.0.15** + +Next step is to install the necessary libraries. Go to **Sketch** -> **Include Library** -> **Manage Libraries...** or you can use **Ctrl+Shift+I**. Then you can search for the necessary libraries and install them. + +- Library [ESPAsyncWebSrv by dvarrel 1.2.7](https://github.com/dvarrel/ESPAsyncWebSrv) +- Library [AsyncTCP by dvarrel 1.1.4](https://github.com/dvarrel/AsyncTCP) +- Library [ArduinoJson by bblanchon 7.0.4](https://github.com/bblanchon/ArduinoJson) +- Library [UniqueID by Luiz Henrique Cassettari1.3.0](https://github.com/ricaun/ArduinoUniqueID) + + +## Arduino IDE configuration + +Board configuration in the arduino IDE 2.3.2 +- Tools -> Board -> ESP32 Arduino -> AI Thinker ESP32 +- Tools -> Flash frequency -> 80MHz +- Tools -> Core Debug Level -> None +- Tools -> Erase all Flash Before Sketch Upload -> Disable **(first flash, new board = enable. otherwise = disable)** +- Tools -> Flash Mode -> DIO +- Tools -> Partition scheme -> Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS) + +When flashing the firmware to a new, empty ESP32-CAM device for the first time, it is necessary to use the 'Erase' function. + +This can be found under **Tools** -> **Erase all Flash Before Sketch Upload** -> **Enable**. + +After the initial firmware upload to the MCU, it's necessary to disable this option. If you do not disable this option, your camera configuration will continue to be erased from the flash memory after uploading new firmware from the Arduino IDE. + + +## How to flash binnary files to ESP32-cam board from Linux/MAC/Windows + +To upload the firmware on the MAC or Linux platform, you must use the console. First, ensure you have installed esptool for Python. You can find it on the manufacturer's website, ESPRESSIF, [here](https://docs.espressif.com/projects/esp-at/en/latest/esp32/Get_Started/Downloading_guide.html). + +And command for FLASH FW is here, where **/dev/ttya0** is your serial interface for communication with the ESP32-cam board. +``` +python3 -m esptool -p /dev/ttya0 -b 460800 --before default_reset --after hard_reset --chip +esp32 write_flash --erase-all --flash_mode dio --flash_size 4MB --flash_freq 80m 0x1000 +ESP32_PrusaConnectCam_web.ino.bootloader.bin 0x8000 +ESP32_PrusaConnectCam_web.ino.partitions.bin 0x10000 ESP32_PrusaConnectCam_web.ino.bin +``` + +Here is the partitions table: + +| Name | Type | SubType | Offset | Size | Flags | +|---------|---------|---------|----------|----------|-------| +| nvs | data | nvs | 0x9000 | 0x5000 | | +| otadata | data | ota | 0xe000 | 0x2000 | | +| app0 | app | ota_0 | 0x10000 | 0x1E0000 | | +| app1 | app | ota_1 | 0x1F0000 | 0x1E0000 | | +| spiffs | data | spiffs | 0x3D0000 | 0x20000 | | +| coredump| data | coredump| 0x3F0000 | 0x10000 | | + +However, for uploading the firmware, it's important to use this configuration of addresses and files: + +- address **0x1000** - **ESP32_PrusaConnectCam.ino.bootloader.bin** +- address **0x8000** - **ESP32_PrusaConnectCam.ino.partitions.bin** +- address **0x10000** - **ESP32_PrusaConnectCam.ino.bin** + +Here is tool and configuration for [windows platform](https://www.espressif.com/en/support/download/other-tools) + + + +It's necessary to erase the FLASH using the **ERASE** button before the first firmware flash. + + + + +## How to reset configuration to factory settings +To reset the settings to factory defaults, follow these instructions: + + + +- Connect PIN **IO12** to **ground**. +- **Plug in** the power supply. +- Wait for **10 seconds**. +- After 10 seconds, the **FLASH LED will start flashing**. +- **Disconnect** PIN **IO12** from **ground** (but do't disconnect the power supply). +- After disconnecting **IO12** from **ground**, the **FLASH LED** will **stop flashing**, and the MCU will **automatically rebooted**. +- Now the MCU is in the factory settings. + + +## Status LED + +On the board, there is a status LED that provides a visual indicator of the module's current status +through blinking at defined intervals. + + + +Upon module activation, the LED illuminates. After processor initialization, the LED exhibits different blinking intervals based on the current mode of the module + +- **Just service AP Mode:** The LED blinks every **400 ms**, indicating the module's availability in service AP mode. +- **Connecting to WiFi AP:** While connecting to a WiFi Access Point, the LED blinks at intervals of **800 ms**. +- **Connected to WiFi Network:** Upon successful connection to a WiFi network, the LED blinks at intervals of **4000 ms**, signaling a stable connection. +- **Problematic State:** If an issue or error occurs, the LED accelerates its blinking to every **100 ms**. + + +## Schematic for ESP32-cam board + + + +Parts description + + + +Pinout + + + + +## FLASH LED issue +The board has a problem with the FLASH LED, as it lacks any current limitation for the LED. Consequently, frequent use of the FLASH LED can lead to corruption, due to excessive current flow. + +One simple solution is to connect an external LED via a relay, transistor, or MOSFET to the board, as shown in the next picture. Using a relay is not ideal, but it provides a simple solution. + + + +Another solution is to use a LED COB or a USB LED lamp. I utilized a board from a simple USB LED lamp. The transistor has a current limitation of 500mA, and my USB lamp has a current consumption of 180mA. The original LED has a current consumption of 60-80mA, and the USB lamp has a current consumption of approximately 180mA. After calculation, the total current consumption is approximately 260mA, which falls within the current limitation of the transistor. Therefore, it is possible to solder the negative wire from the COB LED or the USB LED lamp to the transistor. The positive wire needs to be soldered to +5V. + +This is my USB LED lamp + + + +The next step is to solder the negative wire from the LED lamp to the transistor collector, and the positive wire from the LED lamp to the +5V on the board. + + + +The third option is to solder a resistor between the collector of the transistor and the PCB. I used a 10-ohm resistor in a 0603 package. This option is more complicated for users with limited soldering experience. + + + + +## External/internal WiFi antenna + +The standard ESP32-CAM board utilizes an internal antenna on the PCB. However, this antenna can sometimes cause issues with the quality of the WiFi signal, leading to slow photo uploads to PrusaConnect or connectivity problems. Fortunately, there is an option to connect an external antenna. This requires changing the resistor position, as shown in the picture below. Then, you can use a 2.4GHz Wi-Fi cable with a U.FL to RP-SMA connector and a standard 2.4GHz WiFi antenna + + + + +## Power Supply + +The device requires a 5V power supply, with a maximum current consumption of 2A. When using the original programmer, power is supplied via a micro USB connector. + + +## Debug logs + +It is possible to save debug logs to a microSD card, but the card must be formatted to FAT32. Currently, the maximum tested capacity for a microSD card is 16GB. If a microSD card is inserted into the camera, it is necessary to reboot the camera. When a microSD card is inserted into the camera before boot, logging to the microSD card is automatically enabled. If no microSD card is inserted, the saving of debug logs to the microSD card is automatically disabled. Enabling the saving of debug logs to a microSD card is only possible during camera boot, so it is necessary to restart the camera after inserting the microSD card. Debug logs are saved as plain text in the file Syslog.log + + +## Serial console configuration + +Currently is possible set the basicaly camera configuration during serial console. Baud speed for communication with MCU is **115200 8N1** + +Commands for configuration have simple syntax + +| command | separator | variable | termination | line terminator | +|--------------|-----------|-----------|-------------|-----------------| +| setwifissid | : | SSID | ; | \n | + +Currently available commands are listed in the table below: + +| Command | Description | +|-------------------|---------------------------------------------------------------------| +| setwifissid | Setting WiFi SSID, where variable SSID is network name | +| setwifipass | Setting WiFi password, where variable PASSWORD is WiFi password | +| wificonnect | Connecting to WiFi network | +| mcureboot | Rebooting the MCU | +| commandslist | Listing currently supported commands via serial console | +| getwifimode | Print currently WiFi mode. STA/AP/AP+STA | +| getwifistastatus | Print WiFi STA status. Connected/Disconnected/Connecting.... | +| getwifistaip | Print IP address for WiFi STA | +| getserviceapssid | Print service AP SSID name | +| setauthtoken: | Set authentication token for Prusa Connect | + +Standard commands sequence for camera basic settings is + +- setwifissid:SSID; +- setwifipass:PASSWORD; +- wificonnect; +- setauthtoken:TOKEN; +- mcureboot; diff --git a/doc/ESP32-CAM-AI-Thinker-schematic-diagram.png b/doc/ESP32-CAM-AI-Thinker-schematic-diagram.png new file mode 100644 index 0000000..1bac44c Binary files /dev/null and b/doc/ESP32-CAM-AI-Thinker-schematic-diagram.png differ diff --git a/doc/cam_versions.jpg b/doc/cam_versions.jpg new file mode 100644 index 0000000..b2d6d01 Binary files /dev/null and b/doc/cam_versions.jpg differ diff --git a/doc/esp32-cam.jpg b/doc/esp32-cam.jpg new file mode 100644 index 0000000..4f65905 Binary files /dev/null and b/doc/esp32-cam.jpg differ diff --git a/doc/esp32-cam_ext_ant.png b/doc/esp32-cam_ext_ant.png new file mode 100644 index 0000000..760f5f0 Binary files /dev/null and b/doc/esp32-cam_ext_ant.png differ diff --git a/doc/esp32-cam_flash_led_resistor.jpg b/doc/esp32-cam_flash_led_resistor.jpg new file mode 100644 index 0000000..7637644 Binary files /dev/null and b/doc/esp32-cam_flash_led_resistor.jpg differ diff --git a/doc/esp32-cam_parts.jpg b/doc/esp32-cam_parts.jpg new file mode 100644 index 0000000..815656a Binary files /dev/null and b/doc/esp32-cam_parts.jpg differ diff --git a/doc/esp32-cam_with_led_lamp.jpg b/doc/esp32-cam_with_led_lamp.jpg new file mode 100644 index 0000000..919fc0e Binary files /dev/null and b/doc/esp32-cam_with_led_lamp.jpg differ diff --git a/doc/esp32_and_prog.jpg b/doc/esp32_and_prog.jpg new file mode 100644 index 0000000..d438cff Binary files /dev/null and b/doc/esp32_and_prog.jpg differ diff --git a/doc/esp32cam flash.jpg b/doc/esp32cam flash.jpg new file mode 100644 index 0000000..14531f3 Binary files /dev/null and b/doc/esp32cam flash.jpg differ diff --git a/doc/factory_cfg.jpg b/doc/factory_cfg.jpg new file mode 100644 index 0000000..7236b1a Binary files /dev/null and b/doc/factory_cfg.jpg differ diff --git a/doc/how to flash chip select.jpg b/doc/how to flash chip select.jpg new file mode 100644 index 0000000..c10243e Binary files /dev/null and b/doc/how to flash chip select.jpg differ diff --git a/doc/how to flash.jpg b/doc/how to flash.jpg new file mode 100644 index 0000000..0427b31 Binary files /dev/null and b/doc/how to flash.jpg differ diff --git a/doc/pinout.png b/doc/pinout.png new file mode 100644 index 0000000..e7f650d Binary files /dev/null and b/doc/pinout.png differ diff --git a/doc/relay_flash_bb.png b/doc/relay_flash_bb.png new file mode 100644 index 0000000..e5bcb00 Binary files /dev/null and b/doc/relay_flash_bb.png differ diff --git a/doc/status_led.jpg b/doc/status_led.jpg new file mode 100644 index 0000000..870a25f Binary files /dev/null and b/doc/status_led.jpg differ diff --git a/doc/usb_lamp.jpg b/doc/usb_lamp.jpg new file mode 100644 index 0000000..dc5e445 Binary files /dev/null and b/doc/usb_lamp.jpg differ diff --git a/webpage/assets/icons/eye-slash.svg b/webpage/assets/icons/eye-slash.svg new file mode 100644 index 0000000..0c02012 --- /dev/null +++ b/webpage/assets/icons/eye-slash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webpage/assets/icons/eye.svg b/webpage/assets/icons/eye.svg new file mode 100644 index 0000000..b8d55f9 --- /dev/null +++ b/webpage/assets/icons/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webpage/assets/icons/favicon.svg b/webpage/assets/icons/favicon.svg new file mode 100644 index 0000000..85938b4 --- /dev/null +++ b/webpage/assets/icons/favicon.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/webpage/assets/icons/github-icon.svg b/webpage/assets/icons/github-icon.svg new file mode 100644 index 0000000..314ee70 --- /dev/null +++ b/webpage/assets/icons/github-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webpage/assets/icons/light-off-icon.svg b/webpage/assets/icons/light-off-icon.svg new file mode 100644 index 0000000..c585fc4 --- /dev/null +++ b/webpage/assets/icons/light-off-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webpage/assets/icons/light-on-icon.svg b/webpage/assets/icons/light-on-icon.svg new file mode 100644 index 0000000..09b3d55 --- /dev/null +++ b/webpage/assets/icons/light-on-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webpage/assets/icons/reboot-icon.svg b/webpage/assets/icons/reboot-icon.svg new file mode 100644 index 0000000..92594c9 --- /dev/null +++ b/webpage/assets/icons/reboot-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webpage/assets/icons/refresh-icon.svg b/webpage/assets/icons/refresh-icon.svg new file mode 100644 index 0000000..76601c1 --- /dev/null +++ b/webpage/assets/icons/refresh-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webpage/assets/icons/wifi-icon-0.svg b/webpage/assets/icons/wifi-icon-0.svg new file mode 100644 index 0000000..9232a64 --- /dev/null +++ b/webpage/assets/icons/wifi-icon-0.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/webpage/assets/icons/wifi-icon-1.svg b/webpage/assets/icons/wifi-icon-1.svg new file mode 100644 index 0000000..7e6a60e --- /dev/null +++ b/webpage/assets/icons/wifi-icon-1.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/webpage/assets/icons/wifi-icon-2.svg b/webpage/assets/icons/wifi-icon-2.svg new file mode 100644 index 0000000..c07d254 --- /dev/null +++ b/webpage/assets/icons/wifi-icon-2.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/webpage/assets/icons/wifi-icon-3.svg b/webpage/assets/icons/wifi-icon-3.svg new file mode 100644 index 0000000..5666c9f --- /dev/null +++ b/webpage/assets/icons/wifi-icon-3.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/webpage/assets/icons/wifi-icon-4.svg b/webpage/assets/icons/wifi-icon-4.svg new file mode 100644 index 0000000..6617fc6 --- /dev/null +++ b/webpage/assets/icons/wifi-icon-4.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/webpage/assets/icons/wifi-icon.svg b/webpage/assets/icons/wifi-icon.svg new file mode 100644 index 0000000..c3d8c63 --- /dev/null +++ b/webpage/assets/icons/wifi-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webpage/assets/logo/prusa_connect_esp32_cam.svg b/webpage/assets/logo/prusa_connect_esp32_cam.svg new file mode 100644 index 0000000..48f7c10 --- /dev/null +++ b/webpage/assets/logo/prusa_connect_esp32_cam.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webpage/cookies.html b/webpage/cookies.html new file mode 100644 index 0000000..9f9e464 --- /dev/null +++ b/webpage/cookies.html @@ -0,0 +1,7 @@ + + +

To read the Cookie policy, please visit this page.

+ + \ No newline at end of file diff --git a/webpage/gtac.html b/webpage/gtac.html new file mode 100644 index 0000000..5af720b --- /dev/null +++ b/webpage/gtac.html @@ -0,0 +1,7 @@ + + +

To read the General Terms and Conditions, please visit this page.

+ + \ No newline at end of file diff --git a/webpage/index.html b/webpage/index.html new file mode 100644 index 0000000..9127b2e --- /dev/null +++ b/webpage/index.html @@ -0,0 +1,90 @@ + + + + + Prusa ESP32-cam + + + + + + + + +
+ +
+
+
+ +
+
+
+
+

Trigger interval: s



+

+


+

+
+
+
+ +
+ + + + + +
+ +
+
+ +



+ + + + + +

Prusa Connect ESP32 cam

Author

Miroslav Pivovarsky

Licence | General Terms and Conditions | Privacy Policy | Cookie Preferences
+ + + + + \ No newline at end of file diff --git a/webpage/license.html b/webpage/license.html new file mode 100644 index 0000000..5b066fe --- /dev/null +++ b/webpage/license.html @@ -0,0 +1,8 @@ + + +

The software for device falls under the GPL-3.0 license terms. To read the license terms please visit this page.

+ + \ No newline at end of file diff --git a/webpage/page_auth.html b/webpage/page_auth.html new file mode 100644 index 0000000..2a45e6b --- /dev/null +++ b/webpage/page_auth.html @@ -0,0 +1,23 @@ + + + + +
+ + + + + + + +
Set web authentication
WEB authentication
Username
Password Show Password
Confirm Password Show Password
+
+ + + + \ No newline at end of file diff --git a/webpage/page_config.html b/webpage/page_config.html new file mode 100644 index 0000000..096da17 --- /dev/null +++ b/webpage/page_config.html @@ -0,0 +1,79 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
Basic image settings
Connect Token 
Fingerprint
Trigger Interval [s] 
Image qualityLow High
Resolution + +
BrightnessLow High
ContrastLow High
SaturationLow High
Horizontal mirror
Vertical flip
LED light
LED flash
Flash durationLow High
Flash duration ms
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
Advanced image settings
Automatic white balancing
Automatic white balancing gain
Automatic white balancing mode + +
Automatic Exposure Control
Second Level Automatic Exposure Control
Automatic exposure levelLow High
Automatic exposure timeLow High
Automatic exposure time ms
Automatic gain control
Automatic gain control levelLow High
Bad pixel correction
White pixel correction
Raw gamma correction
Lens correction
+
+ + + + \ No newline at end of file diff --git a/webpage/page_system.html b/webpage/page_system.html new file mode 100644 index 0000000..bb1748e --- /dev/null +++ b/webpage/page_system.html @@ -0,0 +1,48 @@ + + + +
+ + + + + + + + + + + + + + + + +
System status
PrusaConnect Status
Wi-Fi mode
Wi-Fi service AP SSID
Uptime
Software version
Software build
Available software update Check update from cloud
System configuration
mDNS record.local 
Log level + +
Get logs
+
+
+
+ + + + + + + +
Firmware update
Status:Ready
Processing:
0%
+
+ + + + \ No newline at end of file diff --git a/webpage/page_wifi.html b/webpage/page_wifi.html new file mode 100644 index 0000000..3e5dc7a --- /dev/null +++ b/webpage/page_wifi.html @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + +
+

Connection status

+

Status:

+

SSID:

+

Signal: % / dBm

+

IP Address:

+

mDNS: https://.local

+
+
+

Available networks

+
+ + + + + + + + + + + + + +
Network name (SSID)Signal strength (RSSI)ChannelEncryption
+
+ +
+
+
+ + + + + +
Connect to Wi-Fi network
Wi-Fi network name (SSID)
Password
+
+ + + + \ No newline at end of file diff --git a/webpage/privacypolicy.html b/webpage/privacypolicy.html new file mode 100644 index 0000000..5ec4e3f --- /dev/null +++ b/webpage/privacypolicy.html @@ -0,0 +1,7 @@ + + +

To read the Privacy Policy, please visit this page.

+ + \ No newline at end of file diff --git a/webpage/scripts.js b/webpage/scripts.js new file mode 100644 index 0000000..728d688 --- /dev/null +++ b/webpage/scripts.js @@ -0,0 +1,448 @@ +function get_data(val) { + jQuery.ajax({ + url: 'json_input', + type: 'GET', + timeout: 5000, + success: function(data) { + console.log("Incommming data: "); + console.log(data); + var obj = JSON.parse(data); + console.log(obj); + + if (!document.querySelector('#light-icon img')) { + var img = document.createElement('img'); + img.src = (obj.led == "true") ? 'light-on-icon.svg' : 'light-off-icon.svg'; + document.getElementById('light-icon').appendChild(img); + } + + if (val == "config") { + $("#fingerprint").text(obj.fingerprint); + $("#refreshInterval").text(obj.refreshInterval); + document.getElementById('tokenid').value = obj.token; + document.getElementById('refreshid').value = obj.refreshInterval; + document.getElementById('photo_qualityid').value = obj.photoquality; + document.getElementById('framesizeid').value = obj.framesize; + document.getElementById('brightnessid').value = obj.brightness; + document.getElementById('contrastid').value = obj.contrast; + document.getElementById('saturationid').value = obj.saturation; + document.getElementById('hmirrorid').checked = obj.hmirror; + document.getElementById('vflipid').checked = obj.vflip; + document.getElementById('lencid').checked = obj.lensc; + document.getElementById('exposure_ctrlid').checked = obj.exposure_ctrl; + document.getElementById('awbid').checked = obj.awb; + document.getElementById('awb_gainid').checked = obj.awb_gain; + document.getElementById('wb_modeid').value = obj.wb_mode; + document.getElementById('ledid').checked = obj.led; + document.getElementById('flashid').checked = obj.flash; + document.getElementById('flash_timeid').value = obj.flash_time; + document.getElementById('bpcid').checked = obj.bpc; + document.getElementById('wpcid').checked = obj.wpc; + document.getElementById('raw_gamaid').checked = obj.raw_gama; + document.getElementById('aec2id').checked = obj.aec2; + document.getElementById('ae_levelid').value = obj.ae_level; + document.getElementById('aec_valueid').value = obj.aec_value; + document.getElementById('gain_ctrlid').checked = obj.gain_ctrl; + document.getElementById('agc_gainid').value = obj.agc_gain; + document.getElementById("flash_time_value").innerText = obj.flash_time; + document.getElementById("aec_value_value").innerText = obj.aec_value; + $("#status_hmirror").text((obj.hmirror == "true") ? "On" : "Off"); + $("#status_vflip").text((obj.vflip == "true") ? "On" : "Off"); + $("#status_lensc").text((obj.lensc == "true") ? "On" : "Off"); + $("#status_exposure_ctrl").text((obj.exposure_ctrl == "true") ? "On" : "Off"); + $("#status_awb").text((obj.awb == "true") ? "On" : "Off"); + $("#status_awb_gain").text((obj.awb_gain == "true") ? "On" : "Off"); + $("#status_led").text((obj.led == "true") ? "On" : "Off"); + $("#status_flash").text((obj.flash == "true") ? "On" : "Off"); + $("#status_bpc").text((obj.bpc == "true") ? "On" : "Off"); + $("#status_wpc").text((obj.wpc == "true") ? "On" : "Off"); + $("#status_raw_gama").text((obj.raw_gama == "true") ? "On" : "Off"); + $("#status_aec2").text((obj.aec2 == "true") ? "On" : "Off"); + $("#status_gain_ctrl").text((obj.gain_ctrl == "true") ? "On" : "Off"); + sliderCheck(); + } + + if (val == "auth") { + document.getElementById('authid').checked = obj.auth; + $("#status_auth").text((obj.auth == "true") ? "On" : "Off"); + document.getElementById('auth_username').value = obj.auth_username; + } + + if (val == "wifi") { + $("#ssid").text(obj.ssid); + $("#rssi").text(obj.rssi); + $("#rssi_percentage").text(obj.rssi_percentage); + $("#ip").text(obj.ip); + $("#mdns").text(obj.mdns); + $("#wifi_network_status").text(obj.wifi_network_status); + + if (!document.querySelector('#main-wifi-signal wifi_img')) { + var wifi_img = document.createElement('wifi_img'); + wifi_img.width = 19; + wifi_img.height = 12; + wifi_img.src = getIconPath(obj.rssi); + document.getElementById('main-wifi-signal').appendChild(wifi_img); + } + } + + if (val == "system") { + $("#uptime").text(obj.uptime); + $("#sw_ver").text(obj.sw_ver); + $("#sw_build").text(obj.sw_build); + $("#last_upload_status").text(obj.last_upload_status); + $("#wifi_mode").text(obj.wifi_mode); + $("#sw_new_ver").text(obj.sw_new_ver); + $("#service_ap_ssid").text(obj.service_ap_ssid); + document.getElementById('mdnsid').value = obj.mdns; + document.getElementById('loglevelid').value = obj.log_level; + } + }, + error: function(html) { + console.log("json Timeout or error"); + //alert("jquery timeout or comunication error"); + } + }); +} + +function sliderCheck() { + var ranges = document.querySelectorAll(".slider"); + ranges.forEach(function(range) { + var percent = (range.value - range.min) / (range.max - range.min) * 100; + var gradient = "linear-gradient(to right, #FA6831 " + percent + "%, #d3d3d3 " + percent + "%)"; + range.style.background = gradient; + range.oninput = function() { + var percent = (this.value - this.min) / (this.max - this.min) * 100; + var gradient = "linear-gradient(to right, #FA6831 " + percent + "%, #d3d3d3 " + percent + "%)"; + this.style.background = gradient; + } + }); +} + +function getIconPath(rssi) { + let path; + if (rssi == 0) { + path = 'wifi-icon-0.svg'; + } else if (rssi <= -70) { + path = 'wifi-icon-1.svg'; + } else if (rssi > -70 && rssi <= -60) { + path = 'wifi-icon-2.svg'; + } else if (rssi > -60 && rssi <= -50) { + path = 'wifi-icon-3.svg'; + } else { + path = 'wifi-icon-4.svg'; + } + return path; +} + +var OpenImageclickCount = 0; + +function openImage() { + var img = document.getElementById("photo"); + if (OpenImageclickCount % 2 == 0) { + img.style.position = "fixed"; + img.style.top = "5%"; + img.style.left = "5%"; + img.style.width = "auto"; + img.style.height = "auto"; + img.style.maxWidth = "100%"; + img.style.maxHeight = "90%"; + img.style.zIndex = "9999"; + } else { + img.style.position = ""; + img.style.top = ""; + img.style.left = ""; + img.style.width = ""; + img.style.height = ""; + img.style.zIndex = ""; + } + OpenImageclickCount++; +} + +function actionButton(url, reload, msg) { + var xhr = new XMLHttpRequest(); + + if (msg != '') { + alert(msg); + } + + xhr.onreadystatechange = function() { + if (xhr.readyState == 4 && xhr.status == 200) { + if (reload == true) { + setTimeout(function() { + location.reload(); + }, 200); + } + } + }; + + xhr.open('GET', url, true); + xhr.send(); +} + +function setActive(link) { + var links = document.querySelectorAll('#links li a'); + links.forEach(function(item) { + item.classList.remove('active'); + }); + link.classList.add('active'); +} + +var links = document.querySelectorAll('#links li a'); +links.forEach(function(link) { + link.addEventListener('click', function() { + setActive(link); + }); +}); + +function addClickListener(id) { + var link = document.getElementById(id); + if (!link.hasOwnProperty('clickListener')) { + link.addEventListener('click', function(event) { + event.preventDefault(); + window.open(link.href, '_blank'); + }); + link.clickListener = true; + } +} + +/* wifi page */ +function setWifi(val_ssid, val_pass) { + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", "wifi_cfg?wifi_ssid=" + encodeURIComponent(val_ssid) + "&wifi_pass=" + encodeURIComponent(val_pass), false); + xmlHttp.send(null); + alert(xmlHttp.responseText); + get_data("wifi"); +} + +function scanWifi() { + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", "wifi_scan?", false); + xmlHttp.send(null); + alert(xmlHttp.responseText); + get_data("wifi"); + setTimeout(function() { + GetDataAndPrintTableWiFi(); + }, 8000); +} + +function GetDataAndPrintTableWiFi() { + $("#wifi_ntw").find("tr:gt(0)").remove(); + $.ajax({ + url: 'json_wifi', + type: 'GET', + timeout: 15000, + dataType: 'json', + data: {}, + + success: function(data) { + for (var i = 0; i < data.length; i++) { + const IconName = "wifi-icon-" + i; + var row = $('' + data[i].ssid + '
' + data[i].channel + '' + data[i].encryption + ''); + $('#wifi_ntw').append(row); + + if (!document.querySelector('#' + IconName + ' img')) { + var img = document.createElement('img'); + img.src = getIconPath(data[i].rssi); + document.getElementById(IconName).prepend(img); + document.getElementById(IconName).append(data[i].rssi_percentage); + document.getElementById(IconName).append("% / "); + document.getElementById(IconName).append(data[i].rssi); + document.getElementById(IconName).append("dBm"); + } + } + }, + error: function(jqXHR, textStatus, errorThrown) { + console.log('Error:' + textStatus + '-' + errorThrown); + } + }); +} + +/* auth page */ +function setAuth(val_name, val_pass) { + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", "basicauth_cfg?" + "auth_username=" + encodeURIComponent(val_name) + "&auth_password=" + encodeURIComponent(val_pass), false); + xmlHttp.send(null); + alert(xmlHttp.responseText); + get_data("auth"); +} + +function changeValue(val, url, reload) { + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", url + val, false); + xmlHttp.send(null); + if ((url == "set_int?refresh=") || (url == "set_token?token=") || (url == "set_mdns?mdns=")) { + alert(xmlHttp.responseText); + } + if (url == "set_flash_time?flash_time=") { + document.getElementById("flash_time_value").innerText = val; + } + get_data(reload); +} + +function togglePasswordVisibility() { + const passwordInput = document.getElementById("auth_password"); + const eyeIcon = document.getElementById("eye-icon"); + if (passwordInput.getAttribute("type") === "password") { + passwordInput.setAttribute("type", "text"); + passwordInput.classList.add("reveal"); + eyeIcon.src = "eye-slash.svg"; + eyeIcon.alt = "Hide Password"; + } else { + passwordInput.setAttribute("type", "password"); + passwordInput.classList.remove("reveal"); + eyeIcon.src = "eye.svg"; + eyeIcon.alt = "Show Password"; + } +} + +/* system page */ +if (typeof uploadingFirmware === 'undefined') { + var uploadingFirmware = false; +} + +if (typeof FileSize === 'undefined') { + var FileSize = 0; +} + +function uploadFile() { + alert("Started updating..."); + const firmwareInput = document.getElementById('firmwareInput'); + const statusDiv = document.getElementById('status'); + const file = firmwareInput.files[0]; + FileSize = file.size; + SetFirmwareSize(file.size); + + if (file) { + statusDiv.innerText = 'Updating...'; + uploadingFirmware = true; + const formData = new FormData(); + formData.append('firmware', file); + + fetch('/upload', { + method: 'POST', + body: formData, + }) + .then((response) => { + if (response.ok) { + response.text().then((data) => { + const jsonData = JSON.parse(data); + updateProgress(); + uploadingFirmware = false; + if (jsonData.errorMessage) { + alert(`Error message: ${jsonData.errorMessage}`); + } + }); + } else { + uploadingFirmware = false; + response.text().then((errorMessage) => { + alert(`Error message: ${errorMessage}`); + }); + } + }) + .catch((error) => { + console.error('Error:', error); + uploadingFirmware = false; + }); + } else { + statusDiv.innerText = 'No file selected'; + } +} + +function SetFirmwareSize(val) { + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", "set_firmware_size?size=" + val, false); + xmlHttp.send(null); +} + +function updateProgress() { + if (!uploadingFirmware) { + return; + } + + fetch('/UpdateProcessing', { + method: 'GET', + }) + .then((response) => { + if (response.ok) { + return response.json(); + } else { + throw new Error('Failed to fetch progress'); + } + }) + .then((data) => { + const statusDiv = document.getElementById('status'); + var progressBar = document.getElementById("myProgressBar"); + progressBar.style.width = data.processed_percent + "%"; + progressBar.innerHTML = data.processed_percent + "%"; + + statusDiv.innerText = data.message; + uploadingFirmware = data.updating; + if (data.updating == false && !updateCompleted) { + alert('Operation done. Please reboot MCU.'); + uploadingFirmware = false; + updateCompleted = true; + clearInterval(updateInterval); + } + }) + .catch((error) => { + console.error('Error:', error); + var progressBar = document.getElementById("myProgressBar"); + progressBar.innerHTML = "Error"; + clearInterval(updateInterval); + }); +} + +function checkUpdate() { + var xmlHttp = new XMLHttpRequest(); + alert("Connecting to server... Please wait several second"); + xmlHttp.open("GET", "/check_web_ota_update", false); + xmlHttp.send(null); + alert(xmlHttp.responseText); + get_data("system"); +} + +function updateWeb() { + alert("Started updating from cloud."); + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", "web_ota_update?update=true", false); + xmlHttp.send(null); + uploadingFirmware = true; +} + +function validatePasswords() { + var password = document.getElementById("auth_password").value; + var confirmPassword = document.getElementById("auth_password_confirm").value; + var saveButton = document.querySelector(".btn_save_a"); + var passwordStatus = document.getElementById("pass_match"); + + if (password === confirmPassword) { + passwordStatus.innerHTML = "✔️"; + saveButton.disabled = false; + } else { + passwordStatus.innerHTML = "❌"; + saveButton.disabled = true; + } +} + +if (document.getElementById("auth_password")) { + document.getElementById("auth_password").addEventListener("input", validatePasswords); +} + +if (document.getElementById("auth_password_confirm")) { + document.getElementById("auth_password_confirm").addEventListener("input", validatePasswords); +} + +if ((document.getElementById("auth_password")) && (document.getElementById("auth_password_confirm"))) { + validatePasswords(); +} + +function setupCollapsibleButtons() { + $(".btn_collapsible").click(function(){ + $(this).toggleClass("active"); + var content = $(this).parent().next(); + if (content.css("display") === "block") { + content.css("display", "none"); + } else { + content.css("display", "block"); + } + }); +} diff --git a/webpage/styles.css b/webpage/styles.css new file mode 100644 index 0000000..7ad6680 --- /dev/null +++ b/webpage/styles.css @@ -0,0 +1,500 @@ +body { + font-family: sans-serif; +} + +/* index styles */ + .p1 { + color: #797979; + font: normal normal normal 18px/5px sans-serif; + letter-spacing: 0px; +} + .p2 { + text-align: left; + font: normal normal bold 14px/20px sans-serif; + letter-spacing: 0px; + color: #808080; + opacity: 1; + display: inline-block; +} + .p3 { + text-align: left; + font: normal normal normal 14px/20px sans-serif; + letter-spacing: 0px; + color: #808080; + opacity: 1; + display: inline-block; +} + .p4 { + text-align: left; + font: normal normal normal 14px/20px sans-serif; + letter-spacing: 0px; + color: #808080; + opacity: 1; +} + .p5 { + text-align: center; + font: normal normal bold 14px/20px sans-serif; + letter-spacing: 0px; + color: #000000; + opacity: 1; +} +/* NAVIGATION BAR */ + nav { + display: flex; + background-color: transparent; +} + .top_bar { + display: flex; + width: 100%; +} + .top_bar li { + display: inline-block; + padding: 5px; +} + .top_bar li a { + text-decoration: none; + cursor: pointer; + display: flex; + align-items: center; +} + .top_bar li a:hover { + text-decoration: underline #fa6831; + text-underline-position: under; + text-underline-offset: 8px; + text-decoration-thickness: 2px; +} + #links li a.active { + text-decoration: underline #fa6831; + text-underline-position: under; + text-underline-offset: 8px; + text-decoration-thickness: 2px; +} +/* CFG BAR */ +cfg { + display: flex; + flex-direction: column; + text-align: center; + font: normal normal bold 14px/20px sans-serif; + letter-spacing: 0px; + color: #2A2A2A; + opacity: 1; +} +cfg_bar li { + display: inline-block; + padding: 14px; + font-size: 16px; + left: 50%; +} +cfg_bar li a { + text-decoration: none; + cursor: pointer; + color: #212529; +} +cfg_bar li a:hover { + color: #fa6831; +} +/* CONTAINER */ + .container { + display: table; + height: 100%; + width: 100%; +} + .container_left-half { + grid-column: 1; + display: table-cell; + vertical-align: middle; + width: 50%; + text-align: center; +} + .container_right-half { + grid-column: 2; + display: table-cell; + vertical-align: middle; + width: 50%; +} +/* CHECKBOX SLIDER */ + .switch { + position: relative; + display: inline-block; + width: 30px; + height: 17px; + vertical-align: middle; +} + .switch input { + opacity: 0; + width: 0; + height: 0; +} + .checkbox_slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} + .checkbox_slider:before { + position: absolute; + content: ""; + height: 13px; + width: 13px; + left: 2px; + bottom: 2px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + input:checked+.checkbox_slider { + background-color: #797979; +} + input:focus+.checkbox_slider { + box-shadow: 0 0 1px #797979; +} + input:checked+.checkbox_slider:before { + -webkit-transform: translateX(13px); + -ms-transform: translateX(13px); + transform: translateX(13px); +} + .checkbox_slider.round { + border-radius: 13px; +} + .checkbox_slider.round:before { + border-radius: 50%; +} +/* BUTTON */ + .btn { + width: 306px; + height: 30px; + text-align: center; + font: normal normal bold 14px/5px sans-serif; + color: #000000; + background-color: white; + border-radius: 5px; + border: 1px solid #343a40; +} + .btn:hover { + background-color: #FA6831; + color: white; +} +/* BOTTON table */ + #botton { + width: 100%; + text-align: center; + background: #F5F5F5 0% 0% no-repeat padding-box; + opacity: 1; + bottom: 0; +} +/* ----- styles config ----- */ + .pc1 { + text-align: right; + font: normal normal normal 11px/5px sans-serif; + letter-spacing: 0px; + color: #797979; + opacity: 1; +} + .pc2 { + text-align: left; + font: normal normal normal 12px/5px sans-serif; + letter-spacing: 0px; + color: #000000; + opacity: 1; +} + .pc3 { + text-align: right; + font: normal normal normal bold 12px/17px sans-serif; + letter-spacing: 0px; + color: #2A2A2A; + opacity: 1; +} +/* data table */ + #data { + font-family: Arial, Helvetica, sans-serif; + border-collapse: collapse; + width: 100%; + table-layout: fixed; +} + #data td, #data th { + padding: 8px; +} + #data th { + padding-top: 12px; + padding-bottom: 12px; + text-align: left; +} +/* BUTTON */ + .btn_save { + width: 69px; + height: 24px; + text-align: center; + font: normal normal bold 14px/5px sans-serif; + color: #000000; + background-color: white; + border-radius: 5px; + border: 1px solid #343a40; +} + .btn_save:hover { + background-color: #FA6831; + color: white; +} +/* RANGE */ + .slider { + -webkit-appearance: none; + width: 133px; + height: 10px; + border-radius: 5px; + background: linear-gradient(to right, #d3d3d3 50%, #FA6831 50%); + outline: none; +} + .slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: #FA6831; + cursor: pointer; +} + .slider::-moz-range-thumb { + width: 16px; + height: 16px; + border-radius: 50%; + background: #FA6831; + cursor: pointer; +} +/* ----- styles wifi ----- */ + .w1 { + text-align: left; + font: normal normal bold 12px/17px sans-serif; + letter-spacing: 0px; + color: #2A2A2A; + opacity: 1; +} + .w2 { + font: normal normal normal 11px/5px sans-serif; + letter-spacing: 0px; + color: #797979; + opacity: 1; +} + .w2 span { + vertical-align: middle; +} + #center_tb { + border-collapse: collapse; + width: 100%; + table-layout: fixed; + text-align: left; +} +/* wifi_ntw table */ + #wifi_ntw { + font: normal normal normal 12px/5px sans-serif; + border-collapse: collapse; + width: 100%; + table-layout: fixed; + text-align: left; +} + #wifi_ntw td { + border-bottom: 1px solid #ddd; + padding: 8px; +} + #wifi_ntw tr:nth-child(even) { + background: #F8F8F8 0% 0% no-repeat padding-box; +} + #wifi_ntw tr:hover { + background-color: #ddd; +} + #wifi_ntw th { + background-color: transparent; + text-align: left; + font: normal normal bold 13px/11px sans-serif; + letter-spacing: 0px; + color: #2A2A2A; + opacity: 1; +} + #wifi_ntw tr { + border-bottom: 1px solid #ccc; +} + #wifi_ntw img { + width: 19px; + height: 12px; +} +/* BUTTON */ + .btn_save_w { + width: 178px; + height: 24px; + text-align: center; + font: normal normal bold 14px/5px sans-serif; + color: #000000; + background-color: white; + border-radius: 5px; + border: 1px solid #343a40; +} + .btn_save_w:hover { + background-color: #FA6831; + color: white; +} +/* ----- styles auth ----- */ + .pa1 { + text-align: right; + font: normal normal normal 11px/5px sans-serif; + letter-spacing: 0px; + color: #797979; + opacity: 1; +} + .pa2 { + text-align: left; + font: normal normal bold 12px/17px sans-serif; + letter-spacing: 0px; + color: #2A2A2A; + opacity: 1; +} +.pa3 { + text-align: right; + font: normal normal normal bold 12px/17px sans-serif; + letter-spacing: 0px; + color: #2A2A2A; + opacity: 1; +} + +/* BUTTON */ + .btn_save_a { + width: 178px; + height: 24px; + text-align: center; + font: normal normal bold 14px/5px sans-serif; + color: #000000; + background-color: white; + border-radius: 5px; + border: 1px solid #343a40; +} + .btn_save_a:hover { + background-color: #FA6831; + color: white; +} + .toggle-password { + position: relative; + cursor: pointer; +} + .toggle-password img { + position: absolute; + top: 50%; + right: 0; + transform: translateY(-50%); +} + .password-container { + display: flex; + align-items: center; +} + #auth_password.reveal { + -webkit-text-security: none; +} + #auth_password { + -webkit-text-security: disc; +} +/* ----- styles system ----- */ + .ps1 { + text-align: right; + font: normal normal normal 11px/5px sans-serif; + letter-spacing: 0px; + color: #797979; + opacity: 1; +} + .ps2 { + text-align: left; + font: normal normal normal 12px/5px sans-serif; + letter-spacing: 0px; + color: #000000; + opacity: 1; +} +.ps3 { + text-align: right; + font: normal normal normal bold 12px/17px sans-serif; + letter-spacing: 0px; + color: #2A2A2A; + opacity: 1; +} +/* data table */ + #data { + font-family: Arial, Helvetica, sans-serif; + border-collapse: collapse; + width: 100%; + table-layout: fixed; +} + #data td, #data th { + padding: 8px; +} + #data th { + padding-top: 12px; + padding-bottom: 12px; + text-align: left; +} +/* update table */ + update { + font-family: Arial, Helvetica, sans-serif; + border-collapse: collapse; + width: 100%; + table-layout: fixed; +} + #update td, #update th { + padding: 8px; +} + #update th { + padding-top: 12px; + padding-bottom: 12px; + text-align: left; +} +/* BUTTON */ + .btn_update { + width: 178px; + height: 24px; + text-align: center; + font: normal normal bold 14px/5px sans-serif; + color: #000000; + background-color: white; + border-radius: 5px; + border: 1px solid #343a40; +} + .btn_update:hover { + background-color: #FA6831; + color: white; +} + .underlined-text { + text-decoration: underline; + color: blue; + cursor: pointer; +} +/* progress bar*/ + .progress-container { + width: 100%; + background-color: #ccc; +} + .progress-bar { + width: 0px; + height: 15px; + background-color: #FA6831; + text-align: center; + line-height: 15px; + color: white; +} + +/* advanced cam cfg */ +.content { + display: none; +} + +.btn_collapsible { + width: 300px; + height: 24px; + text-align: center; + font: normal normal bold 14px/5px sans-serif; + color: #000000; + background-color: white; + border-radius: 5px; + border: 1px solid #343a40; +} +.btn_collapsible:hover { + background-color: #FA6831; + color: white; +} diff --git a/webpage/webpage_h_generator.sh b/webpage/webpage_h_generator.sh new file mode 100644 index 0000000..8e4f1d9 --- /dev/null +++ b/webpage/webpage_h_generator.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +original_webpage_path=../ESP32_PrusaConnectCam/WebPage.h +new_webpage_path=./WebPage.h + +echo "Starting the WebPage.h generator" + +echo "Copying the original WebPage.h file to the new location" +cp ${original_webpage_path} ${new_webpage_path} + +# Read the index.html file and generate the WebPage.h file +echo "Generating index.html" +html_content=$(cat index.html | awk '{printf "%s\\n", $0}') +awk -v var="$html_content" '/^const char index_html\[\] PROGMEM = R"rawliteral\(/,/rawliteral";/ { if (/^const char index_html\[\] PROGMEM = R"rawliteral\(/) { print "const char index_html[] PROGMEM = R\"rawliteral(\n" var ")rawliteral\";"; next } { next } } 1' WebPage.h > temp && mv temp WebPage.h + +# Read the page_auth.html file and generate the WebPage.h file +echo "Generating page_auth.html" +html_content=$(cat page_auth.html | awk '{printf "%s\\n", $0}') +awk -v var="$html_content" '/^const char page_auth_html\[\] PROGMEM = R"rawliteral\(/,/rawliteral";/ { if (/^const char page_auth_html\[\] PROGMEM = R"rawliteral\(/) { print "const char page_auth_html[] PROGMEM = R\"rawliteral(\n" var ")rawliteral\";"; next } { next } } 1' WebPage.h > temp && mv temp WebPage.h + +# Read the page_wifi.html file and generate the WebPage.h file +echo "Generating page_wifi.html" +html_content=$(cat page_wifi.html | awk '{printf "%s\\n", $0}') +awk -v var="$html_content" '/^const char page_wifi_html\[\] PROGMEM = R"rawliteral\(/,/rawliteral";/ { if (/^const char page_wifi_html\[\] PROGMEM = R"rawliteral\(/) { print "const char page_wifi_html[] PROGMEM = R\"rawliteral(\n" var ")rawliteral\";"; next } { next } } 1' WebPage.h > temp && mv temp WebPage.h + +# Read the page_config.html file and generate the WebPage.h file +echo "Generating page_config.html" +html_content=$(cat page_config.html | awk '{printf "%s\\n", $0}') +awk -v var="$html_content" '/^const char page_config_html\[\] PROGMEM = R"rawliteral\(/,/rawliteral";/ { if (/^const char page_config_html\[\] PROGMEM = R"rawliteral\(/) { print "const char page_config_html[] PROGMEM = R\"rawliteral(\n" var ")rawliteral\";"; next } { next } } 1' WebPage.h > temp && mv temp WebPage.h + +# Read the page_system.html file and generate the WebPage.h file +echo "Generating page_system.html" +html_content=$(cat page_system.html | awk '{printf "%s\\n", $0}') +awk -v var="$html_content" '/^const char page_system_html\[\] PROGMEM = R"rawliteral\(/,/rawliteral";/ { if (/^const char page_system_html\[\] PROGMEM = R"rawliteral\(/) { print "const char page_system_html[] PROGMEM = R\"rawliteral(\n" var ")rawliteral\";"; next } { next } } 1' WebPage.h > temp && mv temp WebPage.h + +# Read the syles.css file and generate the WebPage.h file +echo "Generating styles.css" +html_content=$(cat styles.css | awk '{printf "%s\\n", $0}') +awk -v var="$html_content" '/^const char styles_css\[\] PROGMEM = R"rawliteral\(/,/rawliteral";/ { if (/^const char styles_css\[\] PROGMEM = R"rawliteral\(/) { print "const char styles_css[] PROGMEM = R\"rawliteral(\n" var ")rawliteral\";"; next } { next } } 1' WebPage.h > temp && mv temp WebPage.h + +# Read the scripts.js file and generate the WebPage.h file +echo "Generating scripts.js" +html_content=$(cat scripts.js | awk '{printf "%s\\n", $0}') +awk -v var="$html_content" '/^const char scripts_js\[\] PROGMEM = R"rawliteral\(/,/rawliteral";/ { if (/^const char scripts_js\[\] PROGMEM = R"rawliteral\(/) { print "const char scripts_js[] PROGMEM = R\"rawliteral(\n" var ")rawliteral\";"; next } { next } } 1' WebPage.h > temp && mv temp WebPage.h + +# Read the license.html file and generate the WebPage.h file +echo "Generating license.html" +html_content=$(cat license.html | awk '{printf "%s\\n", $0}') +awk -v var="$html_content" '/^const char license_html\[\] PROGMEM = R"rawliteral\(/,/rawliteral";/ { if (/^const char license_html\[\] PROGMEM = R"rawliteral\(/) { print "const char license_html[] PROGMEM = R\"rawliteral(\n" var ")rawliteral\";"; next } { next } } 1' WebPage.h > temp && mv temp WebPage.h + +# Read the gtac.html file and generate the WebPage.h file +echo "Generating gtac.html" +html_content=$(cat gtac.html | awk '{printf "%s\\n", $0}') +awk -v var="$html_content" '/^const char gtac_html\[\] PROGMEM = R"rawliteral\(/,/rawliteral";/ { if (/^const char gtac_html\[\] PROGMEM = R"rawliteral\(/) { print "const char gtac_html[] PROGMEM = R\"rawliteral(\n" var ")rawliteral\";"; next } { next } } 1' WebPage.h > temp && mv temp WebPage.h + +# Read the privacypolicy.html file and generate the WebPage.h file +echo "Generating privacypolicy.html" +html_content=$(cat privacypolicy.html | awk '{printf "%s\\n", $0}') +awk -v var="$html_content" '/^const char privacypolicy_html\[\] PROGMEM = R"rawliteral\(/,/rawliteral";/ { if (/^const char privacypolicy_html\[\] PROGMEM = R"rawliteral\(/) { print "const char privacypolicy_html[] PROGMEM = R\"rawliteral(\n" var ")rawliteral\";"; next } { next } } 1' WebPage.h > temp && mv temp WebPage.h + +# Read the cookies.html file and generate the WebPage.h file +echo "Generating cookies.html" +html_content=$(cat cookies.html | awk '{printf "%s\\n", $0}') +awk -v var="$html_content" '/^const char cookies_html\[\] PROGMEM = R"rawliteral\(/,/rawliteral";/ { if (/^const char cookies_html\[\] PROGMEM = R"rawliteral\(/) { print "const char cookies_html[] PROGMEM = R\"rawliteral(\n" var ")rawliteral\";"; next } { next } } 1' WebPage.h > temp && mv temp WebPage.h + +# Copy the generated WebPage.h file to the ESP32 project +echo "WebPage.h generated successfully" +echo "Copy the new WebPage.h file to the ESP32 project" +cp ${new_webpage_path} ${original_webpage_path} +rm -f ${new_webpage_path} + +exit 0 \ No newline at end of file