MCU & FPGA Algoritimos,OneWire,Sensores Sistema de Ronda com OneWire e iButton DS1904 usando ESP32: Implementação Completa, Multithread e à Prova de Falhas

Sistema de Ronda com OneWire e iButton DS1904 usando ESP32: Implementação Completa, Multithread e à Prova de Falhas


O que é o barramento OneWire e por que ele é interessante para sistemas de segurança

O barramento OneWire é um padrão de comunicação serial desenvolvido originalmente pela Dallas Semiconductor (hoje Maxim Integrated / Analog Devices) que se destaca por um detalhe bem atraente para sistemas embarcados: ele usa apenas uma linha de dados mais o GND comum. Ou seja, em vez de precisar de vários fios como em SPI ou de duas linhas como em I²C, o OneWire permite que o microcontrolador converse com vários dispositivos usando apenas um único pino de E/S (GPIO) e um resistor de pull-up. Em aplicações como controle de acesso, identificação de pessoas e rondas de vigilância, essa simplicidade reduz custo, facilita instalação em campo e torna o sistema mais robusto na prática.

Do ponto de vista elétrico, o OneWire usa um barramento open-drain (ou open-collector): nenhum dispositivo “empurra” o sinal para nível alto; todos apenas puxam a linha para o nível baixo quando necessário. Quem mantém a linha em nível lógico alto é um resistor de pull-up ligado à alimentação (típico de 4,7 kΩ em 3,3 V ou 5 V, dependendo dos dispositivos). O ESP32, atuando como mestre do barramento, configura um GPIO em modo open-drain e coordena toda a comunicação, gerando os pulsos de tempo corretos para reset, escrita e leitura de bits, enquanto os dispositivos OneWire (os escravos) respondem em instantes bem definidos de tempo. Isso significa que o protocolo é extremamente sensível a temporização, e é por isso que, mais à frente, vamos falar em código thread-safe e no cuidado com interrupções e tasks do FreeRTOS.

Cada dispositivo OneWire possui um código ROM de 64 bits único no mundo, o que o torna perfeito para uso como “chave” de identificação. Os famosos iButtons são exatamente isso: pequenos “botões metálicos” encapsulando um CI OneWire, que pode armazenar dados e/ou fornecer um ID único. No nosso artigo vamos focar no DS1904, um iButton que reúne um RTC (relógio em tempo real) com interface OneWire. Ele não só permite identificar o vigilante pelo ID único, como também possibilita registrar horário de passagem nos pontos de ronda com alta confiabilidade temporal, algo ideal para sistemas de auditoria e segurança.

Na prática, o barramento OneWire funciona com uma relação bem clara de mestre–escravos: apenas o ESP32 inicia as comunicações, envia comandos e define as janelas de tempo em que os escravos podem responder. Os escravos nunca falam “sozinhos”; eles apenas respondem quando o mestre permite, o que simplifica o controle da linha e evita colisões. A comunicação é feita em “slots de tempo” bem definidos: o mestre puxa a linha para baixo por alguns microssegundos para escrever um bit 0 ou 1, ou cria uma janela de leitura onde o escravo pode, ou não, puxar a linha para baixo para indicar o valor do bit. Apesar de parecer complicado à primeira vista, esse esquema é bastante estável e, uma vez encapsulado em uma camada de driver bem escrita, o programador de aplicação passa a enxergar o barramento apenas como funções de “ler ROM”, “escrever comando”, “ler dados”, de forma bem amigável.

Em sistemas de segurança e ronda, a combinação “barramento simples + dispositivos robustos” é muito valiosa. Um iButton pode ser instalado em cada ponto de verificação (posto de ronda), o vigilante carrega seu “botão” pessoal (ou a central possui iButtons por ponto, dependendo da lógica do projeto) e o ESP32, ligado a um leitor OneWire em cada ponto, registra a passagem. Como o barramento permite cabos mais longos que protocolos como I²C (desde que respeitados alguns cuidados de impedância, resistência e capacitância de cabo, que discutiremos em capítulo específico), podemos ter um cabeamento relativamente extenso ligando diversos leitores ou uma placa central com vários canais.

Ao longo dos próximos capítulos, vamos sair dessa visão conceitual e entrar em detalhes práticos: primeiro sobre sinais e temporização do OneWire, depois sobre como implementar um driver OneWire thread-safe no ESP32 usando FreeRTOS, em seguida como conversar especificamente com o iButton DS1904, e finalmente como integrar tudo em um sistema de ronda que dispara uma buzina se o vigilante demorar mais de 10 minutos para alcançar o próximo ponto, usando tasks independentes e notificações de tarefa (thread notify) para ativar e desativar o alarme.


Como funciona a comunicação OneWire (reset, presença, leitura e escrita)

Para trabalhar de forma segura com o barramento OneWire, é essencial entender como a troca de dados acontece no nível elétrico e temporal. Mesmo que futuramente você utilize bibliotecas prontas, compreender esses detalhes permite ajustar o driver para cabos longos, vários dispositivos no barramento e cenários onde interrupções podem afetar a temporização — algo especialmente relevante no ESP32, que possui Wi-Fi, Bluetooth, tasks concorrentes e muitas interrupções.

A comunicação OneWire segue quatro operações fundamentais:


1. Reset Pulse (pulso de reset)

Toda transação começa com o mestre (ESP32) puxando a linha para baixo por aproximadamente 480 µs. Este pulso longo serve como anúncio de que o mestre quer iniciar uma nova sessão no barramento.

Após soltar a linha, o mestre aguarda entre 15 µs e 60 µs, período em que qualquer escravo presente deve puxar a linha para baixo por cerca de 60 a 240 µs, sinalizando o chamado Presence Pulse.
Esse evento confirma que ao menos um dispositivo OneWire está conectado e pronto para receber comandos.


2. Presence Pulse (pulso de presença)

Quando um dispositivo OneWire reconhece o pulso de reset, ele responde com um pulso curto de nível baixo, indicando que está disponível.
O mestre então prossegue enviando comandos.

Para o sistema de ronda, essa etapa é crítica: se o leitor de iButton não receber o Presence Pulse, significa que o vigilante não encostou o DS1904 corretamente — algo que a aplicação deve detectar.


3. Escrita de Bits (Write 0 / Write 1)

