Um Chat via Bluetooth para Android, MacOS e Windows

File:ClassicBluetoothVsLowEnergyBluetooth.png

Introdução

A inspiração para este artigo surgiu durante uma POC que estamos fazendo com um cliente implementando uma solução RFID com mobile.

Mas qual a relação do RFID com Bluetooth? Bem, a principio nenhuma relação direta, exceto pelo fato de que a ampla maioria dos leitores RFID que suportam integração para mobile (Android) o fazem via comunicação Bluetooth SPP (serial port profile).

Bluetooth SPP é um dos muitos profiles suportados pela tecnologia Bluetooth. Na prática, estamos falando de comunicação serial (socket) sobre Bluetooth. Neste link você encontra uma lista de todos os profiles suportados oficialmente pelo Bluetooth standard, lembrando que diferentes devices suportam diferentes conjuntos de profiles.

Com isso chegamos ao chat via Bluetooth…

Bluetooth no Delphi e C++ Builder

O RAD Studio oferece suporte nativo para Bluetooth e BluetoothLE (low energy). Apenas para contextualizar, BluetoothLE é diferente de Bluetooth, e se presta a outras necessidadesd – principalmente a implementações de dispositivos IoT, como beacons e etc…

Voltando ao Bluetooth standard, este que você utiliza quando faz o pareamento de dois devices, ou ainda de seu smartphone com o rádio de seu carro, observe que no título do artigo não está mencionado suporte para iOS, e por uma razão bastante simples: o iOS não implementa Bluetooth SPP.

Por qual razão? Bem, perdemos a oportunidade de perguntar ao Steve (Jobs), então temos que nos contentar apenas com esta lista de profiles suportados: https://support.apple.com/en-us/HT204387.

Obviamente há uma explicação científica para o tema. Caso alguém saiba mais a respeito, acrescente aos comentários deste artigo!Com isso, o suporte a Bluetooth e BLE no RAD Studio também apresenta esta “limitação”, digamos assim.

Mais detalhes sobre versões e protocolos Bluetooth no Delphi e C++ estão neste link da documentação: http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Using_Bluetooth.

Código pelo Amor de Deus?

Sim, já estamos chegando lá. Mas esta teoria é importante para compreender o que estamos fazendo.Para estabelecer uma conexão utilizando SPP, leia-se porta serial, utilizamos basicamente uma conexão socket, muito parecido com qualquer solução socket que você já tenha implementado.

Quando estabelecemos uma conexão socket via Bluetooth (formalmente um rfcomm server e respectivo client), devemos especificar um UUID. Esse cara funciona como um identificador único para que o client possa se conectar ao serviço correto, já que podemos criar múltiplos canais de comunicação de maneira simultânea. Uma boa discussão sobre este tema você encontra aqui: http://stackoverflow.com/questions/13964342/android-how-do-bluetooth-uuids-work.

