Persistindo dispositivos e organizando nosso projeto Dart com Clean Architecture – Série Braço Robótico #4

Neste quarto artigo da nossa série sobre controle de um braço robótico usando Arduino, Dart e Flutter, vamos avançar significativamente rumo a um projeto robusto, escalável e profissional.

Nessa etapa, nosso foco é persistir e gerenciar os dispositivos conectados, além de organizar melhor nosso código aplicando os conceitos da arquitetura limpa (Clean Architecture).

📌 Todo o código desenvolvido neste artigo está disponível no repositório Git:
👉 https://github.com/brasizza/projeto_braco_robo.git


Por que persistir os dispositivos?

Ao gerenciar conexões com múltiplas placas ESP32 (Vespa), precisamos manter informações detalhadas e persistentes sobre cada dispositivo. Isso garante que nosso sistema tenha controle sobre quem está conectado, facilitando ações como identificação única, gerenciamento individualizado e remoção adequada caso haja desconexão.

No nosso projeto, optamos por utilizar um modelo simples, porém eficiente: a entidade Device.


Entidade Device

O modelo (Device) representa cada dispositivo conectado e contém informações essenciais para a identificação e interação com o mesmo.

Exemplo do nosso modelo:

class Device {
  String deviceId;
  String deviceType;
  WebSocket? socket;

  Device({
    required this.deviceId,
    required this.deviceType,
    this.socket,
  });

  Map<String, dynamic> toMap() {
    return {
      'deviceId': deviceId,
      'deviceType': deviceType,
    };
  }

  factory Device.fromMap(Map<String, dynamic> map) {
    return Device(
      deviceId: map['deviceId'],
      deviceType: map['deviceType'],
    );
  }
}

Observe que optamos por armazenar o deviceId e o deviceType, facilitando consultas futuras e gerando uma identificação clara de cada braço robótico conectado.


Gerenciando nossos Devices

Para facilitar a manipulação e armazenamento dos nossos dispositivos conectados, criamos uma classe chamada GerenciarDevices. Ela é responsável por cadastrar novos dispositivos, removê-los caso desconectem e listar todos os dispositivos ativos.

Veja um trecho dessa classe:

class GerenciarDevices {
  final Map<String, Device> _devices = {};

  void cadastrarDevice(Device device) {
    if (!_devices.containsKey(device.deviceId)) {
      _devices[device.deviceId] = device;
    }
  }

  void removeDevice(String deviceId) {
    if (_devices.containsKey(deviceId)) {
      _devices[deviceId]?.socket?.close();
      _devices.remove(deviceId);
    }
  }

  List<Device> mostrarDevices() => _devices.values.toList();
}

Implementação do SocketServer em Dart

Na camada de infraestrutura (infra), criamos um servidor WebSocket (SocketServer) eficiente que gerencia conexões, desconexões, e timeouts. Esse servidor interage diretamente com nossa classe de gerenciamento (GerenciarDevices).

Pontos importantes do SocketServer:

  • Recepção das conexões:
Future<void> _pegarRequequisicao(HttpRequest request) async {
  WebSocket socket = await WebSocketTransformer.upgrade(request);
  socket.pingInterval = Duration(seconds: 5);

  String? deviceId = request.headers.value('deviceID');
  String? deviceType = request.headers.value('deviceType');

  if (deviceId == null || deviceType == null) {
    socket.close(WebSocketStatus.goingAway);
  } else {
    var device = Device(deviceId: deviceId, deviceType: deviceType, socket: socket);
    gerenciarDevices.cadastrarDevice(device);
    print("Socket foi conectado, bem vindo: $deviceId");

    socket.listen((mensagem) {
      print("Mensagem do socket: $mensagem");
    }, onError: (erro) {
      print("Erro na conexão: $erro");
      gerenciarDevices.removeDevice(deviceId);
    }, onDone: () {
      print("Socket desconectado");
      gerenciarDevices.removeDevice(deviceId);
    });
  }
}

Tratamento inteligente de conexões:
Garantimos estabilidade com reconexões automáticas em caso de perda de conexão e identificamos claramente cada conexão através do deviceId.

Listagem de dispositivos via HTTP:
Implementamos também uma rota HTTP simples que permite listar rapidamente todos os dispositivos conectados, facilitando monitoramento externo:

void gerenciarHttp(HttpRequest request) {
  if (request.uri.path == '/devices') {
    request.response
      ..headers.contentType = ContentType.json
      ..statusCode = HttpStatus.ok
      ..write(json.encode({
        'devices': gerenciarDevices.mostrarDevices().map((d) => d.toMap()).toList()
      }))
      ..close();
  }
}

Aplicando Clean Architecture (Arquitetura Limpa)

Organizar nosso projeto utilizando a Arquitetura Limpa foi essencial para torná-lo modularizado, fácil de expandir e manter. Adotamos a seguinte estrutura básica no projeto:

  • Domain
    • Entidades (Device)
    • Casos de uso (GerenciarDevices)
  • Infra
    • Camada responsável pelo gerenciamento do servidor WebSocket (SocketServer)
  • Main
    • Inicialização do projeto e injeção de dependências.

Essa separação clara de responsabilidades permite uma escalabilidade muito maior, onde mudanças em uma camada não impactam diretamente as outras.


Próximos Passos

Com nosso projeto já estruturado e organizado, estamos preparados para dar um passo adiante e desenvolver o aplicativo em Flutter. O app permitirá controlar remotamente e intuitivamente nosso braço robótico utilizando a comunicação que criamos via WebSocket.

Continue acompanhando a série e domine a criação de projetos completos usando Arduino, Dart e Flutter!

Publicar comentário