A escrita funciona em slots de tempo de aproximadamente 60–70 µs:

  • Write 1:
    O mestre puxa a linha para baixo por 6–10 µs e então libera.
    O escravo interpreta isso como bit “1”.
  • Write 0:
    O mestre mantém a linha baixa por 60 µs completos.
    O escravo interpreta isso como bit “0”.

Note como o tempo determina tudo — qualquer jitter ou atraso pode corromper a comunicação.


4. Leitura de Bits (Read Slot)

A leitura funciona assim:

  1. O mestre abaixa a linha por ~6 µs.
  2. Libera a linha.
  3. Após ~9 µs, ele amostra o valor lógico.
  4. O escravo pode (ou não) puxar a linha para baixo para informar um “0”.
  5. Se não fizer nada, a linha permanece em “1”.

Essa janela precisa ser muito precisa. É aqui que uma implementação thread-safe no ESP32 faz toda a diferença — qualquer preempção pode quebrar a leitura.


Por que isso importa para o ESP32?

O ESP32 é extremamente multitarefa:

  • interrupções de Wi-Fi,
  • Bluetooth,
  • tarefas de FreeRTOS concorrentes,
  • watchdog timers.

Se o código OneWire não for protegido, a temporização pode falhar e o DS1904 será mal interpretado.

Por isso:

  • Usaremos gpio_set_level() com pino configurado em open-drain.
  • Desativaremos interrupções apenas dentro das janelas críticas.
  • Utilizaremos mutex para garantir thread safety.
  • Encapsularemos toda a temporização crítica em funções atômicas.

Nos próximos capítulos você terá um driver completo e seguro.


Por que o OneWire é especialmente útil com o DS1904?

O DS1904 combina duas características úteis:

  • ID único (ROM de 64 bits) — permite identificar quem está executando a ronda.
  • RTC interno — registra horário da leitura, permitindo auditoria confiável.

Quando o vigilante encosta o iButton no leitor, o sistema pode:

  1. Ler o ID → identifica o funcionário.
  2. Ler o relógio interno → captura timestamp sólido.
  3. Gravar ponto de ronda.
  4. Resetar um timer de 10 minutos até o próximo ponto.

Se o próximo ponto não for alcançado dentro do tempo, o sistema aciona uma buzina.


Construção de um Driver OneWire Seguro no ESP32: Mutex + Critical Sections (FreeRTOS)

Para trabalhar corretamente com o barramento OneWire no ESP32 é preciso mais que simplesmente manipular GPIOs. O protocolo exige temporização em microssegundos extremamente estrita, e isso pode ser facilmente prejudicado pelo ambiente multitarefa do FreeRTOS, interrupções internas do chip (Wi-Fi/Bluetooth), watchdogs e trocas de contexto. Se o tempo de um pulso variar alguns microssegundos, uma leitura pode ser interpretada como “0” em vez de “1”, corrompendo toda a comunicação.

Por esse motivo, a abordagem mais robusta combina duas camadas de proteção:

  1. Mutex (Semáforo de exclusão mútua)
    Impede que duas tasks tentem usar o barramento ao mesmo tempo.
  2. Critical Sections (taskENTER_CRITICAL)
    Desabilitam interrupções durante trechos de tempo crítico, garantindo que o código não seja interrompido bem no meio de um slot OneWire (que dura cerca de 60 µs).

Usar apenas mutex não é suficiente, pois o FreeRTOS pode preemptar a task dentro da janela sensível do protocolo. Já usar apenas critical sections é perigoso, pois tasks podem interferir entre si. A solução mais segura é combinar os dois, garantindo simultaneamente exclusão mútua entre threads e preservação absoluta de temporização.


Arquitetura Geral do Driver

Nosso driver OneWire para o ESP32 será composto por:

  • inicialização do GPIO em modo open-drain (fundamental para barramentos OneWire);
  • um mutex global para proteger chamadas de alto nível;
  • um spinlock para controlar critical sections e impedir interrupções nos pulsos;
  • funções críticas (bit a bit) protegidas por critical sections;
  • funções de alto nível (reset, write byte, read byte) protegidas por mutex.

Dessa forma, garantimos:

  • estabilidade do protocolo mesmo com Wi-Fi ativo;
  • robustez quando várias tasks coexistem;
  • nenhum risco de corrupção por interrupções no meio da leitura;
  • compatibilidade com qualquer dispositivo OneWire, incluindo nosso foco: o iButton DS1904.

Código completo do driver OneWire (ESP32 + FreeRTOS, thread-safe e timing-safe)

#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "driver/gpio.h"
#include "esp32/rom/ets_sys.h"

#define ONEWIRE_PIN 4

// Mutex para exclusão mútua entre tasks
static SemaphoreHandle_t ow_mutex = NULL;

// Spinlock / mux para critical section (desabilita interrupções no core)
static portMUX_TYPE ow_spinlock = portMUX_INITIALIZER_UNLOCKED;

/**
 * @brief Inicializa o barramento OneWire no pino definido.
 *        Configura o GPIO como open-drain e cria o mutex.
 */
void onewire_init(void)
{
    if (ow_mutex == NULL) {
        ow_mutex = xSemaphoreCreateMutex();
    }

    gpio_config_t io_conf = {
        .pin_bit_mask = (1ULL << ONEWIRE_PIN),
        .mode = GPIO_MODE_INPUT_OUTPUT_OD,   // open-drain
        .pull_up_en = GPIO_PULLUP_DISABLE,   // usar pull-up EXTERNO
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE
    };
    gpio_config(&io_conf);

    gpio_set_level(ONEWIRE_PIN, 1); // libera a linha
}

/**
 * @brief Envia pulso de reset e detecta presence pulse em modo protegido.
 *
 * @return true se algum dispositivo respondeu (presence detectado).
 */
bool onewire_reset(void)
{
    bool presence = false;

    // Garante que só uma task use o barramento por vez
    xSemaphoreTake(ow_mutex, portMAX_DELAY);

    // Região crítica: temporização de reset em microssegundos
    taskENTER_CRITICAL(&ow_spinlock);

    // 1) Mestre puxa a linha para LOW por ~480µs
    gpio_set_level(ONEWIRE_PIN, 0);
    ets_delay_us(480);

    // 2) Libera a linha (pull-up externo leva a HIGH)
    gpio_set_level(ONEWIRE_PIN, 1);

    // 3) Aguarda 70µs e lê o presence
    ets_delay_us(70);
    presence = (gpio_get_level(ONEWIRE_PIN) == 0);

    // 4) Espera o restante da janela de reset
    ets_delay_us(410);

    taskEXIT_CRITICAL(&ow_spinlock);

    xSemaphoreGive(ow_mutex);

    return presence;
}

/**
 * @brief Envia um único bit no barramento (slot de escrita).
 *        Protegido por critical section para manter temporização.
 */
static inline void onewire_write_bit(int bit)
{
    taskENTER_CRITICAL(&ow_spinlock);

    // Início do slot: sempre puxa para LOW
    gpio_set_level(ONEWIRE_PIN, 0);

    if (bit) {
        // Write '1' – pulso curto LOW, depois libera
        ets_delay_us(6);
        gpio_set_level(ONEWIRE_PIN, 1);
        ets_delay_us(64);  // completa o slot (~70µs)
    } else {
        // Write '0' – mantém LOW por quase todo o slot
        ets_delay_us(60);
        gpio_set_level(ONEWIRE_PIN, 1);
        ets_delay_us(10);
    }

    taskEXIT_CRITICAL(&ow_spinlock);
}

/**
 * @brief Lê um único bit do barramento (slot de leitura).
 *        Protegido por critical section para garantir a janela de amostragem.
 */
static inline int onewire_read_bit(void)
{
    int bit;

    taskENTER_CRITICAL(&ow_spinlock);

    // Início do slot de leitura: mestre puxa para LOW rapidamente
    gpio_set_level(ONEWIRE_PIN, 0);
    ets_delay_us(6);

    // Libera linha para que o escravo possa responder
    gpio_set_level(ONEWIRE_PIN, 1);

    // Aguarda ~9µs até o ponto de amostragem
    ets_delay_us(9);
    bit = gpio_get_level(ONEWIRE_PIN);

    // Espera fim do slot (~55µs restantes)
    ets_delay_us(55);

    taskEXIT_CRITICAL(&ow_spinlock);

    return bit;
}

/**
 * @brief Escreve um byte (8 bits LSB-first) no barramento.
 *        NÃO usa mutex nem critical direto – assume que quem chamar
 *        já está com o mutex travado. A temporização está dentro
 *        das funções de bit com critical section.
 */
static inline void onewire_write_byte_internal(uint8_t data)
{
    for (int i = 0; i < 8; i++) {
        onewire_write_bit((data >> i) & 0x01);
    }
}

/**
 * @brief Lê um byte (8 bits LSB-first) do barramento.
 *        Também assume mutex já travado.
 */
static inline uint8_t onewire_read_byte_internal(void)
{
    uint8_t val = 0;

    for (int i = 0; i < 8; i++) {
        val |= (onewire_read_bit() << i);
    }

    return val;
}

/**
 * @brief Função pública para escrever um byte no barramento
 *        com proteção de mutex (thread-safe).
 */
void onewire_write_byte(uint8_t data)
{
    xSemaphoreTake(ow_mutex, portMAX_DELAY);
    onewire_write_byte_internal(data);
    xSemaphoreGive(ow_mutex);
}

/**
 * @brief Função pública para ler um byte no barramento
 *        com proteção de mutex (thread-safe).
 */
uint8_t onewire_read_byte(void)
{
    xSemaphoreTake(ow_mutex, portMAX_DELAY);
    uint8_t r = onewire_read_byte_internal();
    xSemaphoreGive(ow_mutex);
    return r;
}

Interpretação didática dessa arquitetura

O driver acima entrega três garantias fundamentais:

1. Ninguém interrompe uma operação OneWire

Ao usar taskENTER_CRITICAL, garantimos que nenhuma interrupção (Wi-Fi, Bluetooth, timers internos) vai alterar o tempo dos pulsos.
O protocolo OneWire depende de:

  • 6–10 µs para escrever “1”
  • 60 µs para escrever “0”
  • janelas de amostragem entre 6 e 15 µs

Qualquer preempção quebraria essas janelas.

2. Nenhuma task concorre pelo barramento

O mutex impede que tasks paralelas afetem o barramento.
Exemplo:

  • task de ronda lendo DS1904
  • task de diagnóstico testando presença
  • task de logs lendo ROM

Sem mutex → desastre.

3. Bloqueio apenas onde é realmente necessário

A critical section só envolve:

  • write_bit
  • read_bit
  • reset

Ou seja, trechos curtíssimos.


Comunicando com o iButton DS1904: Leitura do ID e do Relógio RTC

Com o driver OneWire já estável, thread-safe e protegido contra variações de temporização, podemos finalmente trabalhar diretamente com o iButton DS1904, que é o “coração” do nosso sistema de ronda. Ele combina duas funções que interessam muito em segurança:

  1. Identificação única (ROM de 64 bits)
    Cada DS1904 possui um código único mundialmente, usado para identificar o vigilante.
  2. RTC interno (Relógio de Tempo Real)
    Permite registrar o momento exato em que o vigilante passou pelo ponto de ronda, tornando o sistema auditável.

O protocolo do DS1904 segue estritamente o padrão OneWire: a primeira etapa de qualquer comunicação é enviar um comando ROM (Match ROM, Read ROM, Skip ROM), seguido pelos comandos de função específicos do dispositivo.


4.1 Estrutura da ROM de 64 bits

O código ROM possui:

  • 8 bits: Family Code
  • 48 bits: Número de série
  • 8 bits: CRC

Para o DS1904, o Family Code é 0x24.

Esse ID é perfeito para uso como chave de autenticação do vigilante.


4.2 Comandos ROM importantes

ComandoCódigoFunção
READ ROM0x33Lê ID quando há apenas um dispositivo no barramento
MATCH ROM0x55Seleciona um dispositivo específico
SKIP ROM0xCCUsa quando há apenas um dispositivo no barramento

Para um sistema com leitor único por ponto de ronda, podemos usar o SKIP ROM, mas como vamos registrar vigilantes diferentes, utilizaremos preferencialmente o READ ROM.


4.3 Comandos específicos do DS1904

ComandoCódigoDescrição
READ CLOCK0x66Lê o contador RTC (32 bits)
WRITE CLOCK0x99Ajusta o valor do clock

O RTC do DS1904 conta segundos desde um “epoch” interno do chip.


4.4 Função: Ler o ID (ROM)

bool ds1904_read_rom(uint8_t rom[8])
{
    if (!onewire_reset()) return false;

    // Comando READ ROM
    onewire_write_byte(0x33);

    for (int i = 0; i < 8; i++)
        rom[i] = onewire_read_byte();

    return true;
}

Explicação

  1. Reset – sincroniza barramento.
  2. READ ROM (0x33) – pede que o chip envie seu ID.
  3. Le os 8 bytes de ID.
  4. Guarda em rom[8].

Esse ID será usado para identificar qual vigilante tocou o iButton no ponto.


4.5 Função: Selecionar um DS1904 pelo ID (MATCH ROM)

Quando temos um único vigilante por ponto, ainda assim é bom garantir que estamos falando com o DS1904 correto, especialmente se o sistema crescer.

bool ds1904_match_rom(uint8_t rom[8])
{
    if (!onewire_reset()) return false;

    onewire_write_byte(0x55); // MATCH ROM

    for (int i = 0; i < 8; i++)
        onewire_write_byte(rom[i]);

    return true;
}

4.6 Função: Ler o relógio interno (RTC)

bool ds1904_read_clock(uint32_t *clock_value)
{
    uint8_t rom[8];

    // Identifica qual DS1904 está conectado
    if (!ds1904_read_rom(rom))
        return false;

    // Seleciona o dispositivo pelo ID
    if (!ds1904_match_rom(rom))
        return false;

    // Envia comando READ CLOCK
    onewire_write_byte(0x66);

    uint32_t val = 0;
    for (int i = 0; i < 4; i++) {
        val |= ((uint32_t)onewire_read_byte()) << (8 * i);
    }

    *clock_value = val;
    return true;
}

Explicação detalhada

  1. Primeiro lemos a ROM → identificamos o chip.
  2. Depois usamos MATCH ROM → selecionamos explicitamente esse DS1904.
  3. Enviamos READ CLOCK (0x66).
  4. O chip devolve 4 bytes LSB-first.
  5. Montamos o valor de 32 bits.

Esse valor é o timestamp interno do DS1904 (não é o mesmo epoch do Unix; a conversão vem depois).


4.7 Validação do vigilante

O sistema pode ter uma tabela (em flash, EEPROM ou servidor) com os IDs autorizados.
Por exemplo:

uint8_t vigilantes_autorizados[][8] = {
    { 0x24, 0xA2, 0x17, 0x03, 0x4B, 0x00, 0x00, 0x9F },
    { 0x24, 0x98, 0xFF, 0x22, 0x17, 0x00, 0x00, 0xE4 }
};

Após ler o ID da ROM, o sistema verifica:

  • se o vigilante é autorizado,
  • se está no ponto correto,
  • registra horário,
  • notifica o sistema para reiniciar o cronômetro de 10 minutos.

Agora já podemos:

  • Identificar o vigilante pelo ID único do DS1904.
  • Registrar o horário exato usando o RTC interno.
  • Integrar isso com o sistema de ronda.

Sistema de Ronda Multithread: Tasks, Notificações e Buzina de Alarme (10 minutos)

Agora vamos juntar tudo em uma aplicação prática com o ESP32 rodando FreeRTOS: um sistema de ronda em que o vigilante precisa se identificar com seu iButton DS1904 em cada ponto de verificação. Se ele demorar mais de 10 minutos entre um ponto e outro, o sistema aciona uma buzina de alarme.

A ideia central é usar:

  • Uma task para cuidar da ronda (leitura do DS1904, validação e registro);
  • Uma task para controlar a buzina, baseada em notificações de tarefa (task notifications);
  • O driver OneWire thread-safe que você já viu (mutex + critical sections).

Assim, cada “ação” importante fica em uma thread separada e bem definida:

  • Task 1 → “Ronda”: lê o iButton (OneWire + DS1904) e, a cada passagem válida, notifica a task da buzina para reiniciar o “relógio” de 10 minutos.
  • Task 2 → “Buzina”: fica bloqueada esperando notificações; se não receber nenhuma por 10 minutos, considera atraso na ronda e liga a buzina, mantendo-a ligada até a próxima notificação.

5.1 Conceito de Task Notification no FreeRTOS

Em vez de usar filas ou timers de software, vamos usar as notificações de tarefa:

  • ulTaskNotifyTake() pode bloquear uma task até:
    • Receber uma notificação (de outra task), ou
    • Expirar um tempo máximo de espera (timeout).

Esse padrão encaixa perfeitamente no nosso requisito:

  • “Se em 10 minutos não chegar uma nova notificação (ou seja, o vigilante não passou no próximo ponto), toca a buzina.”

5.2 Pinos de hardware

Vamos supor:

#define ONEWIRE_PIN  4   // Já usado no driver OneWire
#define BUZZER_PIN   5   // Saída digital para a buzina

A buzina pode ser acionada diretamente (se for de baixa corrente) ou via transistor / relé, dependendo do projeto.


5.3 Inicialização da buzina

Uma função simples para configurar o pino de saída:

static void buzzer_init(void)
{
    gpio_config_t io_conf = {
        .pin_bit_mask = (1ULL << BUZZER_PIN),
        .mode = GPIO_MODE_OUTPUT,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type = GPIO_INTR_DISABLE
    };
    gpio_config(&io_conf);

    gpio_set_level(BUZZER_PIN, 0); // buzina inicialmente desligada
}

static void buzzer_on(void)
{
    gpio_set_level(BUZZER_PIN, 1);
}

static void buzzer_off(void)
{
    gpio_set_level(BUZZER_PIN, 0);
}

5.4 Handles das tasks

Vamos declarar os handles globalmente:

static TaskHandle_t buzzerTaskHandle = NULL;
static TaskHandle_t rondaTaskHandle  = NULL;
  • buzzerTaskHandle: necessário para que a task de ronda possa chamar xTaskNotifyGive() e sinalizar eventos de “passagem de ponto”.
  • rondaTaskHandle: aqui não é estritamente necessário que outra task o notifique, mas deixamos declarado para extensões futuras (como supervisão externa, comandos remotos etc.).

5.5 Task da Buzina (esperando notificação ou timeout de 10 minutos)

Essa task é o “relógio de segurança” do sistema:

  • Ela espera receber uma notificação da task de ronda toda vez que o vigilante passa em um ponto;
  • Se 10 minutos se passarem sem notificação, ela entende que houve atraso e aciona a buzina.