Basicamente, existem algumas UUID previamente definidos (https://www.bluetooth.com/specifications/assigned-numbers/service-discovery), os quais atendem serviços específicos, e você pode definir seu próprio identificar para uma aplicação em particular.

Felizmente nosso TBluetooth implementa tudo o que precisamos, e de maneira simples – só pra variar 😉

Agora sim, código!

Antes de mais nada, lembre-se que os devices devem estar devidamente pareados para que a conexão Bluetooth ocorra. Existem forma de automatizar o pareamento, mas não para todas as plataformas.

Em linhas gerais, do ponto de vista do “client”, tudo que temos que fazer é estabelecer a conexão socket, e então enviar o que desejamos.Já do lado “server”, utilizaremos um TTask para manter o Socket Server “ouvindo” em segundo plano.

Este é o conceito geral.

Aqui temos o código de ambas as classes, um “writer” e um “reader“:

unit uBlueChat;

interface

uses SysUtils, System.Classes, System.StrUtils, System.Threading,
  System.Bluetooth, System.Types, System.Generics.Collections,
{$IFDEF MACOS}
  Macapi.CoreFoundation,
{$ENDIF}
  FMX.Memo;

type
  TTextEvent = procedure(const Sender: TObject; const AText: string;
    const aDeviceName: string) of object;

type
  TBlueChatWriter = class(TComponent)
  private
    fSendUUID: TGUID;

    fDeviceName: string;
    fBlueDevice: TBluetoothDevice;

    fSendSocket: TBluetoothSocket;
    procedure SetBlueDevice(Value: string);
  public
    constructor Create(AOwner: TComponent); override;

    function SendMessage(sMessage: string): boolean;
    property DeviceName: string read fDeviceName write SetBlueDevice;
  end;

type
  TBlueChatReader = class(TComponent)
  private
    fReadUUID: TGUID;

    fDeviceName: string;
    fTaskReader: ITask;

    fReadSocket: TBluetoothSocket;
    fServerSocket: TBluetoothServerSocket;

    FOnTextReceived: TTextEvent;
    procedure SetOnTextReceived(const Value: TTextEvent);
  public
    constructor Create(AOwner: TComponent); override;
    procedure StartReader;
    procedure StopReader;

    property OnTextReceived: TTextEvent read FOnTextReceived
      write SetOnTextReceived;
    property DeviceName: string read fDeviceName write fDeviceName;
  end;

implementation

{ TBlueChatSend }

const
  cTimeOut: integer = 5000;

function FindBlueDevice(DeviceName: string): TBluetoothDevice;
var
  i: integer;
  aBTDeviceList: TBluetoothDeviceList;
begin
  aBTDeviceList := TBluetoothManager.Current.CurrentAdapter.PairedDevices;
  for i := 0 to aBTDeviceList.Count - 1 do
    if aBTDeviceList.Items[i].DeviceName = DeviceName then
      exit(aBTDeviceList.Items[i]);
  Result := nil;
end;

constructor TBlueChatWriter.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  fBlueDevice := nil;
  fSendSocket := nil;

  fSendUUID := StringToGuid('{14800546-CF05-481F-BE41-4EC0246D862D}');
end;

function TBlueChatWriter.SendMessage(sMessage: string): boolean;
begin
  try
    try
      if fBlueDevice = nil then
        raise Exception.Create('Select a bluetooth device first...');

      fSendSocket := fBlueDevice.CreateClientSocket(fSendUUID, False);
      if fSendSocket = nil then
        raise Exception.Create('Cannot create client socket to ' + fDeviceName);

      fSendSocket.Connect;
      if fSendSocket.Connected then
        fSendSocket.SendData(TEncoding.ASCII.GetBytes(sMessage))
      else
        raise Exception.Create('Cannot connect to ' + fDeviceName);

      Result := True;
    except
      on E: Exception do
        raise Exception.Create('Exception raised sending message: ' +
          E.Message);
    end;
  finally
    if fSendSocket.Connected then
      fSendSocket.Close;
    FreeAndNil(fSendSocket);
  end;
end;

procedure TBlueChatWriter.SetBlueDevice(Value: string);
begin
  if Value <> fDeviceName then
  begin
    fDeviceName := Value;
    fBlueDevice := FindBlueDevice(fDeviceName);
    if fBlueDevice = nil then
      raise Exception.Create('Cannot find device ' + fDeviceName);
  end;
end;

{ TBlueChatRead }

constructor TBlueChatReader.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  fTaskReader := nil;
  fReadSocket := nil;
  fServerSocket := nil;

  fReadUUID := StringToGuid('{14800546-CF05-481F-BE41-4EC0246D862D}');
end;

procedure TBlueChatReader.SetOnTextReceived(const Value: TTextEvent);
begin
  FOnTextReceived := Value;
end;

procedure TBlueChatReader.StartReader;
var
  Data: TBytes;
begin
  if fServerSocket <> nil then
    FreeAndNil(fServerSocket);

  fServerSocket := TBluetoothManager.Current.CurrentAdapter.CreateServerSocket
    ('FMXBlueChat', fReadUUID, False);

  fTaskReader := TTask.Create(
    procedure()
    begin
      fReadSocket := nil;
      while (fTaskReader.Status <> TTaskStatus.Canceled) do
      begin
        try
          fReadSocket := fServerSocket.Accept(cTimeOut);
          if (fReadSocket <> nil) and (fReadSocket.Connected) then
          begin
            Data := fReadSocket.ReceiveData;
            if Length(Data) > 0 then
            begin
              if Assigned(FOnTextReceived) then
                FOnTextReceived(Self, TEncoding.ASCII.GetString(Data),
                  fDeviceName);
            end;
          end;
        except
          on E: Exception do
          begin
            FreeAndNil(fReadSocket);
            raise Exception.Create('Exception raised receiving message: ' +
              E.Message);
          end;
        end;
        FreeAndNil(fReadSocket);
      end;
    end);
  fTaskReader.Start;
end;

procedure TBlueChatReader.StopReader;
begin
  if fTaskReader <> nil then
    if fTaskReader.Status <> TTaskStatus.Canceled then
      fTaskReader.Cancel;
end;

end.

A interface visual, por sua vez, é algo bastante simples. Estou certo que você vai compreender facilmente como tudo está funcionando investigando o exemplo que estou disponibilizando nos links abaixo, mas o essencial mesmo está nestas classes.

Advertisements

Lançamento Oficial do RAD Studio XE7 (Delphi e C++ Builder)

A Embarcadero está lançando a release de número 7 da série XE do Delphi e C++ Builder, com foco em multi-dispositivos, aplicações conectadas e computação paralela.

Esta versão aperfeiçoa nosso suporte para aplicações móveis, e amplia o suporte para aplicações conectadas com Bluetooth e Bluetooth LE completamente integrados a RTL. Isso vai nos permitir a construção de apps que se conectam ao mundo, a chamada “Internet das Coisas“.

Neste artigo você encontrará as principais novidades da versão XE7 para todas as áreas do produto, desde aplicações desktop até sensores bluetooth de monitoramento cardíaco 😉

FireUI

Trata-se de um enorme avanço na maneira de construir interfaces para multi-dispositivos. Em outras palavras, estamos tornando ainda mais simples para você criar uma aplicação que vai se comportar adequadamente em qualquer dispositivo (ou tamanho de tela). O “Multi-Device Designer”, aliado a novos componentes com o “MultiView” e aos “Behavior Services” (serviços que captam o comportamento do device em tempo de execução) permitem a criação de uma única app com interfaces apropriadas a cada dispositivo. Sim, isto significa um único formulário sendo compilado para desktop, smartphones, tablets, óculos, relógios e toda sorte de dispositivo onde voce encontrar um Android ou iOS rodando…

Veja neste vídeo introdutório como tornamos isso possível: https://www.youtube.com/watch?v=QOfmoAtqh9E

Aplicações Conectadas

O novo suporte a bluetooth, totalmente integrado a RTL, permite conexão com qualquer device (gadget) que suporte esta tecnologia. Estamos falando de interagir com relógios inteligentes, medidores de batimento cardíaco, e uma infinidade de dispositivos que já estão no mercado. Suportamos bluetooth tradicional e também bluetooth LE (low energy), para aplicações de aproximação por exemplo.

Além disso, através do AppTethering, você pode conectar suas aplicações existentes em VCL ou FMX com aplicações móveis, via WiFi (introduzido no XE6) ou o novo suporte a Bluetooth.

Nesta página você encontra uma série de exemplos e vídeos de integrações com os mais diversos dispositivos, utilizando Delphi e C++ Builder: http://www.embarcadero.com/br/products/rad-studio/gadgets-wearables

Aqui um vídeo demonstrando a integração com um sensor para prática de esportes: https://www.youtube.com/watch?v=oeyGzuC_QqU#t=53

Parallel Programming Library

Disponível para VCL e FireMonkey, permite o uso efetivo de equipamentos multi-core, incluindo aqui dispositivos móveis! Estamos falando de “parallel for loops, futures, task, thread pooling, automatic task scheduling” e outros recursos que permitem, por exemplo, executar uma consulta complexa em segundo plano sem que sua interface fique bloqueada, de uma maneira muito simples.

Neste vídeo você pode observar uma implementação do algoritmo do “Jogo da Vida” (http://pt.wikipedia.org/wiki/Jogo_da_vida) proposto pelo matemático John Horton Conway em 1970. Daí o nome de “Conway’s Game of Life”: https://www.youtube.com/watch?v=Ni3JDxNFiiw. Observe no detalhe as CPUs “trabalhando” em paralelo… todo o poder de um servidor multi-core em suas mãos!

Enterprise Mobility Services (EMS)

Refere-se a uma infraestrutura pronta para aplicações distribuídas, altamente escalável, com módulos customizados carregáveis, controle de usuários e estatísticas de acesso, e armazenamento de dados.

Ficou muito difícil de compreender? Mas na verdade não é… vamos abordar por outro ângulo:

Recordem que no XE6 introduzimos um novo framework para suportar BaaS (Backend as a Service). O framework BaaS permite, entre outras coisas, que você armazene dados nas núvens em um dos provedores suportados (Kinvey, Parse, App42), em formato de objetos JSON, sem que você tenha que construir uma aplicação servidora para isso. São infra-estruturas prontas que oferecem gestão de usuários, estatísticas de uso e armazenagem de dados, entre outras como push notification para qualquer plataforma. Neste post você aprende como criar sua primeira app conectada a um serviço como estes: http://blogs.embarcadero.com/sarinadupont/2014/04/15/introducing-rad-studio-xe6-and-baas/.

Muito bem, nosso EMS, presente no Delphi e C++ Builder XE7, permite que você crie e hospede seu próprio serviço BaaS. Seria algo como possuir seu “Kinvey” ou “Parse” particular, incluindo serviços como gestão de usuários, estatísticas de uso das APIs e armazenamento de dados, tudo em um único pacote.

Um servidor EMS pode ser distribuído como um módulo do IIS ou do Apache, neste momento apenas para plataforma Windows.

O RAD Studio traz uma licença de desenvolvimento do EMS para até 5 usuários, a partir da edição Professional. Este produto terá uma licença de distribuição sempre baseada em quantidade de usuários finais. Ou seja, este produto deverá ser licenciado a parte do RAD Studio, Delphi e C++ Builder para distribuição final. E isto se justifica porque, além da tratar-se de uma infraestrutura pronta para distribuição imediata, ele ainda traz de forma integrada licenças do Interbase Server (para servidores e desktops) e Interbase ToGo (para mobile), ambos com criptografia e capacidade ilimitadas.

Em resumo: uma solução completa que inclui suporte a APIs customizadas, acesso a dados, gestão de usuários, estatísticas e armazenamento, tudo integrado e pronto para deployment. E o melhor, tudo baseado em tecnologia standard, acessível por qualquer outra plataforma ou linguagem de desenvolvimento.

ps: em outras tecnologias você pode também encontrar esta infraestrutura referenciada como MEAP (Mobile Enterprise Application Platform).

Maiores detalhes sobre formas de licenciamento e custos em breve.

IDE, VCL, FireMonkey, FireDAC e muito mais!

Vou tentar resumir aqui os principais pontos em termos de novidades e melhorias para a IDE, VCL, FMX, FireDAC e Utilitários. Todos os demais detalhes você pode conferir diretamente nesta página do Wiki do produto: http://goo.gl/7fnf9K.

IDE = Guided Tours (permite aprender sobre o RAD Studio de maneira interativa), gerenciamento do “Entitlement List” via Project Options, controle de versão GIT integrado, além de SubVersion, adição de bibliotecas Java externas via Project Manager…

FMX = Mapeamento nativo de controles iOS (TEdit e TCalendar), suporte para Multi-Monitor, Full-Screen para Android KitKat, Pull-to-Refresh para TListView no iOS e Android, FMX Save State…

VCL = Novo JumpList para o ToolBar no Win7 e Win8, TParallel (System.Threading), AppTethering e Bluetooth – incluindo BlueTooth LE para Windows 8, OmniXML para melhor performance no processamento de arquivos XML…

FireDAC = Suporte para streaming no MSSQL, suporte para ETL através do novo TFDBatchMove (TFDBatchMove, TFDBatchMoveTextReader, TFDBatchMoveTextWriter, TFDBatchMoveDataSetReader, TFDBatchMoveDataSetWriter, TFDBatchMoveSQLReader, and TFDBatchMoveSQLWriter), novo driver específico para IBLite/IBToGo, novos recursos de serialização, leitura e gravação de Metadata (CreateTable, GenerateCreateTable, GenerateDropTable, etc.)…

Novo PAServer para MAC = com suporte a multiplas instâncias e interface visual de configuração em OSX…

Novo Java2OP.exe = Java para Object Pascal! Permite a geração das classes correspondentes em Object Pascal para chamadas a bibliotecas Java, podendo importar um JAR, uma classe ou um subset de uma classe da API do Android…

Resumo

Como vocês podem ver trata-se de uma das mais completas versões já lançadas pela Embarcadero. E observem que ainda não abordamos todos os assuntos, como suporte a “Bluetooth Proximity” nativo, assunto de nosso próximo post/vídeo.

Tentado em provar o novo RAD Studio, Delphi ou C++ Builder XE7??? O trial já está disponível para download: https://downloads.embarcadero.com/free/rad_studio

E antes de encerrar, um lembrete especial: todas estas novas capacidades, entre tantos outros assuntos interessasntes, serão abordadas em detalhes em nosso Embarcadero Conference, logo mais em 16 de Outubro. Fique ligado neste hotsite: http://www.embarcaderoconference.com.br para novidades.

Abraços e até a próxima!