From d81cfeba7ab72f7df71458ccbfb3286e29aa681a Mon Sep 17 00:00:00 2001 From: Matheus Nespolo Date: Fri, 2 Oct 2026 20:48:13 -0300 Subject: [PATCH 1/2] fix(firmware): reaplica timeout 12,5 s, passagem e estoque 15; corrige LCD em ERRO, publicacao escalonada e status das esteiras Co-Authored-By: Claude Sonnet 5.5 --- README.md | 2 +- .../data_flow_inventory.ino | 80 +++++++++++++------ docs/CHANGELOG.md | 9 +++ docs/testes/plano_de_testes.md | 4 +- 4 files changed, 67 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 42e6ea1..a993354 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,7 @@ O Arduino opera com **5 estados**: | 1 | **AGUARDANDO_PEDIDO** | Sistema em repouso, monitora botões e comandos remotos | | 2 | **VERIFICANDO_ESTOQUE** | Verifica sensor do topo + contador interno | | 3 | **ACIONANDO_ESTEIRA** | Liga motor da esteira secundária correspondente | -| 4 | **ENTREGANDO_PECA** | Monitora sensor da junção com timeout de 9s | +| 4 | **ENTREGANDO_PECA** | Monitora o sensor da junção (timeout de 12,5 s); só debita o estoque após a confirmação e mantém o motor 3 s depois dela | | 5 | **ERRO** | Sinaliza falha no LCD, aguarda reset manual | > 📊 Diagramas completos (máquina de estados, fluxo operacional e sequência de comunicação) em [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md#3-máquina-de-estados-arduino). diff --git a/arduino/data_flow_inventory/data_flow_inventory.ino b/arduino/data_flow_inventory/data_flow_inventory.ino index d49d285..5fc6181 100644 --- a/arduino/data_flow_inventory/data_flow_inventory.ino +++ b/arduino/data_flow_inventory/data_flow_inventory.ino @@ -8,6 +8,8 @@ // Máquina de estados com 5 etapas: // AGUARDANDO_PEDIDO → VERIFICANDO_ESTOQUE → ACIONANDO_ESTEIRA // → ENTREGANDO_PECA → ERRO +// Entrega: a peça só é debitada do estoque depois que o sensor de junção da +// esteira confirma a passagem; sem confirmação em TIMEOUT_ENTREGA → ERRO. // 3 esteiras secundárias (A, B, C) alimentam a esteira principal. // Roda de estoque com 3 compartimentos ao final da principal. // @@ -89,10 +91,15 @@ LiquidCrystal_I2C lcd(0x27, 16, 2); #define VELOCIDADE_ESTEIRA_A VELOCIDADE_SECUNDARIA #define VELOCIDADE_ESTEIRA_B VELOCIDADE_SECUNDARIA #define VELOCIDADE_ESTEIRA_C 140 -#define TIMEOUT_ENTREGA 9000 // ms (medição 25/08: peça leva ~5s até o sensor, - // mas precisa de folga extra para sair da esteira secundária) +#define TIMEOUT_ENTREGA 12500 // ms — tempo máximo para a peça sair do topo e ser + // CONFIRMADA pelo sensor de junção (medição 25/08: ~5 s + // até o sensor; folga ampla). NÃO inclui os + // TEMPO_SAIDA_ESTEIRA_MS de saída, que contam depois da + // confirmação. Estourou → ERRO e motor desligado. #define DEBOUNCE_BTN 200 // ms -#define INTERVALO_PUBLICACAO 1000 // ms — intervalo para publicar status periódico +#define INTERVALO_PUBLICACAO 1000 // ms — ciclo completo: cada mensagem periódica sai 1x por ciclo +#define PASSOS_PUBLICACAO 4 // mensagens por ciclo (status, estoque, sensores, esteiras), uma por vez +#define ESTOQUE_INICIAL 15 // peças de cada tipo ao ligar (o LCD 16x2 comporta até 99 por peça) // ============================================================ // ESTADOS DA MÁQUINA DE ESTADOS @@ -118,10 +125,11 @@ const char* nomesEstados[] = { // ============================================================ Estado estadoAtual = AGUARDANDO_PEDIDO; int pecaSolicitada = 0; // 1 = A, 2 = B, 3 = C -int estoque[4] = {0, 5, 5, 5}; // índice 1=A, 2=B, 3=C +int estoque[4] = {0, ESTOQUE_INICIAL, ESTOQUE_INICIAL, ESTOQUE_INICIAL}; // índice 1=A, 2=B, 3=C unsigned long tempoInicio = 0; unsigned long ultimoDebounce = 0; unsigned long ultimaPublicacao = 0; +uint8_t passoPublicacao = 0; // qual mensagem periódica sai na próxima vez bool erroTimeout = false; bool erroSemEstoque = false; @@ -139,6 +147,12 @@ const unsigned long TEMPO_SAIDA_ESTEIRA_MS = 3000; bool saindoDaEsteira = false; unsigned long tempoSaidaIniciada = 0; +// O estoque só é debitado com a peça CONFIRMADA pelo sensor de junção, e a +// confirmação exige uma passagem: o sensor precisa ser visto livre (HIGH) depois +// que a esteira liga. Um sensor que já está em LOW na partida (peça parada sobre +// ele, desalinhamento) não confirma nada sozinho — o pedido termina em timeout. +bool juncaoLiberada = false; + // Pausa pós-entrega sem bloquear o loop (substitui o antigo delay(1500)). bool aguardandoLimpezaEntrega = false; unsigned long tempoLimpezaEntrega = 0; @@ -271,7 +285,6 @@ void publicarEstado() { doc["uptime"] = millis() / 1000; serializeJson(doc, Serial); Serial.println(); - Serial.flush(); } void publicarEstoque() { @@ -282,7 +295,6 @@ void publicarEstoque() { doc["pecaC"] = estoque[3]; serializeJson(doc, Serial); Serial.println(); - Serial.flush(); } void publicarSensores() { @@ -333,16 +345,21 @@ void publicarPedido(char peca) { Serial.println(); } +// Esteira secundária realmente ligada: na pausa pós-entrega (Fase C) o motor +// já está parado, então o dashboard não deve mostrá-la como ligada. +bool esteiraLigada(int peca) { + if (pecaSolicitada != peca) return false; + return estadoAtual == ACIONANDO_ESTEIRA || + (estadoAtual == ENTREGANDO_PECA && !aguardandoLimpezaEntrega); +} + void publicarStatusEsteiras() { StaticJsonDocument<200> doc; doc["type"] = "esteiras"; doc["principal"] = 1; // sempre ligada - doc["secA"] = (estadoAtual == ACIONANDO_ESTEIRA && pecaSolicitada == 1) || - (estadoAtual == ENTREGANDO_PECA && pecaSolicitada == 1) ? 1 : 0; - doc["secB"] = (estadoAtual == ACIONANDO_ESTEIRA && pecaSolicitada == 2) || - (estadoAtual == ENTREGANDO_PECA && pecaSolicitada == 2) ? 1 : 0; - doc["secC"] = (estadoAtual == ACIONANDO_ESTEIRA && pecaSolicitada == 3) || - (estadoAtual == ENTREGANDO_PECA && pecaSolicitada == 3) ? 1 : 0; + doc["secA"] = esteiraLigada(1) ? 1 : 0; + doc["secB"] = esteiraLigada(2) ? 1 : 0; + doc["secC"] = esteiraLigada(3) ? 1 : 0; serializeJson(doc, Serial); Serial.println(); } @@ -386,6 +403,7 @@ void interpretarComando(String linha) { erroTimeout = false; erroSemEstoque = false; saindoDaEsteira = false; + juncaoLiberada = false; aguardandoLimpezaEntrega = false; pecaSolicitada = 0; pararTodasSecundarias(); @@ -475,16 +493,24 @@ void loop() { // Processa comandos recebidos do ESP32 processarComando(); - // Publicação periódica de status. + // Publicação periódica de status, ESCALONADA: uma mensagem por vez, a cada + // INTERVALO_PUBLICACAO / PASSOS_PUBLICACAO ms, de modo que cada mensagem + // continua saindo 1x por INTERVALO_PUBLICACAO. A Serial do Uno (9600 baud, + // buffer de 64 bytes) bloqueia o loop enquanto esvazia o buffer; enviando as + // 4 mensagens de uma vez o loop ficava preso por centenas de ms por segundo e + // um pulso curto do sensor de junção podia passar despercebido (falso timeout). // Inclui o estoque: no trecho UART Arduino→ESP32 as mensagens são QoS 0; // republicar a cada ciclo reconcilia o dashboard caso o pacote do boot // ou de uma entrega se perca. - if (millis() - ultimaPublicacao >= INTERVALO_PUBLICACAO) { + if (millis() - ultimaPublicacao >= INTERVALO_PUBLICACAO / PASSOS_PUBLICACAO) { ultimaPublicacao = millis(); - publicarEstado(); - publicarEstoque(); - publicarSensores(); - publicarStatusEsteiras(); + switch (passoPublicacao) { + case 0: publicarEstado(); break; + case 1: publicarEstoque(); break; + case 2: publicarSensores(); break; + default: publicarStatusEsteiras(); break; + } + passoPublicacao = (passoPublicacao + 1) % PASSOS_PUBLICACAO; } switch (estadoAtual) { @@ -541,6 +567,7 @@ void loop() { if (pecaSolicitada == 3) ligarEsteiraC(); tempoInicio = millis(); + juncaoLiberada = false; publicarStatusEsteiras(); estadoAtual = ENTREGANDO_PECA; break; @@ -602,16 +629,21 @@ void loop() { if (pecaSolicitada == 2) pecaChegou = sensorJuncaoJ2(); if (pecaSolicitada == 3) pecaChegou = sensorJuncaoJ3(); - if (pecaChegou) { + // A confirmação exige passagem: primeiro o sensor precisa ser visto livre. + if (!pecaChegou) juncaoLiberada = true; + + if (pecaChegou && juncaoLiberada) { // NÃO para o motor ainda — inicia a fase de saída da esteira. atualizarLCD("Saindo da", "esteira..."); saindoDaEsteira = true; tempoSaidaIniciada = millis(); } else if (millis() - tempoInicio > TIMEOUT_ENTREGA) { - // Timeout — peça nunca chegou ao sensor de junção + // Timeout — peça não foi confirmada pelo sensor de junção: motor desligado, + // estoque intacto (o débito só ocorre após a confirmação, na Fase B). pararTodasSecundarias(); erroTimeout = true; + atualizarLCD("ERRO: Timeout", "Aguarde comando"); publicarErro("timeout"); estadoAtual = ERRO; } @@ -622,11 +654,9 @@ void loop() { // ESTADO 5: ERRO // ---------------------------------------------------------- case ERRO: { - if (erroTimeout) { - atualizarLCD("ERRO: Timeout", "Aguarde comando"); - } else if (erroSemEstoque) { - // Mantém mensagem de sem estoque - } + // A mensagem do LCD é desenhada UMA vez, na transição para ERRO (timeout ou + // sem estoque). Redesenhar a cada volta do loop (lcd.clear() + Strings) + // fazia o display piscar e fragmentava o heap do Uno (2 KB de RAM). // Reset via botão físico: DESABILITADO // Para reativar, descomente: diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 65100ae..67b176e 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,6 +12,15 @@ Convenção de seções: `Adicionado`, `Alterado`, `Corrigido`, `Segurança`, `R ## [Não publicado] +### Alterado + +- **Firmware Uno — revisão do código e reaplicação das atualizações de 02/10/2026** + - **Reaplicado:** `TIMEOUT_ENTREGA` de 9 s para **12,5 s** (vigia só o trajeto topo → junção; estourou, as três esteiras são desligadas e o sistema vai para `ERRO` sem debitar estoque), confirmação por **passagem** no sensor de junção (o sensor precisa ser visto livre depois que a esteira liga) e estoque inicial de **15** peças por tipo. O PWM por esteira (`VELOCIDADE_ESTEIRA_A/B/C`) foi mantido como estava (C em 140) + - **Corrigido:** o LCD era redesenhado (`lcd.clear()` e criação de `String`) a cada volta do loop enquanto o sistema estava em `ERRO` por timeout, o que fazia o display piscar e fragmentava o heap do Uno (2 KB de RAM); a mensagem agora é desenhada uma única vez, na transição para `ERRO` + - **Corrigido:** a publicação periódica enviava as 4 mensagens (status, estoque, sensores e esteiras) de uma vez, com `Serial.flush()`, o que bloqueava o loop por centenas de milissegundos a cada segundo (9600 baud) e podia fazer um pulso curto do sensor de junção passar despercebido (falso timeout). Agora as mensagens saem escalonadas, uma a cada 250 ms (cada uma continua saindo 1x por segundo) e sem `flush` + - **Corrigido:** durante a pausa de 1,5 s depois da entrega o dashboard ainda mostrava a esteira como ligada, mesmo com o motor já parado + - **Validado:** o sketch compila para o Arduino Uno (`arduino-cli`, 54% da flash e 55% da RAM). **Não validado em bancada**: o LCD apagado e a esteira C fraca (possível causa elétrica: fiação, diodo antiparalelo, driver IRF520 ou fonte) seguem em investigação e este firmware não os corrige por si só + ### Revertido - **Firmware Uno — volta ao comportamento anterior às atualizações de 02/10/2026** diff --git a/docs/testes/plano_de_testes.md b/docs/testes/plano_de_testes.md index 9b7fc6f..aef7e3b 100644 --- a/docs/testes/plano_de_testes.md +++ b/docs/testes/plano_de_testes.md @@ -360,7 +360,7 @@ Detalhes completos (pinagem, ligações e checklists): [`test/esteira_peca_b/REA - [ ] Entregas consecutivas de A e B decrementam o estoque corretamente - [ ] Peça C rejeitada com `{"evento":"erro","tipo":"peca_indisponivel"}` sem travar a FSM - [ ] Pedido com FSM ocupada gera evento `ocupado` -- [ ] **Timeout de 9 s** → `ERRO`; `CMD:RESET` recupera o sistema +- [ ] **Timeout de 12,5 s** → `ERRO`; `CMD:RESET` recupera o sistema - [ ] Status periódico (`status`, `sensores`, `esteiras`) publicado a cada ~1 s nos tópicos MQTT --- @@ -548,5 +548,5 @@ Detalhes completos (pinagem, ligações e checklists): [`test/esteira_peca_b/REA - **Telemetria Prometheus/Grafana (Card #25) — Concluída (01/10/2026):** o stack foi validado (smoke test e conferência dos painéis) e as capturas dos dashboards foram anexadas ao card. Esta observação substitui o "Não validado" e as pendências de 30/09, e o burn-in pode observar os dashboards Grafana em paralelo. -- **Firmware Uno — pendências de bancada (02/10/2026):** a compilação dos 4 sketches foi verificada com `arduino-cli` (procedimento em `docs/CI-CD.md`), mas o comportamento não foi testado em hardware. Ao voltar à bancada: (1) calibrar o PWM da esteira C (`VELOCIDADE_ESTEIRA_C`, inicial 140), medindo o tempo topo → junção, que deve ficar próximo ao de A e B (~5 s) e bem abaixo dos 9 s do timeout; (2) provocar o timeout de 9 s (sem peça) e conferir `ERRO`, motor desligado e estoque intacto; (3) executar antes o diagnóstico B/C do roteiro da Semana 7, ainda não realizado. +- **Firmware Uno — pendências de bancada (02/10/2026):** a compilação dos 4 sketches foi verificada com `arduino-cli` (procedimento em `docs/CI-CD.md`), mas o comportamento não foi testado em hardware. Ao voltar à bancada: (1) calibrar o PWM da esteira C (`VELOCIDADE_ESTEIRA_C`, inicial 140), medindo o tempo topo → junção, que deve ficar próximo ao de A e B (~5 s) e bem abaixo dos 12,5 s do timeout; (2) provocar o timeout de 12,5 s (sem peça) e conferir `ERRO`, motor desligado e estoque intacto; (3) iniciar um pedido com o sensor de junção já bloqueado e confirmar que o estoque não é debitado sem uma passagem; (4) executar antes o diagnóstico B/C do roteiro da Semana 7, ainda não realizado. From 0b41755f8a76dec1ff6e6f680dc29a85dcb2cb37 Mon Sep 17 00:00:00 2001 From: Matheus Nespolo Date: Fri, 2 Oct 2026 21:05:47 -0300 Subject: [PATCH 2/2] fix(firmware): filtro da juncao, estados publicados na ordem certa, parser estrito, buffer do ESP32 e docs alinhadas Co-Authored-By: Claude Sonnet 5.5 --- README.md | 2 +- .../data_flow_inventory.ino | 86 ++++++++++++------- docs/ARCHITECTURE.md | 20 +++-- docs/CHANGELOG.md | 7 +- docs/testes/plano_de_testes.md | 2 +- esp32/gateway_mqtt/gateway_mqtt.ino | 4 + 6 files changed, 80 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index a993354..d894fb4 100644 --- a/README.md +++ b/README.md @@ -242,7 +242,7 @@ O Arduino opera com **5 estados**: | # | Estado | Descrição | |---|--------|-----------| -| 1 | **AGUARDANDO_PEDIDO** | Sistema em repouso, monitora botões e comandos remotos | +| 1 | **AGUARDANDO_PEDIDO** | Sistema em repouso, aguarda comandos remotos (botões físicos desabilitados) | | 2 | **VERIFICANDO_ESTOQUE** | Verifica sensor do topo + contador interno | | 3 | **ACIONANDO_ESTEIRA** | Liga motor da esteira secundária correspondente | | 4 | **ENTREGANDO_PECA** | Monitora o sensor da junção (timeout de 12,5 s); só debita o estoque após a confirmação e mantém o motor 3 s depois dela | diff --git a/arduino/data_flow_inventory/data_flow_inventory.ino b/arduino/data_flow_inventory/data_flow_inventory.ino index 5fc6181..09b46b3 100644 --- a/arduino/data_flow_inventory/data_flow_inventory.ino +++ b/arduino/data_flow_inventory/data_flow_inventory.ino @@ -85,12 +85,15 @@ LiquidCrystal_I2C lcd(0x27, 16, 2); // ============================================================ #define VELOCIDADE_PRINCIPAL 180 // PWM 0-255 #define VELOCIDADE_SECUNDARIA 200 // PWM 0-255 (padrão das esteiras secundárias) -// Velocidade por esteira secundária (PWM 0-255). A e B usam o padrão; a C tem -// motor mais forte, então usa um PWM menor para igualar o tempo de entrega. -// Valor da C é ponto de partida: calibrar em bancada (depois da troca do motor). +// Velocidade por esteira secundária (PWM 0-255). Cada esteira tem a sua constante +// para poder ser calibrada sozinha. Em bancada o PWM mínimo que move a esteira +// com peça foi ~150 (docs/testes/plano_de_testes.md): NÃO fique abaixo disso sem +// medir. A C começa igual a A e B; para igualar o tempo de entrega de um motor +// diferente, calibre o valor da C em bancada. #define VELOCIDADE_ESTEIRA_A VELOCIDADE_SECUNDARIA #define VELOCIDADE_ESTEIRA_B VELOCIDADE_SECUNDARIA -#define VELOCIDADE_ESTEIRA_C 140 +#define VELOCIDADE_ESTEIRA_C VELOCIDADE_SECUNDARIA +#define FILTRO_JUNCAO_MS 30 // ms — leitura do sensor de junção precisa ficar estável #define TIMEOUT_ENTREGA 12500 // ms — tempo máximo para a peça sair do topo e ser // CONFIRMADA pelo sensor de junção (medição 25/08: ~5 s // até o sensor; folga ampla). NÃO inclui os @@ -141,8 +144,8 @@ String bufferComando = ""; // Fase de SAÍDA DA ESTEIRA: depois que a peça atinge o sensor de junção, a // esteira secundária continua ligada por este tempo para a peça sair // fisicamente da esteira (a junção só marca "quase no fim"). Calibrar em -// bancada — a soma (tempo até o sensor + este valor) deve caber no -// TIMEOUT_ENTREGA. Medição 25/08: ~5 s até o sensor. +// bancada. Este tempo é contado DEPOIS da confirmação pelo sensor de junção e +// NÃO entra no TIMEOUT_ENTREGA (que só vigia o trajeto topo → junção). const unsigned long TEMPO_SAIDA_ESTEIRA_MS = 3000; bool saindoDaEsteira = false; unsigned long tempoSaidaIniciada = 0; @@ -152,6 +155,8 @@ unsigned long tempoSaidaIniciada = 0; // que a esteira liga. Um sensor que já está em LOW na partida (peça parada sobre // ele, desalinhamento) não confirma nada sozinho — o pedido termina em timeout. bool juncaoLiberada = false; +bool juncaoUltima = false; // última leitura do sensor de junção +unsigned long juncaoMudouEm = 0; // quando a leitura mudou pela última vez // Pausa pós-entrega sem bloquear o loop (substitui o antigo delay(1500)). bool aguardandoLimpezaEntrega = false; @@ -324,13 +329,17 @@ void publicarEntrega(char peca) { Serial.println(); } -void publicarErro(const char* tipo) { +// "peca" = letra A/B/C a que o erro se refere, ou 0 quando não se aplica +// (peca_invalida, comando_desconhecido). Antes o campo levava sempre a peça do +// pedido em curso, o que mostrava no dashboard "ocupado — Peça A" para um +// pedido de B. +void publicarErro(const char* tipo, char peca) { StaticJsonDocument<200> doc; doc["type"] = "evento"; doc["evento"] = "erro"; doc["tipo"] = tipo; - if (pecaSolicitada > 0) { - doc["peca"] = String((char)('A' + pecaSolicitada - 1)); + if (peca != 0) { + doc["peca"] = String(peca); } serializeJson(doc, Serial); Serial.println(); @@ -380,25 +389,26 @@ void interpretarComando(String linha) { if (linha.length() == 0) return; if (linha.startsWith("CMD:PECA:")) { - char pecaChar = linha.charAt(9); // A, B ou C + // Formato exato: "CMD:PECA:" + 1 letra (A, B ou C); qualquer outra coisa é inválida. + char pecaChar = (linha.length() == 10) ? linha.charAt(9) : 0; int peca = 0; if (pecaChar == 'A') peca = 1; else if (pecaChar == 'B') peca = 2; else if (pecaChar == 'C') peca = 3; if (peca == 0) { - publicarErro("peca_invalida"); + publicarErro("peca_invalida", 0); return; } if (estadoAtual != AGUARDANDO_PEDIDO) { - publicarErro("ocupado"); + publicarErro("ocupado", pecaChar); return; } pecaSolicitada = peca; publicarPedido(pecaChar); estadoAtual = VERIFICANDO_ESTOQUE; - } else if (linha.startsWith("CMD:RESET")) { + } else if (linha == "CMD:RESET") { if (estadoAtual == ERRO) { erroTimeout = false; erroSemEstoque = false; @@ -407,15 +417,15 @@ void interpretarComando(String linha) { aguardandoLimpezaEntrega = false; pecaSolicitada = 0; pararTodasSecundarias(); + estadoAtual = AGUARDANDO_PEDIDO; exibirEstoque(); publicarEstado(); publicarEstoque(); // sincroniza dashboard com o LCD após reset - estadoAtual = AGUARDANDO_PEDIDO; } // RESET fora de ERRO: ignorado de propósito (sistema já estável) } else { - publicarErro("comando_desconhecido"); + publicarErro("comando_desconhecido", 0); } } @@ -439,6 +449,18 @@ void processarComando() { void setup() { Serial.begin(9600); + // Motores primeiro: o gate do IRF520 não pode ficar flutuando durante a + // inicialização do LCD e o delay de 2 s (o motor poderia dar trancos). + // IRF520 — apenas 1 PWM por motor. + int motores[] = { MOTOR_A, MOTOR_B, MOTOR_C }; + for (int i = 0; i < 3; i++) { + pinMode(motores[i], OUTPUT); + analogWrite(motores[i], 0); // Inicia desligado + } + + // Evita que um barramento I2C travado (ruído do motor) prenda o Uno inteiro. + Wire.setWireTimeout(25000, true); + // Inicializa LCD lcd.init(); lcd.backlight(); @@ -448,13 +470,6 @@ void setup() { // Publica estoque inicial para sincronizar com o dashboard publicarEstoque(); - // Configura pinos dos motores (IRF520 — apenas 1 PWM por motor) - int motores[] = { MOTOR_A, MOTOR_B, MOTOR_C }; - for (int i = 0; i < 3; i++) { - pinMode(motores[i], OUTPUT); - analogWrite(motores[i], 0); // Inicia desligado - } - // Configura pinos dos sensores int sensores[] = { SENSOR_TOPO_A, SENSOR_TOPO_B, SENSOR_TOPO_C, @@ -547,13 +562,14 @@ void loop() { if (temPeca && estoque[pecaSolicitada] > 0) { atualizarLCD("Separando:", nomePeca); - publicarEstado(); estadoAtual = ACIONANDO_ESTEIRA; + publicarEstado(); } else { - atualizarLCD("ERRO: Sem estoque", nomePeca); + atualizarLCD("ERRO:Sem estoque", nomePeca); // 16 colunas erroSemEstoque = true; - publicarErro("sem_estoque"); estadoAtual = ERRO; + publicarErro("sem_estoque", (char)('A' + pecaSolicitada - 1)); + publicarEstado(); } break; } @@ -568,6 +584,8 @@ void loop() { tempoInicio = millis(); juncaoLiberada = false; + juncaoUltima = false; + juncaoMudouEm = millis(); publicarStatusEsteiras(); estadoAtual = ENTREGANDO_PECA; break; @@ -584,10 +602,10 @@ void loop() { if (millis() - tempoLimpezaEntrega >= PAUSA_POS_ENTREGA_MS) { aguardandoLimpezaEntrega = false; pecaSolicitada = 0; + estadoAtual = AGUARDANDO_PEDIDO; exibirEstoque(); publicarEstado(); publicarStatusEsteiras(); - estadoAtual = AGUARDANDO_PEDIDO; } break; } @@ -629,10 +647,18 @@ void loop() { if (pecaSolicitada == 2) pecaChegou = sensorJuncaoJ2(); if (pecaSolicitada == 3) pecaChegou = sensorJuncaoJ3(); + // Filtro: a leitura precisa ficar estável por FILTRO_JUNCAO_MS antes de valer + // (ruído do PWM / comparador do TCRT5000 não confirma passagem falsa). + if (pecaChegou != juncaoUltima) { + juncaoUltima = pecaChegou; + juncaoMudouEm = millis(); + } + bool juncaoEstavel = (millis() - juncaoMudouEm) >= FILTRO_JUNCAO_MS; + // A confirmação exige passagem: primeiro o sensor precisa ser visto livre. - if (!pecaChegou) juncaoLiberada = true; + if (juncaoEstavel && !pecaChegou) juncaoLiberada = true; - if (pecaChegou && juncaoLiberada) { + if (juncaoEstavel && pecaChegou && juncaoLiberada) { // NÃO para o motor ainda — inicia a fase de saída da esteira. atualizarLCD("Saindo da", "esteira..."); saindoDaEsteira = true; @@ -644,8 +670,10 @@ void loop() { pararTodasSecundarias(); erroTimeout = true; atualizarLCD("ERRO: Timeout", "Aguarde comando"); - publicarErro("timeout"); estadoAtual = ERRO; + publicarErro("timeout", (char)('A' + pecaSolicitada - 1)); + publicarEstado(); + publicarStatusEsteiras(); } break; } diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 2421e3f..3273e85 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -108,14 +108,18 @@ Linhas que não são JSON (logs de debug do Arduino) só aparecem no monitor ser **5 estados:** 1. **AGUARDANDO_PEDIDO** → recebe comando 2. **VERIFICANDO_ESTOQUE** → verifica se há peça -3. **ACIONANDO_ESTEIRA** → liga motor, aguarda sensores -4. **ENTREGANDO_PECA** → pausa, decrementa, publica -5. **ERRO** → timeout ou rejeição (requer CMD:RESET) - -**Rejeições explícitas:** -- `peca_indisponivel` — Peça não existe (ex.: "C" sem estoque) -- `ocupado` — Comando durante acionamento -- `comando_desconhecido` — Inválido +3. **ACIONANDO_ESTEIRA** → liga o motor da esteira secundária escolhida +4. **ENTREGANDO_PECA** → monitora o sensor de junção (timeout de 12,5 s); ao confirmar a passagem, mantém o motor 3 s, decrementa o estoque e publica a entrega +5. **ERRO** → `sem_estoque` ou `timeout` (requer CMD:RESET) + +**Erros que levam a ERRO (exigem CMD:RESET):** +- `sem_estoque` — sem peça no sensor do topo ou estoque zerado +- `timeout` — a peça não foi confirmada pelo sensor de junção em 12,5 s (motor desligado, estoque intacto) + +**Rejeições (não mudam o estado):** +- `peca_invalida` — letra diferente de A, B ou C +- `ocupado` — pedido com a FSM fora de AGUARDANDO_PEDIDO +- `comando_desconhecido` — linha não reconhecida **Diagramas:** diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 67b176e..df92b3f 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -15,11 +15,14 @@ Convenção de seções: `Adicionado`, `Alterado`, `Corrigido`, `Segurança`, `R ### Alterado - **Firmware Uno — revisão do código e reaplicação das atualizações de 02/10/2026** - - **Reaplicado:** `TIMEOUT_ENTREGA` de 9 s para **12,5 s** (vigia só o trajeto topo → junção; estourou, as três esteiras são desligadas e o sistema vai para `ERRO` sem debitar estoque), confirmação por **passagem** no sensor de junção (o sensor precisa ser visto livre depois que a esteira liga) e estoque inicial de **15** peças por tipo. O PWM por esteira (`VELOCIDADE_ESTEIRA_A/B/C`) foi mantido como estava (C em 140) + - **Reaplicado:** `TIMEOUT_ENTREGA` de 9 s para **12,5 s** (vigia só o trajeto topo → junção; estourou, as três esteiras são desligadas e o sistema vai para `ERRO` sem debitar estoque), confirmação por **passagem** no sensor de junção (o sensor precisa ser visto livre depois que a esteira liga) e estoque inicial de **15** peças por tipo. O PWM por esteira (`VELOCIDADE_ESTEIRA_A/B/C`) foi mantido, mas o valor da C passou de 140 para **200** (igual a A e B): o plano de testes registra ~150 como o PWM mínimo que move a esteira com peça, então 140 não movia a peça; calibre a C em bancada se o motor novo for mais rápido - **Corrigido:** o LCD era redesenhado (`lcd.clear()` e criação de `String`) a cada volta do loop enquanto o sistema estava em `ERRO` por timeout, o que fazia o display piscar e fragmentava o heap do Uno (2 KB de RAM); a mensagem agora é desenhada uma única vez, na transição para `ERRO` - **Corrigido:** a publicação periódica enviava as 4 mensagens (status, estoque, sensores e esteiras) de uma vez, com `Serial.flush()`, o que bloqueava o loop por centenas de milissegundos a cada segundo (9600 baud) e podia fazer um pulso curto do sensor de junção passar despercebido (falso timeout). Agora as mensagens saem escalonadas, uma a cada 250 ms (cada uma continua saindo 1x por segundo) e sem `flush` - **Corrigido:** durante a pausa de 1,5 s depois da entrega o dashboard ainda mostrava a esteira como ligada, mesmo com o motor já parado - - **Validado:** o sketch compila para o Arduino Uno (`arduino-cli`, 54% da flash e 55% da RAM). **Não validado em bancada**: o LCD apagado e a esteira C fraca (possível causa elétrica: fiação, diodo antiparalelo, driver IRF520 ou fonte) seguem em investigação e este firmware não os corrige por si só + - **Corrigido (revisão):** confirmação da junção com filtro de 30 ms (`FILTRO_JUNCAO_MS`), para um pico de ruído não confirmar uma passagem falsa; o estado da FSM é atribuído **antes** de ser publicado (o RESET, o fim da entrega e a verificação publicavam o estado antigo); `peca` do erro `ocupado` agora é a peça pedida (antes era a do pedido em curso); comandos exigem formato exato (`CMD:PECA:` + 1 letra e `CMD:RESET`); mensagem "ERRO:Sem estoque" cabe nas 16 colunas do LCD; motores desligados no início do `setup()` (antes do LCD e do `delay(2000)`); `Wire.setWireTimeout` evita travar o Uno com o I²C preso + - **Corrigido no gateway ESP32:** `Serial2.setRxBufferSize(2048)`, porque durante a reconexão MQTT/TLS o buffer padrão (256 B) transbordava e linhas do Uno chegavam truncadas ou se perdiam + - **Documentação:** `docs/ARCHITECTURE.md` (estados, erros que levam a ERRO e rejeições), tabela de estados do `README.md` e o roteiro de timeout do `docs/testes/plano_de_testes.md` + - **Validado:** o sketch compila para o Arduino Uno (`arduino-cli`, 54% da flash e 55% da RAM); o gateway ESP32 também compila (76% da flash). **Não validado em bancada**: o LCD apagado e a esteira C fraca (possível causa elétrica: fiação, diodo antiparalelo, driver IRF520 ou fonte) seguem em investigação e este firmware não os corrige por si só ### Revertido diff --git a/docs/testes/plano_de_testes.md b/docs/testes/plano_de_testes.md index aef7e3b..b666478 100644 --- a/docs/testes/plano_de_testes.md +++ b/docs/testes/plano_de_testes.md @@ -548,5 +548,5 @@ Detalhes completos (pinagem, ligações e checklists): [`test/esteira_peca_b/REA - **Telemetria Prometheus/Grafana (Card #25) — Concluída (01/10/2026):** o stack foi validado (smoke test e conferência dos painéis) e as capturas dos dashboards foram anexadas ao card. Esta observação substitui o "Não validado" e as pendências de 30/09, e o burn-in pode observar os dashboards Grafana em paralelo. -- **Firmware Uno — pendências de bancada (02/10/2026):** a compilação dos 4 sketches foi verificada com `arduino-cli` (procedimento em `docs/CI-CD.md`), mas o comportamento não foi testado em hardware. Ao voltar à bancada: (1) calibrar o PWM da esteira C (`VELOCIDADE_ESTEIRA_C`, inicial 140), medindo o tempo topo → junção, que deve ficar próximo ao de A e B (~5 s) e bem abaixo dos 12,5 s do timeout; (2) provocar o timeout de 12,5 s (sem peça) e conferir `ERRO`, motor desligado e estoque intacto; (3) iniciar um pedido com o sensor de junção já bloqueado e confirmar que o estoque não é debitado sem uma passagem; (4) executar antes o diagnóstico B/C do roteiro da Semana 7, ainda não realizado. +- **Firmware Uno — pendências de bancada (02/10/2026):** a compilação dos 4 sketches foi verificada com `arduino-cli` (procedimento em `docs/CI-CD.md`), mas o comportamento não foi testado em hardware. Ao voltar à bancada: (1) calibrar o PWM da esteira C (`VELOCIDADE_ESTEIRA_C`, inicial 140), medindo o tempo topo → junção, que deve ficar próximo ao de A e B (~5 s) e bem abaixo dos 12,5 s do timeout; (2) provocar o timeout de 12,5 s (com peça no topo, impedindo-a de chegar à junção) e conferir `ERRO`, motor desligado e estoque intacto; (3) iniciar um pedido com o sensor de junção já bloqueado e confirmar que o estoque não é debitado sem uma passagem; (4) executar antes o diagnóstico B/C do roteiro da Semana 7, ainda não realizado. diff --git a/esp32/gateway_mqtt/gateway_mqtt.ino b/esp32/gateway_mqtt/gateway_mqtt.ino index 5057df0..c945c11 100644 --- a/esp32/gateway_mqtt/gateway_mqtt.ino +++ b/esp32/gateway_mqtt/gateway_mqtt.ino @@ -369,6 +369,10 @@ void setup() { Serial.begin(BAUD_DEBUG); // Serial2 — comunicação com Arduino Uno (9600, igual ao Uno) + // Buffer de recepção maior: durante a reconexão MQTT/TLS o loop fica bloqueado + // por segundos e o Uno segue enviando ~300 B/s; com os 256 B padrão as linhas + // chegavam truncadas ou concatenadas (e um evento de entrega/erro se perdia). + Serial2.setRxBufferSize(2048); Serial2.begin(BAUD_ARDUINO, SERIAL_8N1, PIN_RX2, PIN_TX2); Serial.println();