void buzzer_task(void *pvParameters)
{
    const TickType_t dez_minutos = pdMS_TO_TICKS(10 * 60 * 1000);

    buzzer_off();

    for (;;)
    {
        // Espera por uma notificação por até 10 minutos
        uint32_t notified = ulTaskNotifyTake(pdTRUE, dez_minutos);

        if (notified > 0) {
            // Recebeu notificação ANTES de 10 minutos:
            // significa que o vigilante passou em mais um ponto a tempo.
            // Garante que a buzina esteja desligada e reinicia o ciclo.
            buzzer_off();
            // volta ao início do loop e espera novamente até 10min
        } else {
            // Timeout: NÃO houve notificação em 10 minutos.
            // Considera atraso de ronda -> aciona buzina.
            buzzer_on();

            // Agora espera até a próxima notificação para desligar a buzina.
            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
            buzzer_off();
            // Após desligar, volta ao loop e o ciclo recomeça (mais 10min)
        }
    }
}

Leitura didática:

  • ulTaskNotifyTake(pdTRUE, dez_minutos) bloqueia a task:
    • Se a task receber uma notificação antes de 10 minutos → notified > 0.
    • Se o tempo esgotar sem notificação → retorna 0 (timeout).
  • Cada chamada a xTaskNotifyGive(buzzerTaskHandle) feita pela task de ronda indica:
    • “O vigilante passou em um ponto. Resetar o contador de 10 minutos e manter/desligar buzina.”

5.6 Task da Ronda (leitura do DS1904 e reinício do timer de 10 minutos)

A task de ronda faz:

  1. Fica monitorando o barramento OneWire esperando um DS1904 ser conectado ao leitor;
  2. Quando detecta e lê um iButton:
    • Lê a ROM (ID do vigilante);
    • Lê o clock interno (timestamp);
    • Faz a validação/autenticação (pode ser uma checagem simples em memória ou em servidor);
    • Notifica a task da buzina para resetar o contador de 10 minutos;
    • Registra log (por enquanto via printf, mas você pode depois mandar para flash, SD, servidor etc.).

Um exemplo simples:

extern bool ds1904_read_rom(uint8_t rom[8]);
extern bool ds1904_read_clock(uint32_t *clock_value);
// As funções acima foram apresentadas no capítulo anterior.

void ronda_task(void *pvParameters)
{
    uint8_t  rom[8];
    uint32_t clock_val;

    for (;;)
    {
        // Tenta detectar um DS1904 no barramento
        if (onewire_reset())
        {
            // Lê ID do vigilante
            if (ds1904_read_rom(rom))
            {
                // Lê o relógio interno
                if (ds1904_read_clock(&clock_val))
                {
                    // Aqui você poderia validar o ID contra uma lista de vigilantes autorizados
                    // e registrar o horário de passagem deste ponto.
                    printf("Ronda: ID DS1904 = ");
                    for (int i = 0; i < 8; i++) {
                        printf("%02X ", rom[i]);
                    }
                    printf(" | Clock = %u segundos\n", (unsigned)clock_val);

                    // Notifica a task da buzina: vigilante passou neste ponto
                    if (buzzerTaskHandle != NULL) {
                        xTaskNotifyGive(buzzerTaskHandle);
                    }

                    // Aguarda o vigilante remover o iButton antes de aceitar nova leitura
                    vTaskDelay(pdMS_TO_TICKS(2000));
                }
            }
        }
        else
        {
            // Nenhum DS1904 detectado no momento, espera um pouco e tenta de novo
            vTaskDelay(pdMS_TO_TICKS(500));
        }
    }
}

Explicação didática:

  • onewire_reset() → verifica se há um dispositivo OneWire presente.
  • ds1904_read_rom() → obtém o ID do iButton (quem é o vigilante).
  • ds1904_read_clock() → obtém o timestamp do RTC interno (quando ele passou).
  • xTaskNotifyGive(buzzerTaskHandle) → informa à task da buzina: “recomece a contar 10 minutos a partir de agora; se ninguém passar até lá, dispare o alarme”.

5.7 Função app_main: amarrando tudo

Por fim, precisamos inicializar o driver OneWire, o pino da buzina e criar as tasks:

void app_main(void)
{
    // Inicializa barramento OneWire (driver thread-safe)
    onewire_init();

    // Inicializa o pino da buzina
    buzzer_init();

    // Cria a task da buzina
    xTaskCreate(
        buzzer_task,
        "buzzer_task",
        2048,
        NULL,
        5,
        &buzzerTaskHandle
    );

    // Cria a task da ronda
    xTaskCreate(
        ronda_task,
        "ronda_task",
        4096,
        NULL,
        6,              // prioridade ligeiramente maior que a da buzina, se desejar
        &rondaTaskHandle
    );

    // Opcional: você poderia, aqui, enviar uma notificação inicial para a buzina
    // caso queira começar a contagem imediatamente após o sistema iniciar:
    // if (buzzerTaskHandle != NULL) {
    //     xTaskNotifyGive(buzzerTaskHandle);
    // }
}

5.8 Resumindo a lógica do sistema

  • Quando o sistema inicia, a task da buzina começa esperando por uma notificação de “passagem de ponto”.
  • Cada vez que o vigilante encosta o DS1904:
    • A task de ronda lê o ID e o relógio;
    • Registra o evento (log);
    • Chama xTaskNotifyGive(buzzerTaskHandle) → reinicia o cronômetro de 10 minutos na task da buzina.
  • Se em 10 minutos não houver nova notificação:
    • ulTaskNotifyTake() volta por timeout;
    • A task da buzina chama buzzer_on();
    • O alarme fica ligado até que o vigilante finalmente passe em algum ponto (causando nova notificação).

Assim, você obtém um sistema de ronda simples, mas bem estruturado:

  • Driver OneWire robusto (mutex + critical section);
  • Integração com iButton DS1904 para identificação e horário;
  • Lógica de ronda baseada em tasks e notificações, sem necessidade de timers externos.

Modularizando o Sistema e Caminhos de Evolução

Até aqui trabalhamos tudo em um único bloco de código para facilitar a compreensão passo a passo. Em um projeto real, porém, é importante separar responsabilidades em módulos bem definidos, com headers claros e arquivos .c independentes. Isso facilita manutenção, testes, reuso em outros projetos e, principalmente, evita que o código de aplicação (como a lógica da ronda) fique misturado com detalhes de baixo nível do barramento OneWire ou do DS1904.

Uma organização simples e já bem profissional poderia ser algo como:

