Created
October 19, 2025 17:05
-
-
Save IgorDePaula/437c03631061f4c7bcac46d928ca64fb to your computer and use it in GitHub Desktop.
update firmware via aws
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * ESP32 AWS IoT Jobs OTA - Arduino IDE | |
| * | |
| * Bibliotecas necessárias (instalar via Library Manager): | |
| * - ArduinoJson by Benoit Blanchon | |
| * | |
| * Board: ESP32 Dev Module | |
| * Partition Scheme: Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS) | |
| */ | |
| #include <WiFi.h> | |
| #include <WiFiClientSecure.h> | |
| #include <PubSubClient.h> | |
| #include <HTTPClient.h> | |
| #include <Update.h> | |
| #include <ArduinoJson.h> | |
| // ===== CONFIGURAÇÕES - EDITAR AQUI ===== | |
| #define WIFI_SSID "" | |
| #define WIFI_PASSWORD "" | |
| #define AWS_IOT_ENDPOINT "xxxx-ats.iot.us-east-1.amazonaws.com" | |
| #define AWS_IOT_THING_NAME "Esp32Teste" | |
| #define FIRMWARE_VERSION "1.0.0" | |
| //String("esp32-") + String((uint64_t)ESP.getEfuseMac(), HEX); | |
| // ===== CERTIFICADOS ===== | |
| // Cole seus certificados aqui (veja instruções no final) | |
| const char AWS_CERT_CA[] PROGMEM = R"EOF( | |
| -----BEGIN CERTIFICATE----- | |
| -----END CERTIFICATE----- | |
| )EOF"; | |
| const char AWS_CERT_CRT[] PROGMEM = R"KEY( | |
| -----BEGIN CERTIFICATE----- | |
| -----END CERTIFICATE----- | |
| )KEY"; | |
| const char AWS_CERT_PRIVATE[] PROGMEM = R"KEY( | |
| -----BEGIN RSA PRIVATE KEY----- | |
| -----END RSA PRIVATE KEY----- | |
| )KEY"; | |
| // ===== VARIÁVEIS GLOBAIS ===== | |
| WiFiClientSecure net; | |
| PubSubClient mqtt(net); | |
| char currentJobId[64] = {0}; | |
| bool otaInProgress = false; | |
| // ===== TÓPICOS MQTT ===== | |
| char TOPIC_NOTIFY[128]; | |
| char TOPIC_GET[128]; | |
| // ===== FUNÇÕES ===== | |
| void setupWiFi() { | |
| Serial.print("Conectando WiFi"); | |
| WiFi.mode(WIFI_STA); | |
| WiFi.begin(WIFI_SSID, WIFI_PASSWORD); | |
| while (WiFi.status() != WL_CONNECTED) { | |
| delay(500); | |
| Serial.print("."); | |
| } | |
| Serial.println("\nWiFi conectado!"); | |
| Serial.print("IP: "); | |
| Serial.println(WiFi.localIP()); | |
| } | |
| void updateJobStatus(const char* jobId, const char* status, const char* details = "") { | |
| char topic[256]; | |
| snprintf(topic, sizeof(topic), "$aws/things/%s/jobs/%s/update", | |
| AWS_IOT_THING_NAME, jobId); | |
| StaticJsonDocument<512> doc; | |
| doc["status"] = status; | |
| doc["statusDetails"]["message"] = details; | |
| doc["statusDetails"]["version"] = FIRMWARE_VERSION; | |
| char payload[512]; | |
| serializeJson(doc, payload); | |
| mqtt.publish(topic, payload); | |
| Serial.printf("Job status: %s\n", status); | |
| } | |
| bool performOTA(const char* url) { | |
| Serial.printf("Iniciando OTA de: %s\n", url); | |
| otaInProgress = true; | |
| HTTPClient http; | |
| http.begin(url); | |
| int httpCode = http.GET(); | |
| if (httpCode != HTTP_CODE_OK) { | |
| Serial.printf("HTTP GET falhou: %d\n", httpCode); | |
| http.end(); | |
| otaInProgress = false; | |
| return false; | |
| } | |
| int contentLength = http.getSize(); | |
| Serial.printf("Tamanho: %d bytes\n", contentLength); | |
| if (contentLength <= 0) { | |
| Serial.println("Tamanho inválido"); | |
| http.end(); | |
| otaInProgress = false; | |
| return false; | |
| } | |
| bool canBegin = Update.begin(contentLength); | |
| if (!canBegin) { | |
| Serial.println("Não há espaço para OTA"); | |
| http.end(); | |
| otaInProgress = false; | |
| return false; | |
| } | |
| WiFiClient* stream = http.getStreamPtr(); | |
| size_t written = 0; | |
| uint8_t buff[1024]; | |
| int lastProgress = -1; | |
| while (http.connected() && (written < contentLength)) { | |
| size_t available = stream->available(); | |
| if (available) { | |
| int bytesRead = stream->readBytes(buff, min(available, sizeof(buff))); | |
| size_t bytesWritten = Update.write(buff, bytesRead); | |
| if (bytesWritten != bytesRead) { | |
| Serial.println("Erro ao escrever firmware"); | |
| Update.abort(); | |
| http.end(); | |
| otaInProgress = false; | |
| return false; | |
| } | |
| written += bytesWritten; | |
| int progress = (written * 100) / contentLength; | |
| if (progress != lastProgress && progress % 10 == 0) { | |
| Serial.printf("Download: %d%%\n", progress); | |
| char details[64]; | |
| snprintf(details, sizeof(details), "Download %d%%", progress); | |
| updateJobStatus(currentJobId, "IN_PROGRESS", details); | |
| lastProgress = progress; | |
| } | |
| // Manter MQTT ativo | |
| mqtt.loop(); | |
| } | |
| delay(1); | |
| } | |
| http.end(); | |
| if (written != contentLength) { | |
| Serial.printf("Download incompleto: %d de %d bytes\n", written, contentLength); | |
| Update.abort(); | |
| otaInProgress = false; | |
| return false; | |
| } | |
| if (Update.end()) { | |
| Serial.println("OTA concluído!"); | |
| if (Update.isFinished()) { | |
| Serial.println("Update finalizado com sucesso!"); | |
| otaInProgress = false; | |
| return true; | |
| } else { | |
| Serial.println("Update não finalizado"); | |
| otaInProgress = false; | |
| return false; | |
| } | |
| } else { | |
| Serial.printf("Erro no Update: %d\n", Update.getError()); | |
| otaInProgress = false; | |
| return false; | |
| } | |
| } | |
| void processJob(const char* payload) { | |
| StaticJsonDocument<2048> doc; | |
| DeserializationError error = deserializeJson(doc, payload); | |
| if (error) { | |
| Serial.print("Erro ao parsear JSON: "); | |
| Serial.println(error.c_str()); | |
| return; | |
| } | |
| if (!doc.containsKey("execution")) { | |
| Serial.println("JSON sem 'execution'"); | |
| return; | |
| } | |
| const char* jobId = doc["execution"]["jobId"]; | |
| JsonObject jobDocument = doc["execution"]["jobDocument"]; | |
| if (!jobId || jobDocument.isNull()) { | |
| Serial.println("Job inválido"); | |
| return; | |
| } | |
| strncpy(currentJobId, jobId, sizeof(currentJobId) - 1); | |
| Serial.printf("Job ID: %s\n", jobId); | |
| if (!jobDocument.containsKey("files") || !jobDocument["files"].is<JsonArray>()) { | |
| Serial.println("Job sem 'files'"); | |
| updateJobStatus(jobId, "FAILED", "Job document invalido"); | |
| return; | |
| } | |
| JsonArray files = jobDocument["files"]; | |
| if (files.size() == 0) { | |
| Serial.println("Array 'files' vazio"); | |
| updateJobStatus(jobId, "FAILED", "Nenhum arquivo especificado"); | |
| return; | |
| } | |
| const char* url = files[0]["fileSource"]["url"]; | |
| if (!url) { | |
| Serial.println("URL não encontrada"); | |
| updateJobStatus(jobId, "FAILED", "URL ausente"); | |
| return; | |
| } | |
| Serial.printf("URL: %s\n", url); | |
| updateJobStatus(jobId, "IN_PROGRESS", "Iniciando OTA"); | |
| bool success = performOTA(url); | |
| if (success) { | |
| updateJobStatus(jobId, "SUCCEEDED", "OTA completo"); | |
| delay(2000); | |
| Serial.println("Reiniciando..."); | |
| ESP.restart(); | |
| } else { | |
| updateJobStatus(jobId, "FAILED", "OTA falhou"); | |
| } | |
| } | |
| void mqttCallback(char* topic, byte* payload, unsigned int length) { | |
| Serial.printf("Mensagem recebida em: %s\n", topic); | |
| char message[length + 1]; | |
| memcpy(message, payload, length); | |
| message[length] = '\0'; | |
| Serial.println("Payload:"); | |
| Serial.println(message); | |
| processJob(message); | |
| } | |
| void connectAWS() { | |
| // Configurar certificados | |
| net.setCACert(AWS_CERT_CA); | |
| net.setCertificate(AWS_CERT_CRT); | |
| net.setPrivateKey(AWS_CERT_PRIVATE); | |
| mqtt.setServer(AWS_IOT_ENDPOINT, 8883); | |
| mqtt.setCallback(mqttCallback); | |
| mqtt.setBufferSize(2048); // Aumentar buffer para jobs grandes | |
| Serial.print("Conectando AWS IoT"); | |
| while (!mqtt.connected()) { | |
| if (mqtt.connect(AWS_IOT_THING_NAME)) { | |
| Serial.println("\nAWS IoT conectado!"); | |
| // Subscrever ao tópico de notificação de jobs | |
| snprintf(TOPIC_NOTIFY, sizeof(TOPIC_NOTIFY), | |
| "$aws/things/%s/jobs/notify-next", AWS_IOT_THING_NAME); | |
| mqtt.subscribe(TOPIC_NOTIFY); | |
| Serial.printf("Subscrito: %s\n", TOPIC_NOTIFY); | |
| // Solicitar job pendente | |
| snprintf(TOPIC_GET, sizeof(TOPIC_GET), | |
| "$aws/things/%s/jobs/$next/get", AWS_IOT_THING_NAME); | |
| mqtt.publish(TOPIC_GET, "{}"); | |
| Serial.println("Solicitando jobs pendentes..."); | |
| } else { | |
| Serial.print("."); | |
| delay(5000); | |
| } | |
| } | |
| } | |
| void setup() { | |
| Serial.begin(115200); | |
| delay(1000); | |
| Serial.println("\n=== AWS IoT Jobs OTA Demo ==="); | |
| Serial.printf("Firmware version: %s\n", FIRMWARE_VERSION); | |
| Serial.printf("Thing Name: %s\n", AWS_IOT_THING_NAME); | |
| setupWiFi(); | |
| connectAWS(); | |
| Serial.println("Sistema pronto! Aguardando jobs..."); | |
| } | |
| void loop() { | |
| if (!mqtt.connected() && !otaInProgress) { | |
| connectAWS(); | |
| } | |
| if (!otaInProgress) { | |
| mqtt.loop(); | |
| } | |
| delay(10); | |
| } | |
| /* | |
| * ===== INSTRUÇÕES DE USO ===== | |
| * | |
| * 1. INSTALAR ESP32 BOARD: | |
| * - File → Preferences → Additional Board Manager URLs: | |
| * https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json | |
| * - Tools → Board → Boards Manager → Pesquisar "esp32" → Install | |
| * | |
| * 2. INSTALAR BIBLIOTECA: | |
| * - Sketch → Include Library → Manage Libraries | |
| * - Pesquisar "ArduinoJson" → Install (versão 6.x) | |
| * | |
| * 3. CONFIGURAR BOARD: | |
| * - Tools → Board → ESP32 Arduino → ESP32 Dev Module | |
| * - Tools → Partition Scheme → Minimal SPIFFS (1.9MB APP with OTA) | |
| * - Tools → Upload Speed → 921600 | |
| * - Tools → Flash Size → 4MB (32Mb) | |
| * | |
| * 4. OBTER CERTIFICADOS: | |
| * a) Root CA: | |
| * - Baixar de: https://www.amazontrust.com/repository/AmazonRootCA1.pem | |
| * | |
| * b) Certificados do Dispositivo: | |
| * - AWS Console → IoT Core → All devices → Things → Create thing | |
| * - Baixar: certificate.pem.crt e private.pem.key | |
| * | |
| * c) Colar certificados nas constantes acima (AWS_CERT_CA, AWS_CERT_CRT, AWS_CERT_PRIVATE) | |
| * - Manter formato: R"EOF( ... )EOF" ou R"KEY( ... )KEY" | |
| * - Incluir as linhas -----BEGIN----- e -----END----- | |
| * | |
| * 5. EDITAR CONFIGURAÇÕES: | |
| * - WIFI_SSID e WIFI_PASSWORD | |
| * - AWS_IOT_ENDPOINT (encontrar em: AWS IoT Console → Settings) | |
| * - AWS_IOT_THING_NAME (nome do seu Thing) | |
| * - FIRMWARE_VERSION (incrementar a cada versão) | |
| * | |
| * 6. PRIMEIRA COMPILAÇÃO E UPLOAD: | |
| * - Sketch → Upload | |
| * - Tools → Serial Monitor (115200 baud) | |
| * | |
| * 7. CRIAR JOB NO AWS: | |
| * a) Upload firmware compilado para S3: | |
| * - Localizar .bin em: C:\Users\SeuUsuario\AppData\Local\Temp\arduino_build_xxxxx\ | |
| * - Nome do arquivo: [nome_do_sketch].ino.bin | |
| * - Upload para S3 via console AWS | |
| * | |
| * b) Criar Job Document (job-document.json): | |
| * { | |
| * "operation": "firmware_update", | |
| * "version": "1.1.0", | |
| * "files": [{ | |
| * "fileName": "firmware.bin", | |
| * "fileSource": { | |
| * "url": "https://seu-bucket.s3.amazonaws.com/firmware.bin" | |
| * } | |
| * }] | |
| * } | |
| * | |
| * c) Criar Job: | |
| * - AWS IoT Console → Manage → Jobs → Create | |
| * - Target: Selecionar seu Thing | |
| * - Cole o Job Document | |
| * | |
| * 8. MONITORAR: | |
| * - Abrir Serial Monitor | |
| * - Ver progresso do download | |
| * - Aguardar reinicialização automática | |
| * | |
| * ===== TROUBLESHOOTING ===== | |
| * | |
| * Erro "Sketch too big": | |
| * → Tools → Partition Scheme → Minimal SPIFFS (1.9MB APP with OTA) | |
| * | |
| * Erro "Connection refused": | |
| * → Verificar certificados | |
| * → Verificar Thing Name | |
| * → Verificar Policy anexada ao certificado | |
| * | |
| * Job não chega: | |
| * → Verificar Job targetted para o Thing correto | |
| * → Verificar subscrição aos tópicos | |
| * → Verificar no Serial Monitor se conectou ao AWS IoT | |
| * | |
| * OTA falha: | |
| * → URL do S3 deve ser pública ou presigned | |
| * → Verificar tamanho do firmware (< 1.9MB com partição padrão) | |
| * → Verificar se a partição OTA está configurada | |
| */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment