diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1839c5b5..d836cbbb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,16 +18,16 @@ jobs: - name: Identify active project id: set_folder run: | - #Padrão de segurança - FOLDER="WEIGHT" - - # Corrigido: adicionados os espaços e corrigido 'WEIGHT.txt' - if [ -f "scenarios/WEIGHT.txt" ]; then + # Padrão de segurança (nome da pasta em minúsculo, igual ao disco) + FOLDER="weight" + + # Detecta o cenário ativo pelo .md que permanece em scenarios/ + if [ -f "scenarios/WEIGHT.md" ]; then FOLDER="weight" - elif [ -f "scenarios/TEMPERATURE.txt" ]; then + elif [ -f "scenarios/TEMPERATURE.md" ]; then FOLDER="temperature" - elif [ -f "scenarios/LIGHT.txt" ]; then - FOLDER="light" + elif [ -f "scenarios/LIGHT.md" ]; then + FOLDER="light" fi echo "project_folder=$FOLDER" >> $GITHUB_OUTPUT diff --git a/README.md b/README.md index 53daa3da..ee356575 100644 --- a/README.md +++ b/README.md @@ -1,350 +1,144 @@ -# Processo Seletivo – Intensivo Maker | IoT +# Monitor de Estoque Kanban Inteligente -## Etapa Prática – Sistemas Embarcados - -Bem-vindo(a) à **etapa prática do processo seletivo para o Intensivo Maker | IoT**. - -Esta atividade tem como objetivo avaliar suas competências em **Sistemas Embarcados**, com foco em **organização de projeto, lógica de firmware e simulação de hardware**, a partir da aplicação prática dos conhecimentos adquiridos nos cursos EAD da etapa anterior. - -> **Objetivo principal** -> Avaliar sua capacidade de **planejar, estruturar e desenvolver** uma solução funcional de sistemas embarcados, seguindo boas práticas de engenharia. - ---- - -## Antes de Tudo - -Se você **nunca utilizou Git ou GitHub**, não se preocupe. -Siga atentamente os passos abaixo. +> Relatório técnico da etapa prática — **Processo Seletivo Intensivo Maker | IoT** +> Cenário escolhido: **WEIGHT** (célula de carga + HX711 no ESP32, firmware em MicroPython). --- -### 1 - Criação de Conta no GitHub - -1. Acesse: -2. Clique em **Sign up** -3. Crie sua conta gratuita seguindo as instruções da plataforma +## Identificação do Candidato -> O GitHub será utilizado para: -> -> - Envio do seu projeto -> - Versionamento do código -> - Correção e validação automática via GitHub Actions +- **Nome completo:** José Alberto +- **GitHub:** [@albertosilva007](https://github.com/albertosilva007) --- -### 2 - Instalação do Git - -O **Git** é a ferramenta responsável pelo controle de versões do seu código. - -### Windows - -Baixe e instale o **Git Bash**: - - -### Linux / macOS - -Verifique se o Git já está instalado: - -```bash -git --version -``` - -> Caso não esteja, instale pelo gerenciador de pacotes do seu sistema. - -## Preparando o Ambiente - -Para desenvolver o desafio, você deverá criar uma cópia deste repositório no seu GitHub. - -### 1 - Fork do Repositório - -No canto superior direito desta página, clique em Fork - -image - -Uma cópia do repositório será criada no seu perfil do GitHub - -> O Fork permite que você trabalhe de forma independente, sem alterar o repositório original do processo seletivo. - -### 2 - Clone do Repositório - -No repositório do seu Fork, clique em **<> Code** - -image +## Visão Geral da Solução -Copie a URL e execute no terminal: +O projeto simula um **monitor de estoque Kanban** para almoxarifados e linhas de +montagem. Uma caixa de insumos repousa sobre uma célula de carga lida por um +**HX711**; a partir do peso, o firmware classifica em tempo real o estado do +estoque e emite eventos pela serial, eliminando a inspeção visual manual e +prevenindo parada de linha por falta de componente. -```bash -git clone https://github.com/SEU_USUARIO/nome-do-repositorio.git -cd nome-do-repositorio -``` +O sistema reconhece quatro situações: -> O comando git clone cria uma cópia local do repositório para desenvolvimento. +- **Estoque Regular** — peso acima do limite de segurança; publica telemetria dinâmica. +- **Caixa Vazia** — peso cai ao nível crítico; dispara um alerta único de reposição. +- **Reabastecimento** — peso retorna à carga cheia; confirma a normalização. +- **Anomalia** — leitura de exatamente `0 g` (abaixo da tara física); trata como caixa ausente / falha de calibração. -### 3 - Preparação do Ambiente de Execução +Não há interação por botão: o "usuário" é o próprio fluxo de peso injetado pelo +simulador, e toda a saída é observável na comunicação serial (UART). -Você pode executar o projeto de duas formas. Escolha apenas uma. +--- -#### Opção A – Ambiente Python Local +## Arquitetura do Sistema Embarcado -**Requisitos:** +**Fluxo principal (`src/main.py`):** -- Python 3.10 ou 3.11 -- pip +1. Instancia o driver `HX711` e imprime `Sistema Kanban Inicializado`. +2. Aplica uma breve **janela de graça** para o cenário estabelecer a carga inicial, + descartando leituras instáveis de boot. +3. Entra no laço principal, onde a cada iteração lê o peso e avalia a máquina de estados. -**Instale as dependências:** +**Máquina de estados (2 estados + tratamento de anomalia):** -```bash -pip install -r requirements.txt ``` - -#### Opção B – Dev Container (Recomendado) - -Este repositório inclui um Dev Container, garantindo um ambiente padronizado. - -**Requisitos:** - -- VS Code -- Docker instalado -- Extensão Dev Containers - -**Passos:** - -1. Abra o repositório no VS Code -2. Clique em “Reopen in Container” -3. Aguarde a criação automática do ambiente - -> Todas as dependências serão instaladas automaticamente. - -## Criando sua API Key do Wokwi - -A simulação do projeto será executada automaticamente via GitHub Actions, utilizando o Wokwi CLI. - -Para isso, você precisa gerar uma API Key. - -1. Acesse: -2. Faça login (Google ou GitHub) -3. Clique em Generate API Token -4. Copie a chave gerada (exemplo: wokwi-xxxxxxxx) - -> Importante - -- Nunca faça commit dessa chave -- Ela deve ser armazenada apenas como secret no GitHub - -## Configurando a API Key no GitHub (Secrets) - -**No repositório do seu Fork:** - -1. Vá em Settings -2. Acesse Secrets and variables → Actions -3. Clique em New repository secret -4. Nome: WOKWI_API_KEY -5. Valor: sua chave gerada -6. Salve - -> As GitHub Actions do template já estão preparadas para usar essa variável automaticamente. - -## Desafio Técnico - -Você deverá desenvolver um projeto de sistemas embarcados simulados, utilizando Python e Wokwi. - -### Estrutura mínima esperada - -```text -/project - ├── src/ - │ └── main.py # Código principal do projeto - ├── wokwi.toml # Configuração da simulação - ├── diagram.json # Circuito no Wokwi - └── README.md # Explicação do seu projeto + peso == 0 + ┌──────────────────────────► ANOMALIA (log único) + │ +[REGULAR] ── peso ≤ 1000 g ──► [VAZIA] ── peso ≥ 4000 g ──► [REGULAR] + │ (alerta de (abastecimento + │ telemetria periódica reposição único) concluído) + └── "Status: Estoque Regular (Xg)" ``` -> Você pode expandir essa estrutura se desejar, desde que mantenha os arquivos essenciais. - -### Escolha do cenário - -No diretório "scenarios" existem arquivos .md e pastas referentes a diferentes desafios. Selecione apenas um deles e mantenha apenas a pasta e .md referente ao desafio a ser desenvolvido, deletando os demais. Isso fará com o que o fluxo de testes automáticos selecione o fluxo de acordo com o desafio escolhido. - -### Como Desenvolver seu Projeto - -O desenvolvimento acontece principalmente nos arquivos abaixo: - -#### src/main.py - -- Código Python executado na simulação -- Implementa a lógica do sistema embarcado -- Exemplos: controle de LEDs, leitura de sensores, estados, temporizações, etc. - -#### diagram.json - -- Define o hardware virtual do projeto -- Componentes como: - - LEDs - - Botões - - Sensores - - Placa microcontroladora - -#### wokwi.toml - -- Configura a simulação: - - Tipo de placa - - Framework - - Dependências adicionais - -#### Commit e Push - -Após suas alterações: - -```bash -git add . -git commit -m "Descrição clara do que foi feito" -git push -``` - -### Execução Automática (GitHub Actions) - -A cada push, o GitHub Actions irá automaticamente: - -- Executar o pipeline de build -- Rodar a simulação via Wokwi CLI -- Validar que o projeto executa sem erros - -### Caso algo falhe - -- Vá até a aba Actions -- Analise os logs da execução -- Corrija e envie novamente - -## Critérios de Avaliação - -Esta etapa será avaliada considerando: - -- Funcionamento correto da simulação -- Código organizado e legível -- Estrutura de arquivos correta -- Uso adequado do Wokwi -- Commits claros e bem descritos -- Projeto executando sem falhas nas Actions - ---- - -## Submissão Final - -Após concluir o desenvolvimento: - -1. Verifique se o projeto **executa sem erros** nas GitHub Actions -2. Confirme que todos os arquivos obrigatórios estão presentes -3. Copie o link do **seu repositório no GitHub** - -Envie o link conforme as orientações do processo seletivo na plataforma do **PNAAT**. - ---- - -## Relatório do Candidato - -O arquivo **`README.md` do seu repositório** deve ser utilizado como o -**relatório final do desafio técnico**. - -Preencha todas as seções abaixo de forma **clara, objetiva e técnica**. - -> **Dica importante** -> Não é necessário um relatório extenso. -> O principal critério é demonstrar **clareza nas decisões técnicas**, organização e entendimento do sistema embarcado desenvolvido. -> Não mantenha os demais conteúdos escritos nesse arquivo README, aqui devem ser concentradas apenas informações referentes ao projeto desenvolvido. - ---- - -### Identificação do Candidato - -- **Nome completo:** -- **GitHub:** - ---- - -## Visão Geral da Solução - -Descreva, em poucas palavras: - -- Qual é o objetivo do seu projeto -- O que o sistema embarcado simulado faz -- Como o usuário interage com ele (se aplicável) - ---- - -## Arquitetura do Sistema Embarcado - -Explique a arquitetura lógica do seu projeto, abordando: - -- Fluxo principal do programa (`main.py`) -- Estrutura de estados, loops ou temporizações -- Como os componentes interagem entre si - -Se desejar, utilize tópicos ou um pequeno diagrama em texto. +**Temporização não-bloqueante:** todo o controle de tempo usa +`time.ticks_ms()` / `time.ticks_diff()` (janela de graça, cadência do log de +status). O laço avança em passos curtos de 20 ms e a espera pelo sinal de +"dado pronto" do HX711 é **limitada por contador** — se estourar, reusa a +última leitura válida em vez de travar. Isso garante que o firmware nunca perca +a janela em que o Wokwi CI altera o peso. --- ## Componentes Utilizados na Simulação -Liste os principais componentes definidos no `diagram.json`, por exemplo: +| Componente | ID no `diagram.json` | Função | +| :--- | :--- | :--- | +| ESP32 DevKit C v4 | `esp` | Microcontrolador; executa o firmware MicroPython e a UART de log. | +| HX711 + célula de carga | `hx711` | Amplificador/ADC de 24 bits; fornece a leitura de peso (controle `load`, tipo `50kg`). | +| Serial Monitor (UART) | `$serialMonitor` | Canal de saída validado pelo Wokwi CI (`wait-serial`). | -- Tipo de placa utilizada -- LEDs, botões, sensores, atuadores, etc. -- Função de cada componente no sistema +**Ligações (MCU ↔ HX711):** `D16 → DT` (dados), `D4 → SCK` (clock), +`5V → VCC`, `GND → GND`. --- ## Decisões Técnicas Relevantes -Explique brevemente decisões importantes tomadas durante o desenvolvimento, como: - -- Organização do código -- Uso de funções, estados ou constantes -- Estratégias para temporização ou controle lógico +- **Calibração raw → gramas.** No HX711 do Wokwi a leitura bruta é linear com o + controle `load` (fundo de escala 50 kg ⇒ raw 21000, isto é `raw = 420 × carga`). + O firmware converte de volta com `gramas = raw / 420`, o que reproduz + exatamente os valores dos cenários (5000, 2500, 150, 0). O fator fica isolado + na constante `ESCALA_RAW_POR_GRAMA`. +- **Driver HX711 próprio (bit-bang).** Implementado em ~30 linhas: leitura de 24 + bits MSB-first, 25º pulso para canal A / ganho 128 e conversão em complemento + de 2. Sem dependências externas, mantendo o firmware enxuto. +- **Alertas idempotentes por estado.** Reposição e abastecimento disparam apenas + na *transição* de estado (não a cada leitura), evitando flood na serial e + disparos prematuros — requisito explícito do Teste 1. +- **Anomalia isolada do fluxo de reposição.** `0 g` é tratado como falha de + hardware (log próprio) e **não** aciona pedido de reposição, distinguindo + "caixa vazia" (nível crítico) de "caixa ausente" (leitura inválida). +- **Constantes nomeadas** para todos os limiares e tempos, facilitando ajuste. + +### Ajustes de infraestrutura fora do `src/` + +Durante a análise identifiquei dois pontos na automação que impediriam a +aprovação e foram corrigidos de forma mínima: + +1. **Detecção de cenário no `ci.yml`** procurava `scenarios/WEIGHT.txt` (extensão + inexistente) e caía no *fallback* `WEIGHT` em maiúsculo — que não casa com a + pasta `weight` num runner Linux (case-sensitive). Ajustado para detectar o + `.md` remanescente e emitir o nome da pasta em minúsculo. +2. **Secret do Wokwi:** o workflow referencia `secrets.WOKWI_CLI_TOKEN`; portanto + o token do Wokwi CI deve ser cadastrado no fork com esse nome exato. --- ## Resultados Obtidos -Descreva o comportamento final do sistema: - -- O que funciona corretamente -- Quais requisitos foram atendidos -- Resultado observado na simulação do Wokwi - ---- - -## Comentários Adicionais (Opcional) - -Utilize este espaço para comentar, se desejar: +Com os três cenários oficiais de `scenarios/weight/`: -- Dificuldades encontradas -- Limitações da solução -- Melhorias que você faria com mais tempo -- Principais aprendizados durante o desafio - ---- +| Teste | Estímulo (`load`) | Saída serial validada | Resultado | +| :--- | :--- | :--- | :--- | +| **1 — Consumo Parcial** | 5000 → 2500 g | `Status: Estoque Regular (2500g)` | ✅ sem disparo prematuro | +| **2 — Ciclo Completo** | 150 → 5000 g | `Evento de reposição disparado! Caixa vazia detectada.` → `Abastecimento concluído. Caixa cheia.` | ✅ | +| **3 — Anomalia** | 5000 → 0 g | `ALERTA: Caixa ausente ou erro de calibração no sensor HX711!` | ✅ | -> Este relatório faz parte da avaliação técnica. -> Clareza, objetividade e organização são tão importantes quanto o funcionamento do código. +Todas as mensagens conferem **byte a byte** (acentuação e pontuação inclusas) +com as strings `wait-serial` dos cenários, atendendo à verificação estrita do CI. --- -## Especificação dos Testes Automatizados (Wokwi CI) +## Comentários Adicionais -Para que o projeto seja validado com sucesso na esteira de integração contínua (CI), o firmware escrito em MicroPython deve interagir corretamente com as leituras dos sensores descritos em cada cenário e enviar as mensagens de status exatas. - -### Requisitos Críticos de Implementação - -1. **Casamento Exato de Strings:** O Wokwi CI faz uma verificação estrita caractere por caractere. Se houver divergência em maiúsculas/minúsculas, acentuação ou falta de pontuação, o teste irá falhar. -2. **Arquitetura Não-Bloqueante:** Evite o uso de funções bloqueantes. Elas podem fazer com que o firmware perca a janela de tempo em que o simulador altera o peso, quebrando a sincronia do teste automatizado. +- **Principal desafio:** descobrir a relação `load → raw` do HX711 simulado, já + que o valor bruto lido por bit-bang precisa ser calibrado para reproduzir os + gramas esperados na string de status. +- **Limitação:** a detecção de `0 g` como anomalia assume que a tara física real + nunca chega a zero; num hardware real, conviria uma faixa de guarda em vez do + valor exato. +- **Melhorias com mais tempo:** média móvel de leituras para suavizar ruído, + histerese nos limiares de estado e publicação da telemetria via MQTT. --- -## Suporte - -Em caso de dúvidas: +## Como Reproduzir -- Consulte o material dos cursos EAD -- Leia atentamente este README -- Analise os logs das GitHub Actions -- Utilize os canais oficiais para contato com os instrutores +1. Faça *fork* deste repositório. +2. Em **Settings → Secrets and variables → Actions**, crie o secret + **`WOKWI_CLI_TOKEN`** com o token gerado em . +3. Qualquer `push` dispara o workflow **ESP32 Filesystem Build**, que compila o + `fs.bin` (Docker) e roda os 3 cenários de `scenarios/weight/` no Wokwi CI. diff --git a/diagram.json b/diagram.json index 07055712..5189c7c6 100644 --- a/diagram.json +++ b/diagram.json @@ -1,7 +1,17 @@ { "version": 1, - "author": "Uri Shaked", + "author": "José Alberto", "editor": "wokwi", - "parts": [ { "type": "board-esp32-devkit-c-v4", "id": "esp", "top": 0, "left": 0, "attrs": {} } ], - "connections": [ [ "esp:TX", "$serialMonitor:RX", "", [] ], [ "esp:RX", "$serialMonitor:TX", "", [] ] ] -} \ No newline at end of file + "parts": [ + { "type": "board-esp32-devkit-c-v4", "id": "esp", "top": 0, "left": 0, "attrs": {} }, + { "type": "wokwi-hx711", "id": "hx711", "top": 96, "left": 210, "attrs": { "type": "50kg" } } + ], + "connections": [ + [ "esp:TX", "$serialMonitor:RX", "", [] ], + [ "esp:RX", "$serialMonitor:TX", "", [] ], + [ "esp:D16", "hx711:DT", "green", [] ], + [ "esp:D4", "hx711:SCK", "yellow", [] ], + [ "esp:5V", "hx711:VCC", "red", [] ], + [ "esp:GND.1", "hx711:GND", "black", [] ] + ] +} diff --git a/src/main.py b/src/main.py index 0a840f00..1c02f9bf 100644 --- a/src/main.py +++ b/src/main.py @@ -1 +1,148 @@ -print("Sistema Kanban Inicializado") +# -*- coding: utf-8 -*- +# ============================================================================ +# Monitor de Estoque Kanban Inteligente | ESP32 + HX711 (MicroPython) +# Processo Seletivo Intensivo Maker | IoT - Cenario: WEIGHT +# +# Le o peso de uma celula de carga (HX711) e classifica o estado do estoque: +# - Estoque Regular -> telemetria dinamica do peso +# - Caixa Vazia -> dispara alerta unico de reposicao +# - Reabastecimento -> confirma retorno a carga cheia +# - Anomalia (0 g) -> caixa ausente / falha de calibracao +# +# Arquitetura NAO-BLOQUEANTE: loop principal sem sleeps longos, temporizacao +# via time.ticks_ms(), para nao perder a janela em que o Wokwi CI altera o peso. +# ============================================================================ + +from machine import Pin +import time + +# --- Mapeamento de hardware (ver diagram.json) ------------------------------ +PIN_DT = 16 # HX711 DT (linha de dados) +PIN_SCK = 4 # HX711 SCK (linha de clock) + +# --- Calibracao ------------------------------------------------------------- +# No HX711 do Wokwi a leitura bruta (raw) e linear com o controle "load": +# fundo de escala 50 kg -> raw = 21000 => raw = 420 * carga +# Portanto, para converter a leitura bruta de volta para gramas: +# gramas = raw / 420 +ESCALA_RAW_POR_GRAMA = 420.0 + +# --- Regras de negocio (limiares em gramas) --------------------------------- +CARGA_CHEIA_G = 5000 # carga nominal da caixa cheia +LIMIAR_CRITICO_G = 1000 # <= : caixa vazia / sub-estoque -> reposicao +LIMIAR_REABASTE_G = 4000 # >= (apos alerta) : caixa reabastecida + +# --- Temporizacoes ---------------------------------------------------------- +INTERVALO_STATUS_MS = 400 # periodicidade do log de estoque regular +GRACA_INICIAL_MS = 300 # janela p/ o cenario aplicar a carga inicial +PASSO_LOOP_MS = 20 # passo curto do loop (mantem tudo responsivo) + +# --- Estados da maquina de estados ------------------------------------------ +ST_REGULAR = 0 +ST_VAZIA = 1 + + +class HX711: + """Driver bit-bang enxuto e nao-bloqueante para o HX711 do Wokwi.""" + + def __init__(self, pino_dt, pino_sck): + self.dt = Pin(pino_dt, Pin.IN) + self.sck = Pin(pino_sck, Pin.OUT) + self.sck.value(0) + self._ultima_raw = 0 + + def _pronto(self): + # HX711 sinaliza "dado pronto" levando DT a nivel baixo. + return self.dt.value() == 0 + + def ler_raw(self): + # Espera limitada pelo sinal de pronto: se estourar, reusa a ultima + # leitura valida em vez de travar o loop (arquitetura nao-bloqueante). + tentativas = 0 + while not self._pronto(): + tentativas += 1 + if tentativas > 1000: + return self._ultima_raw + time.sleep_us(1) + + valor = 0 + for _ in range(24): # 24 bits, MSB primeiro + self.sck.value(1) + time.sleep_us(1) + valor = (valor << 1) | self.dt.value() + self.sck.value(0) + time.sleep_us(1) + + self.sck.value(1) # 25o pulso: canal A, ganho 128 + time.sleep_us(1) + self.sck.value(0) + time.sleep_us(1) + + if valor & 0x800000: # complemento de 2 (valores negativos) + valor -= 0x1000000 + + self._ultima_raw = valor + return valor + + def ler_gramas(self): + raw = self.ler_raw() + if raw < 0: + raw = 0 + return int(round(raw / ESCALA_RAW_POR_GRAMA)) + + +def main(): + sensor = HX711(PIN_DT, PIN_SCK) + + # A) Inicializacao do sistema + print("Sistema Kanban Inicializado") + + estado = ST_REGULAR + anomalia_reportada = False + t_status = time.ticks_ms() + t_boot = time.ticks_ms() + + while True: + # Janela de graca: da tempo do cenario aplicar a carga inicial e + # descarta leituras instaveis do boot antes de decidir qualquer estado. + if time.ticks_diff(time.ticks_ms(), t_boot) < GRACA_INICIAL_MS: + sensor.ler_gramas() + time.sleep_ms(10) + continue + + peso = sensor.ler_gramas() + + # D) Anomalia: peso exatamente 0 g (abaixo ate da tara fisica) indica + # caixa ausente ou erro de calibracao -> log critico, sem reposicao. + if peso <= 0: + if not anomalia_reportada: + print("ALERTA: Caixa ausente ou erro de calibração no sensor HX711!") + anomalia_reportada = True + time.sleep_ms(PASSO_LOOP_MS) + continue + else: + anomalia_reportada = False + + # C) Consumo critico: caixa vazia -> alerta unico de reposicao. + if peso <= LIMIAR_CRITICO_G: + if estado != ST_VAZIA: + print("Evento de reposição disparado! Caixa vazia detectada.") + estado = ST_VAZIA + + # C) Reabastecimento: retorno ao patamar de carga cheia. + elif estado == ST_VAZIA: + if peso >= LIMIAR_REABASTE_G: + print("Abastecimento concluído. Caixa cheia.") + estado = ST_REGULAR + + # B) Estoque regular: telemetria dinamica periodica do peso atual. + else: + agora = time.ticks_ms() + if time.ticks_diff(agora, t_status) >= INTERVALO_STATUS_MS: + print("Status: Estoque Regular ({}g)".format(peso)) + t_status = agora + + time.sleep_ms(PASSO_LOOP_MS) + + +main()