main/
  ├─ main.c          // app_main, criação das tasks
  ├─ onewire.c       // driver de barramento OneWire
  ├─ onewire.h
  ├─ ds1904.c        // funções específicas do iButton DS1904
  ├─ ds1904.h
  ├─ ronda.c         // lógica da ronda e task correspondente
  ├─ ronda.h
  ├─ buzzer.c        // controle da buzina e task
  └─ buzzer.h

A ideia é que main.c apenas “monte o Lego”: chame inicializações, crie tasks e faça a cola entre os módulos, enquanto cada arquivo .c se concentra em uma responsabilidade única, com a interface pública declarada no respectivo .h.


6.1 – Interface do driver OneWire (onewire.h / onewire.c)

O módulo onewire encapsula toda a complexidade do protocolo. A aplicação não precisa conhecer detalhes de slots de tempo, pulses de reset ou critical sections. Ela apenas chama funções como onewire_reset(), onewire_write_byte() e onewire_read_byte(). Um header enxuto e funcional poderia ser:

// onewire.h
#pragma once
#include <stdbool.h>
#include <stdint.h>

void onewire_init(void);
bool onewire_reset(void);
void onewire_write_byte(uint8_t data);
uint8_t onewire_read_byte(void);

Dentro de onewire.c fica tudo aquilo que já desenvolvemos: configuração do GPIO em open-drain, criação do mutex, uso do portMUX_TYPE e das critical sections, além das funções internas de leitura e escrita de bits. A aplicação vê um conjunto de funções simples, mas que internamente respeitam o timing e são thread-safe.

Essa separação também permite, futuramente, portar o driver para outro microcontrolador apenas reescrevendo onewire.c e mantendo o mesmo onewire.h, algo muito útil quando se pensa em bibliotecas reutilizáveis.


6.2 – Interface específica do DS1904 (ds1904.h / ds1904.c)

O DS1904 é apenas um dos muitos dispositivos OneWire possíveis. Por isso, faz sentido ter um módulo próprio para ele, que dependa de onewire.h mas esconda seus detalhes de comandos de função e formatação de dados. Um header típico poderia ser:

// ds1904.h
#pragma once
#include <stdbool.h>
#include <stdint.h>

#define DS1904_FAMILY_CODE 0x24

bool ds1904_read_rom(uint8_t rom[8]);
bool ds1904_match_rom(const uint8_t rom[8]);
bool ds1904_read_clock(uint32_t *clock_value);
bool ds1904_write_clock(uint32_t clock_value);
bool ds1904_is_family_ds1904(const uint8_t rom[8]);

No ds1904.c, você implementa as funções usando a API do onewire. A função ds1904_is_family_ds1904() pode, por exemplo, verificar se o primeiro byte da ROM (family code) é igual a 0x24, descartando dispositivos de outro tipo no mesmo barramento. As funções de leitura de clock seguem o padrão que já vimos: onewire_reset(), envio de comando ROM, envio do comando de função (READ CLOCK), leitura de 4 bytes e composição de um uint32_t.

Essa camada é onde você aplica toda a “semântica” do DS1904: saber que aqueles 4 bytes representam um contador de segundos desde um epoch específico, saber que o family code deve ser verificado, tratar eventuais erros de CRC se você desejar algo mais sofisticado, entre outros cuidados.


6.3 – Módulo da buzina (buzzer.h / buzzer.c)

O controle da buzina também merece um módulo próprio, mesmo sendo algo aparentemente simples. No header, você declara a interface tanto para inicialização quanto para a task de monitoramento:

// buzzer.h
#pragma once
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

extern TaskHandle_t buzzerTaskHandle;

void buzzer_init(void);
void buzzer_on(void);
void buzzer_off(void);
void buzzer_task(void *pvParameters);

No buzzer.c ficam a configuração do GPIO, as funções buzzer_on() e buzzer_off() e a buzzer_task() com a lógica da notificação e do timeout de 10 minutos. Desse modo, qualquer outro ponto do código que queira acionar ou garantir que a buzina esteja desligada não precisa saber qual pino é usado ou como a task está implementada. Basta chamar as funções declaradas no header.


6.4 – Módulo de ronda (ronda.h / ronda.c)

A lógica de negócio da ronda também deve ficar isolada em um módulo: é ele que sabe o que significa “passar no ponto de verificação”, como validar o DS1904, como registrar o horário e como notificar a buzina.

Um header simples poderia ser:

// ronda.h
#pragma once
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

extern TaskHandle_t rondaTaskHandle;

void ronda_task(void *pvParameters);

Dentro de ronda.c você inclui ds1904.h e implementa a lógica que já desenhamos: loop contínuo, tentativa de detecção de iButton, leitura de ROM e clock, validação de ID (por enquanto em tabela fixa, futuramente em configuração externa), registro em log e chamada a xTaskNotifyGive(buzzerTaskHandle) sempre que uma passagem for considerada válida. Essa divisão deixa claro que a task de ronda é uma “task de aplicação”, enquanto onewire e ds1904 são camadas de infraestrutura.


6.5 – Arquivo principal do projeto (main.c)

Com todos os módulos prontos, main.c se torna bastante simples, o que é uma boa característica em sistemas embarcados bem estruturados. Ele passa a ser essencialmente um “ponto de entrada” que faz a orquestração inicial:

// main.c
#include "onewire.h"
#include "ds1904.h"
#include "buzzer.h"
#include "ronda.h"

void app_main(void)
{
    onewire_init();
    buzzer_init();

    xTaskCreate(
        buzzer_task,
        "buzzer_task",
        2048,
        NULL,
        5,
        &buzzerTaskHandle
    );

    xTaskCreate(
        ronda_task,
        "ronda_task",
        4096,
        NULL,
        6,
        &rondaTaskHandle
    );

    // Se desejar iniciar já contando 10 minutos a partir do boot:
    // if (buzzerTaskHandle != NULL) {
    //     xTaskNotifyGive(buzzerTaskHandle);
    // }
}

Perceba como app_main() não sabe nada de detalhes de pulso OneWire, nem de como o DS1904 lê o clock, tampouco de como a buzina é acionada em nível elétrico. Ele apenas chama funções em uma sequência lógica. Essa é uma característica importante de uma boa arquitetura: cada camada conhece apenas o necessário da camada imediatamente abaixo.


6.6 – Extensões naturais do sistema de ronda

Uma vez modularizado, fica muito mais fácil evoluir esse sistema para cenários mais complexos, sem transformar o código em um monolito incontrolável. Algumas extensões bem naturais são:

Uma delas é o suporte a múltiplos pontos de ronda. Hoje, a lógica trata implicitamente “um ponto” e estima o atraso com base no tempo entre uma passagem e outra. Você pode avançar para um modelo onde cada ponto de ronda tenha um identificador lógico, talvez armazenado junto com a leitura do DS1904 ou mapeado em tabela. A partir disso, é possível validar não apenas se o vigilante está rodando dentro do tempo, mas também se ele segue uma sequência de pontos obrigatória (por exemplo, ponto 1 → ponto 2 → ponto 3 → retorno à base). Essa lógica continua no módulo ronda.c, que passa a decidir quando uma passagem é válida e quando deve ser registrada como falha.

Outra evolução interessante é o registro persistente dos eventos. Em vez de apenas imprimir no console com printf, você pode criar uma função de log que escreva em uma memória externa, cartão SD ou envie via rede para um servidor central, dependendo do contexto da aplicação. A beleza da modularização é que isso pode ser encapsulado em um módulo específico, como log_ronda.c, consumido pela task de ronda sem alterar a API de onewire ou ds1904.

Além disso, o controle da buzina pode ser sofisticado para um padrão mais amigável ou mais agressivo, como disparos intermitentes, escalonamento de níveis de alarme ou integração com outros atuadores (luzes de corredor, mensagens em um painel, envio de notificação para uma central). Tudo isso continua concentrado no módulo buzzer.c, mantendo o resto do sistema limpo.

Por fim, o tratamento de erros pode ser expandido para detectar e discriminar diferentes tipos de falhas, como ausência de iButton, iButton com family code incorreto, falha na leitura do clock, problemas no barramento (cabo rompido, curto), ou ainda falhas na própria task (por exemplo, watchdog acionando se a task travar). Cada tipo de erro pode ser mapeado para ações diferentes, desde simples logs até acionar alarmes distintos ou bloquear o sistema. Essas estratégias de robustez ganham clareza quando o código está modularizado e cada componente tem uma responsabilidade bem definida.


Estratégias de Validação da Ronda, Estruturas de Dados e Integração com Backend

Agora que o sistema básico está funcionando (driver OneWire, DS1904, tasks de ronda e buzina), vale a pena dar um passo para trás e enxergar isso como um sistema de rondas completo, não apenas “código que lê um iButton e toca uma buzina”. Aqui entra a parte de modelagem, validação e integração – que é o que transforma um protótipo em um produto de segurança utilizável.


7.1 Representando vigilantes, pontos e eventos de ronda

Na prática, o sistema lida com três tipos principais de informação:

  • Vigilante – quem está fazendo a ronda (identificado pelo ROM do DS1904).
  • Ponto de ronda – onde o vigilante está (ponto físico, posto, setor).
  • Evento de ronda – quando alguém passou em determinado ponto.

Mesmo em um ESP32, vale a pena ter uma estrutura minimamente organizada, por exemplo:

typedef struct {
    uint8_t rom[8];      // ID completo do DS1904
    uint32_t codigo;     // ID lógico interno (ex: 1001, 1002...)
} Vigilante_t;

typedef struct {
    uint16_t id_ponto;   // Identificador lógico do ponto (1, 2, 3...)
    uint32_t tempo_max;  // tempo máximo em segundos até o próximo ponto
} PontoRonda_t;

typedef struct {
    uint32_t timestamp_rtc;  // valor lido do DS1904
    uint16_t id_ponto;       // ponto onde ocorreu
    uint32_t id_vigilante;   // código lógico do vigilante
    bool atraso;             // true se chegou atrasado
} EventoRonda_t;

A lógica de negócio (em ronda.c) passa a trabalhar com esses tipos, em vez de apenas imprimir o ROM bruto. Isso facilita:

  • gravar eventos em memória;
  • enviar para um servidor;
  • reprocessar logs depois (auditoria).

7.2 Validando a sequência e o tempo entre pontos

No exemplo base, consideramos apenas “10 minutos sem notificação → buzina”. Mas um sistema de ronda real costuma ter:

  • Uma sequência obrigatória de pontos (por exemplo: 1 → 2 → 3 → 4 → 1 → …);
  • Tempos máximos diferentes entre cada trecho (ex: 5 min entre 1 e 2, 12 min entre 2 e 3, etc.).

Você pode modelar isso como um pequeno autômato de estados ou simplesmente como uma lista ordenada de pontos:

PontoRonda_t rota[] = {
    { .id_ponto = 1, .tempo_max = 300 },   // 5 minutos
    { .id_ponto = 2, .tempo_max = 600 },   // 10 minutos
    { .id_ponto = 3, .tempo_max = 480 },   // 8 minutos
    { .id_ponto = 4, .tempo_max = 900 }    // 15 minutos
};

static uint8_t indice_ponto_atual = 0;

Quando o vigilante passa em um ponto, a lógica pode verificar:

  1. Se ele está no ponto esperado (por exemplo, se esperávamos o ponto 2 e ele apareceu no 4, pode ser uma falha de rota).
  2. Quanto tempo passou desde o último evento válido:
    • timestamp_rtc do DS1904,
    • compara com o timestamp anterior,
    • verifica se excede tempo_max do trecho atual.

Isso permite:

  • tocar a buzina não apenas por “10 minutos absolutos”, mas por atraso em relação à rota;
  • marcar eventos com atraso = true para relatório posterior;
  • identificar “pulos de ponto” ou rondas feitas fora de ordem.

7.3 Tratamento de erros e robustez

Aplicações em campo sempre lidam com condições imperfeitas. Alguns exemplos:

  • o vigilante encosta o iButton de forma rápida demais e a leitura falha;
  • cabo do leitor OneWire com mau contato;
  • DS1904 de outro funcionário (não autorizado) é usado.

A aplicação deve tratar isso de forma clara:

  • Se onewire_reset() falhar repetidamente, você pode:
    • acender um LED de “falha de leitura”;
    • registrar um log de erro (cabo rompido, sem dispositivo, etc.).
  • Se o family code da ROM não for 0x24, você pode recusar a leitura e indicar “dispositivo inválido”.
  • Se o ID do vigilante não estiver na lista de autorizados:
    • ignorar o evento para fins de ronda;
    • opcionalmente registrar como tentativa de uso não autorizado.

Essa robustez faz diferença enorme na prática: evita falsas acusações ao funcionário (“não passou no ponto”) quando, na verdade, era um problema de cabo ou leitor.


7.4 Integração com backend (HTTP, MQTT, etc.)

Um ESP32 conectado (Wi-Fi/Ethernet) abre espaço para monitorar a ronda em tempo real:

  • cada EventoRonda_t pode ser:
    • enviado via HTTP POST para um servidor;
    • publicado em um tópico MQTT (por exemplo, empresa/ronda/ponto1);
    • armazenado localmente em flash/SD e sincronizado depois.

Exemplo conceitual em MQTT (sem entrar no código de rede):

  • Tópico: ronda/vigilante/<id_vigilante>/ponto/<id_ponto>
  • Payload (JSON): { "timestamp_rtc": 123456789, "atraso": false, "rota_ok": true }

No servidor, um painel web pode:

  • desenhar timeline da ronda;
  • destacar atrasos;
  • gerar relatórios diários/semanais.

A modularização ajuda muito: ronda.c apenas chama uma função “enviar_evento_ronda()” que você implementa em um módulo de comunicação. Assim, mudar de HTTP para MQTT ou vice-versa não quebra o núcleo do sistema.


7.5 Segurança lógica e prevenção de fraude

Um sistema de ronda também precisa se proteger contra alguns comportamentos maliciosos, por exemplo:

  • um vigilante tentando “burlar” o sistema encostando o iButton várias vezes no mesmo ponto;
  • alguém copiando um DS1904 ou tentando usar um token não autorizado.

Algumas ideias de mitigação:

  • Tempo mínimo entre leituras no mesmo ponto: por exemplo, ignorar leituras no mesmo ponto em janelas menores que X segundos.
  • Cross-check com dados externos: cruzar os horários de ronda com controle de acesso (catraca, portão) para ver se o vigilante realmente estava na empresa.
  • Assinatura dos registros: se você envia dados a um backend, pode usar um segredo compartilhado ou um token para evitar que um dispositivo não autorizado injete eventos falsos na rede.

A base tecnológica (OneWire + DS1904 + ESP32) já te dá uma fundação forte; o restante é desenho de políticas.


7.6 Amarrando tudo com o barramento OneWire

Fechando o ciclo:

  • O OneWire simplifica o hardware: um único fio de dados + GND, resistor de pull-up e encapsulamento robusto (iButton).
  • O driver thread-safe com mutex + critical sections torna a solução confiável mesmo com multitarefa pesada.
  • O DS1904 entrega identificação única e RTC no mesmo dispositivo, perfeito para sistemas de ronda.
  • As tasks do FreeRTOS e as notificações permitem uma lógica clara:
    • task de ronda detecta passagem → notifica task da buzina → reseta janela de 10 minutos;
    • task da buzina vigia atrasos e aciona o alarme.

A partir daqui, o que diferencia um protótipo de um produto comercial é principalmente:

  • qualidade da modelagem de dados;
  • robustez contra falhas;
  • capacidade de integrar com sistemas maiores (servidor, app, dashboard).

Resumo Final

O barramento OneWire se destaca por sua simplicidade elétrica — apenas um fio de dados mais o GND — e pela robustez dos seus dispositivos, como os iButtons. Essa combinação torna a tecnologia especialmente adequada para sistemas de ronda e controle de presença em ambientes industriais e de segurança. No núcleo do OneWire estão operações temporizadas em microssegundos (reset, presence pulse, read/write), que precisam ser rigorosamente respeitadas para que haja comunicação estável. Por isso, a implementação em microcontroladores multitarefa, como o ESP32, exige mecanismos que garantam tanto exclusão mútua entre tarefas quanto proteção contra interrupções que possam distorcer a temporização. A solução prática é combinar mutex (para evitar concorrência entre tasks) com critical sections (para desabilitar interrupções nos momentos em que o protocolo requer precisão temporal).

O iButton DS1904 é particularmente atraente para aplicações de ronda porque reúne dois elementos essenciais: um ID único mundialmente e um RTC interno que fornece um timestamp confiável sempre que o vigilante encosta o dispositivo no leitor. Isso permite que o sistema identifique quem passou e exatamente quando passou em cada ponto de verificação, registrando eventos com precisão. Ao modularizar o código em camadas — onewire (nivel baixo), ds1904 (interpretação do dispositivo), ronda (lógica de negócio) e buzzer (atuador) — obtém-se um sistema flexível, fácil de manter e altamente reutilizável. A task de ronda lê o DS1904, valida o vigilante, gera um evento e envia uma notificação para a task da buzina, que reinicia uma janela de tempo pré-definida (por padrão, 10 minutos). Se nenhuma nova notificação chegar dentro desse período, a buzina é acionada até que uma nova passagem seja registrada. Essa lógica usa diretamente recursos do FreeRTOS como ulTaskNotifyTake() e xTaskNotifyGive(), que fornecem uma forma eficiente de sincronização entre tasks sem necessidade de filas ou timers adicionais.

Uma vez estruturado, o sistema pode evoluir naturalmente: é possível incluir vários pontos de ronda, sequências obrigatórias, tempos distintos entre pontos, tratamento de erros avançado (device inválido, cabo danificado, leitura incompleta), registro persistente em memória ou cartão SD, e integração com servidores remotos via HTTP ou MQTT. Cada leitura DS1904 pode se transformar em um evento auditável, permitindo painéis administrativos que acompanham a ronda em tempo real. O sistema também pode incorporar segurança lógica para prevenir abusos, como leituras repetidas no mesmo ponto, uso de tokens não autorizados ou tentativas de fraude.

Em síntese, o OneWire associado ao DS1904 e ao ESP32 forma uma solução elegante, robusta e de fácil expansão para implementar rondas profissionais. A comunicação é simples no hardware, confiável no software graças ao tratamento cuidadoso do protocolo, e rica em possibilidades quando inserida em uma arquitetura modular e multitarefa. O resultado é um sistema de ronda eficiente, escalável e pronto para usos reais em segurança patrimonial, industrial e corporativa.

0 0 votos
Classificação do artigo
Inscrever-se
Notificar de
guest
0 Comentários
mais antigos
mais recentes Mais votado
Feedbacks embutidos
Ver todos os comentários

Related Post

0
Adoraria saber sua opinião, comente.x