Mudanças entre as edições de "PTC29008: Projeto 1: um protocolo de comunicação"

De MediaWiki do Campus São José
Ir para navegação Ir para pesquisar
 
(47 revisões intermediárias por 2 usuários não estão sendo mostradas)
Linha 1: Linha 1:
[[Projeto_1:_Sincronização_e_enquadramento|Próxima aula]]
+
<!--[[PTC29008:_Projeto_1:_Mecanismos_Básicos_de_um_Protocolo_de_Comunicação|Próxima aula]]-->
 +
[[PTC29008:_Projeto_1:_Integração_com_subsistema_de_rede_do_Linux|Próxima aula]]
  
  
 
__toc__
 
__toc__
 +
 +
 +
* [https://classroom.github.com/g/prvMWncA Projeto no Github Classroom]
 +
  
 
Um protocolo de comunicação está relacionado aos mecanismos necessários para a entrega de mensagens entre duas aplicações quaisquer. Considerando uma arquitetura de redes em camadas como TCP/IP, protocolos de comunicação correspondem às camadas de enlace até transporte. Questões como garantia de entrega, controle de sequência, tratamento de erros, sincronização, estabelecimento e término de sessão, multiplexação e delimitação de mensagens, entre possivelmente outras, fazem parte do projeto de tais protocolos. Para introduzir o projeto de um protocolo de comunicação, o primeiro projeto da disciplina envolve um protocolo para estabelecimento de enlace sem-fio ponto-a-ponto.
 
Um protocolo de comunicação está relacionado aos mecanismos necessários para a entrega de mensagens entre duas aplicações quaisquer. Considerando uma arquitetura de redes em camadas como TCP/IP, protocolos de comunicação correspondem às camadas de enlace até transporte. Questões como garantia de entrega, controle de sequência, tratamento de erros, sincronização, estabelecimento e término de sessão, multiplexação e delimitação de mensagens, entre possivelmente outras, fazem parte do projeto de tais protocolos. Para introduzir o projeto de um protocolo de comunicação, o primeiro projeto da disciplina envolve um protocolo para estabelecimento de enlace sem-fio ponto-a-ponto.
Linha 14: Linha 19:
  
 
O projeto 1 envolve o desenvolvimento de um protocolo de comunicação usando esse transceiver RF, de forma a oferecer um serviço de comunicação com essas características.
 
O projeto 1 envolve o desenvolvimento de um protocolo de comunicação usando esse transceiver RF, de forma a oferecer um serviço de comunicação com essas características.
 
= O transceiver RF APC 220 =
 
 
O transceiver RF a ser utilizado se chama [http://www.appcon.com.cn/en/productshow.php?cid=6&id=28 APC 220]. Alguns documentos podem ser úteis:
 
* [http://www.dfrobot.com/image/data/TEL0005/APC220_Datasheet.pdf Data sheet]
 
* [http://www.robotshop.com/media/files/PDF/dfrobot-apc220-manual.pdf Manual]
 
* [http://blog.filipeflop.com/wireless/modulo-rf-apc220-arduino.html Um tutorial]
 
* [http://www.dfrobot.com/wiki/index.php?title=APC220_Radio_Data_Module%28SKU:TEL0005%29 Outro tutorial]
 
  
 
= Um primeiro experimento =
 
= Um primeiro experimento =
  
* [http://tele.sj.ifsc.edu.br/~msobral/ptc/proj1/serial.tgz A classe Serial]
+
* [http://tele.sj.ifsc.edu.br/~msobral/ptc/proj1/serial.tgz A classe Serial (C++)]
 
+
* [https://pypi.org/project/pyserial/ Um módulo Python para acessar dispositivos seriais]
 +
** [https://docs.python.org/3/library/venv.html ... e dicas para criar ambientes virtuais de execução Python]
 +
* [https://pythonhosted.org/pyserial/ pySerial: módulo Python para comunicação serial]
 +
* [http://www.tldp.org/HOWTO/Serial-Programming-HOWTO/ Serial Programming HOWTO]
 +
* [https://en.wikibooks.org/wiki/Serial_Programming/Serial_Linux Serial Programming/Serial Linux]
 +
* [http://pubs.opengroup.org/onlinepubs/7908799/xbd/termios.html Documentação sobre termios]
  
 
O primeiro contato com o transceiver RF envolve escrever um programa que transmita a mensagem ''Hello world!'' de um computador a outro usando um enlace sem-fio. Para isso, deve-se:
 
O primeiro contato com o transceiver RF envolve escrever um programa que transmita a mensagem ''Hello world!'' de um computador a outro usando um enlace sem-fio. Para isso, deve-se:
 
# Configurar dois transceivers RF
 
# Configurar dois transceivers RF
 
# Conectá-los a dois computadores diferentes usando adaptadores USB
 
# Conectá-los a dois computadores diferentes usando adaptadores USB
# Testar a comunicação usando programa para comunicação serial (ex: [http://freecode.com/projects/gtkterm gtkterm], [http://linux.die.net/man/8/picocom picocom], [http://linux.die.net/man/1/minicom minicom]). '''OBS:''' ver [http://forum.arduino.cc/index.php?topic=58591.0 esta observação sobre um detalhe quanto ao uso do transceiver via USB].
+
<!--# Testar a comunicação usando programa para comunicação serial (ex: [http://freecode.com/projects/gtkterm gtkterm], [http://linux.die.net/man/8/picocom picocom], [http://linux.die.net/man/1/minicom minicom]). '''OBS:''' ver [http://forum.arduino.cc/index.php?topic=58591.0 esta observação sobre um detalhe quanto ao uso do transceiver via USB].-->
# Escrever um programa que se comunique por meio dos transceivers. Para isso podem ser úteis:
+
# Escrever um programa que se comunique por meio dos transceivers.
#* [http://www.tldp.org/HOWTO/Serial-Programming-HOWTO/ Serial Programming HOWTO]
 
#* [https://en.wikibooks.org/wiki/Serial_Programming/Serial_Linux Serial Programming/Serial Linux]
 
#* [http://pubs.opengroup.org/onlinepubs/7908799/xbd/termios.html Documentação sobre termios]
 
  
  
Linha 91: Linha 90:
 
}
 
}
 
</syntaxhighlight>''main.cpp: exemplo de uso da classe serial''
 
</syntaxhighlight>''main.cpp: exemplo de uso da classe serial''
 +
 +
 +
Para compilar este exemplo, deve-se usar esta linha de comando:
 +
<syntaxhighlight lang=bash>
 +
g++ -std=c++11 -o p1 main.cpp Serial.cpp -I.
 +
</syntaxhighlight>
 
{{collapse bottom}}
 
{{collapse bottom}}
  
 +
<!--
 
== Configuração no VirtualBox ==
 
== Configuração no VirtualBox ==
  
Linha 123: Linha 129:
 
For a host port number higher than 9, the naming \\.\comX where X is the port number, is required. This can also be used for a one digit port number.
 
For a host port number higher than 9, the naming \\.\comX where X is the port number, is required. This can also be used for a one digit port number.
 
{{collapse bottom}}
 
{{collapse bottom}}
 
+
-->
= TAREFA: início do protocolo de enlace =
 
 
 
* ''DICA:'' Ver capítulo 11 do livro "Comunicação de Dados e Redes de Computadores", de Behrouz Forouzan, ou capítulo 5 do livro "Redes de Computadores" de Andrew Tanenbaum.
 
 
 
 
 
Implemente a delimitação de mensagens do seu protocolo de enlace, de forma que mensagens de tamanho variável possam ser transmitidas e corretamente recebidas. Essas mensagens podem ter até 1024 bytes. Em seguida, use-as para transmitir um pequeno arquivo através do enlace sem-fio.
 
 
 
 
 
Para testar o protocolo será usado um programa que emula um link serial. O transceiver RF será usado quando cada funcionalidade do protocolo estiver funcionando nesse emulador. Isso facilita o desenvolvimento do protocolo, pois é mais simples e ágil testar comunicações em um mesmo computador.
 
 
 
 
== Emulador de link serial ==
 
== Emulador de link serial ==
  
Linha 166: Linha 162:
  
 
Mesma que se opte pelo uso do emulador de serial, deve-se notar que, ao final, '''o protocolo deve ser demonstrado na plataforma Linux com o transceiver RF'''. Assim, o uso do emulador de serial tem por finalidade somente facilitar o desenvolvimento dos mecanismos básicos do protocolo.
 
Mesma que se opte pelo uso do emulador de serial, deve-se notar que, ao final, '''o protocolo deve ser demonstrado na plataforma Linux com o transceiver RF'''. Assim, o uso do emulador de serial tem por finalidade somente facilitar o desenvolvimento dos mecanismos básicos do protocolo.
 +
 +
== O transceiver RF APC 220 ==
 +
 +
O transceiver RF a ser utilizado se chama [http://www.appcon.com.cn/en/productshow.php?cid=6&id=28 APC 220]. Alguns documentos podem ser úteis:
 +
* [http://www.dfrobot.com/image/data/TEL0005/APC220_Datasheet.pdf Data sheet]
 +
* [http://www.robotshop.com/media/files/PDF/dfrobot-apc220-manual.pdf Manual]
 +
* [http://blog.filipeflop.com/wireless/modulo-rf-apc220-arduino.html Um tutorial]
 +
* [http://www.dfrobot.com/wiki/index.php?title=APC220_Radio_Data_Module%28SKU:TEL0005%29 Outro tutorial]
 +
 +
== O transceiver NRF24L01 ==
 +
 +
* [https://www.nordicsemi.com/api/sitecore/Products/MedialibraryZipDownload Arquivos do fabricante]
 +
* [http://arduinoinfo.mywikis.net/wiki/Nrf24L01-2.4GHz-HowTo Um monte de coisas (tem que filtrar)]
 +
* [https://forum.arduino.cc/index.php?topic=410574.0 E mais outras coisas ...]
 +
 +
Comandos AT:
 +
 +
<syntaxhighlight lang=text>
 +
AT Commands
 +
Baudrate : AT+BAUD=n where n =  1-6 (1:4800,2:9600,3:14400,4:19200,5:38400,6:115200) (default 9600Kbps)
 +
NRF Rate : AT+RATE=n where n =  1-3 (1:250K, 2:1M, 3:2M ) (default 2Mbps)
 +
Local Address : AT+RXA=0Xnn,0Xnn,0Xnn,0Xnn,0Xnn where nn are the local receiving address (default 0xff,0xff,0xff,0xff,0xff)
 +
Target Address  : AT+TXA=0Xnn,0Xnn,0Xnn,0Xnn,0Xnn where nn are the target address
 +
Operating Freq. : AT+FREQ=2.nnnG where nnn = 400 / 525 (default 2.400G)
 +
Checksum mode : AT+CRC=n where n = 8 /16 (default : 16 bit)
 +
System info : AT?
 +
</syntaxhighlight>
 +
 +
= Início do protocolo de enlace =
 +
 +
* ''DICA:'' Ver capítulo 11 do livro "Comunicação de Dados e Redes de Computadores", de Behrouz Forouzan, ou capítulo 5 do livro "Redes de Computadores" de Andrew Tanenbaum.
 +
 +
 +
<!--* [http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory/ Anatomia de um processo em memória no Linux:] ''revisão sobre uso de memória e sua relação com variáveis, objetos e parâmetros em programas C/C++'' -->
 +
 +
O protocolo de enlace a ser desenvolvido se destina a links com baixa taxa de transmissão e suscetíveis a erros frequentes. As mensagens são curtas (<= 1024 bytes), e transmitidas de forma confiável. Antes de de fato especificar esse protocolo, pode ser útil primeiro tentar transmitir mensagens de forma unidirecional, sem qualquer controle de erros. Essa é a função mais básica do protocolo, pois implica delimitar mensagens enviadas e recebidas.
 +
 +
O '''enquadramento''' (''framing'') é uma função do protocolo de enlace responsável por delimitar quadros na interface com a camada física. Deve-se ter em mente que a camada física oferece um serviço de envio e recepção de sequências de bytes sem qualquer estrutura. Cabe à camada de enlace delimitar as unidades de dados de protocolo (PDU) dentro dessas sequências de bytes.
 +
 +
Existe mais de uma abordagem para delimitar quadros (ver mais no capítulo 11 de ''Data Communications and Computer Networks'', de Behoruz Forouzan, e capítulo 5 de ''Redes de Computadores e a Internet'', de James Kurose e Keith Ross):
 +
 +
{| border=1
 +
! Abordagem
 +
! Descrição
 +
! Exemplos
 +
|-
 +
| Quadros de tamanho fixo / duração definida || Quadros têm sempre mesmo comprimento ou duração|| ATM, TDMA-based
 +
|-
 +
| Sentinela || padrão de bits/bytes delimita quadros || PPP, HDLC
 +
|-
 +
| Contador / duração || Cabeçalho contém duração ou comprimento do quadro || IEEE 802.11
 +
|-
 +
| Presença/ausência de portadora || Ausência de portadora delimita quadros || IEEE 802.11, IEEE 802.3
 +
|}
 +
 +
= Implementação do enquadramento =
 +
 +
A técnica de enquadramento escolhida é a do tipo ''sentinela''. Mais especificamente, escolheu-se a [https://tools.ietf.org/html/rfc1662#page-8 versão dessa técnica implementada pelo protocolo PPP]:
 +
* Usa-se a flag ''7E'' (01111110) como delimitador de quadros
 +
* Usa-se um byte de escape ''7D'' (01111101) para preenchimento de octeto
 +
* O transmissor deve fazer pelo menos o escape dos bytes ''7E'' e ''7D'' que aparecerem no conteúdo do quadro
 +
** Cada byte que sofrer o escape deve ser modificado por meio de um ''XOR 20''. Ex: se o byte a sofrer escape for ''7E'', ele deve ser modificado para ''5E'' (''7E XOR 20 = 5E'').
 +
 +
 +
Uma máquina de estados para o receptor é esta:
 +
 +
[[imagem:PTC-20162-Fsm-rcv.jpg|400px]]
 +
 +
{{collapse top|Versão alternativa da MEF}}
 +
[[imagem:PTC-Sentinela2.jpg|400px]]
 +
{{collapse bottom}}
 +
 +
 +
Além disso, uma possível implementação dessa função do protocolo poderia ser esta:
 +
 +
<syntaxhighlight lang=c>
 +
#ifndef FRAMING_H
 +
#define FRAMING_H
 +
 +
#include <cstdint>
 +
#include "Serial.h"
 +
 +
class Enquadramento {
 +
public:
 +
  Enquadramento(Serial & dev, int bytes_min, int bytes_max);
 +
  ~Enquadramento();
 +
 +
  // envia o quadro apontado por buffer
 +
  // o tamanho do quadro é dado por bytes
 +
  void envia(char * buffer, int bytes);
 +
 +
  // espera e recebe um quadro, armazenando-o em buffer
 +
  // retorna o tamanho do quadro recebido
 +
  int recebe(char * buffer);
 +
 +
private:
 +
  int min_bytes, max_bytes; // tamanhos mínimo e máximo de quadro
 +
  Serial & porta; 
 +
  char buffer[4096]; // quadros no maximo de 4 kB (hardcoded)
 +
 +
  enum Estados {Ocioso, RX, ESC};
 +
 +
  // bytes recebidos pela MEF até o momento 
 +
  int n_bytes;
 +
 +
  // estado atual da MEF
 +
  int estado;
 +
 +
  // aqui se implementa a máquina de estados de recepção
 +
  // retorna true se reconheceu um quadro completo
 +
  bool handle(char byte);
 +
 +
};
 +
 +
#endif
 +
</syntaxhighlight>
 +
 +
== A implementação da máquina de estados ==
 +
 +
A declaração acima sugere implementar a MEF a partir do método ''handle'' da classe Enquadramento. Esse método deve ser executado para cada byte recebido, representando o tratamento de um evento pela MEF. Uma forma usual e direta de implementar uma MEF faz uso de uma estrutura do tipo ''switch-case''. Essa abordagem se baseia em um modelo de programação estruturada. Basicamente ele depende de um algoritmo que executa um procedimento do sistema dependendo de seu estado atual e do evento. A seleção do procedimento se faz com uma estrutura ''switch-case''. O exemplo abaixo mostra o esqueleto de uma MEF implementada usando essa técnica.
 +
 +
<syntaxhighlight lang=c>
 +
// o tratador de eventos de uma MEF hipotética
 +
// A MEF aqui representada nada faz de útil ...
 +
bool Enquadramento::handle(char byte) {
 +
  switch (estado) {
 +
    case Ocioso: // estado Ocioso
 +
      estado = RX; // muda para RX
 +
      break;
 +
    case RX: // estado RX
 +
      estado = ESC; // muda para ESC
 +
      break;
 +
    case ESC: // estado ESC
 +
      estado = Ocioso; // muda para Ocioso
 +
      break;
 +
  }
 +
}
 +
</syntaxhighlight>
 +
 +
<!--
 +
{{collapse top | algumas coisas prontas para ajudar}}
 +
<syntaxhighlight lang=c>
 +
int Enquadramento::recebe(char * buffer_out) {
 +
  char byte;
 +
 +
  while (true) {
 +
    porta.read(&byte, 1);
 +
 +
    if (handle(byte)) {
 +
      memcpy(buffer_out, buffer, n_bytes);
 +
      return n_bytes;
 +
    }
 +
  }
 +
}
 +
</syntaxhighlight>''método Enquadramento::recebe''
 +
{{collapse bottom}}
 +
 +
-->
 +
 +
<!--
 +
<syntaxhighlight lang=c>
 +
int main(int argc, char * argv[]) {
 +
  string path;
 +
 +
  if (argc > 1) path = argv[1];
 +
  else path = "teste.pkt";
 +
 +
  ifstream arq(path);
 +
  if (not arq.is_open()) {
 +
    perror("ao abrir arquivo de quadros ");
 +
    return errno;
 +
  }
 +
 +
  Enquadramento proto(arq, 8, 32);
 +
  char quadro[32];
 +
 +
  while (true) {
 +
    try {
 +
      int bytes = proto.recebe(quadro);
 +
      if (bytes == 0) break;
 +
      dump(quadro, bytes);
 +
    } catch (exception e) {
 +
      cerr << "Erro ao receber quadro ! " << endl;
 +
    }
 +
  }
 +
}
 +
</syntaxhighlight>''main para receber múltiplos quadros ... note que sua máquina de estados deve disparar uma exceção do tipo [http://www.cplusplus.com/reference/exception/exception/ exception] caso ocorra um erro de enquadramento''
 +
 +
 +
<syntaxhighlight lang=c>
 +
int Enquadramento::recebe(char * buffer_out) {
 +
  char byte;
 +
 +
  while (true) {
 +
    //porta.read(&byte, 1);
 +
    byte = porta.get(); // lê um byte do arquivo de teste
 +
    if (porta.fail()) return 0;
 +
 +
    if (handle(byte)) {
 +
      memcpy(buffer_out, buffer, n_bytes);
 +
      return n_bytes;
 +
    }
 +
  }
 +
}
 +
</syntaxhighlight>''método Enquadramento::recebe''
 +
{{collapse bottom}}
 +
-->
 +
 +
== Atividade ==
 +
 +
* [http://tele.sj.ifsc.edu.br/~msobral/ptc/framing.tgz Transmissor e receptor compilados]
 +
 +
<!--
 +
{{collapse top | algumas coisas para ajudar}}
 +
<syntaxhighlight lang=c>
 +
#include <iostream>
 +
//#include <iomanip>
 +
#include <fstream>
 +
#include <stdio.h>
 +
#include <errno.h>
 +
#include "Enquadramento.h"
 +
 +
using namespace std;
 +
 +
void dump(char * buffer, int len) {
 +
  int m = 0, line = 0;
 +
 +
    while (m < len) {
 +
        printf("%02X: ", line*16);
 +
 +
        for (int n=0; n < 16 and m < len; n++, m++) {
 +
            int x = (unsigned char)buffer[m];
 +
            printf("%02X ", x);
 +
        }
 +
        puts("");
 +
        line++;
 +
    }       
 +
}
 +
 +
int main(int argc, char * argv[]) {
 +
  Serial dev("/dev/ttyUSB0", B9600);
 +
 +
  Enquadramento proto(dev, 8, 32);
 +
  char quadro[32];
 +
 +
  proto.envia("1234567890", 10);
 +
 +
  // ou:
 +
  // int bytes = proto.recebe(quadro);
 +
  // dump(quadro, bytes);
 +
}
 +
</syntaxhighlight>''main.cpp para enviar ou receber um único quadro''
 +
{{collapse bottom}}
 +
-->
 +
 +
# Implemente a MEF de recepção do enquadramento
 +
# Escreva um programa que transmita pela serial sequências de caracteres como estas:<syntaxhighlight lang=text>
 +
~abcedf1234567~
 +
~abcedf}^1234567~
 +
~abcedf012}]9876~
 +
~}]}^}]}]}]}^}^}^}]}]}]~
 +
</syntaxhighlight>... e use seu programa de recepção para recebê-las pela serial e identificar os quadros recebidos. Use o ''serialemu'' para conectar as seriais desses dois programas. Você pode usar o seguinte programa para transmitir as sequências de bytes:<syntaxhighlight lang=python>
 +
#!/usr/bin/python3
 +
 +
from serial import Serial
 +
import sys
 +
 +
try:
 +
  porta = sys.argv[1]
 +
  quadro = sys.argv[2]
 +
except:
 +
  print('Uso: %s porta_serial quadro' % sys.argv[0])
 +
  sys.exit(0)
 +
 +
try:
 +
  p = Serial(porta)
 +
except Exception as e:
 +
  print(e)
 +
  sys.exit(0)
 +
 +
pld = quadro.encode('ascii')
 +
 +
n = p.write(pld)
 +
print('Enviou %d bytes: %s' % (n, quadro))
 +
 +
sys.exit(0)
 +
</syntaxhighlight>
 +
# Escreva o método ''envia'' da classe ''Enquadramento'', e modifique o programa de teste de envio para usá-lo para transmitir sequências arbitrárias de bytes.
 +
# Teste a capacidade de recuperar o sincronismo da técnica de enquadramento escolhida. Para isso, modifique o programa de teste para que, propositalmente, transmita quadros com erro de delimitação.

Edição atual tal como às 17h15min de 21 de fevereiro de 2020

Próxima aula




Um protocolo de comunicação está relacionado aos mecanismos necessários para a entrega de mensagens entre duas aplicações quaisquer. Considerando uma arquitetura de redes em camadas como TCP/IP, protocolos de comunicação correspondem às camadas de enlace até transporte. Questões como garantia de entrega, controle de sequência, tratamento de erros, sincronização, estabelecimento e término de sessão, multiplexação e delimitação de mensagens, entre possivelmente outras, fazem parte do projeto de tais protocolos. Para introduzir o projeto de um protocolo de comunicação, o primeiro projeto da disciplina envolve um protocolo para estabelecimento de enlace sem-fio ponto-a-ponto.


Considere o caso de uma nova interface de rede sem-fio composta por um transceiver RF capaz de transmitir a distâncias de até 1 km. No caso de distâncias como essa, a taxa de transmissão possível de ser obtida é de 2400 bps, porém distâncias menores possibilitam taxas maiores, até um máximo de 19200 bps. Esse transceiver pode ser usado como uma interface serial do tipo UART. Portanto, com ele podem-se criar enlaces de média distância e baixas taxas de transmissão.


O transceiver RF usado como UART proporciona uma camada física, cuja interface de acesso a serviço oferece operações de envio e recepção de bytes. Nenhuma facilidade para delimitação de mensagens, endereçamento, sincronização e tratamento de erros é fornecida. De fato, tais serviços devem ser implementados em um protocolo de enlace que use esse transceiver como camada física.


O projeto 1 envolve o desenvolvimento de um protocolo de comunicação usando esse transceiver RF, de forma a oferecer um serviço de comunicação com essas características.

Um primeiro experimento

O primeiro contato com o transceiver RF envolve escrever um programa que transmita a mensagem Hello world! de um computador a outro usando um enlace sem-fio. Para isso, deve-se:

  1. Configurar dois transceivers RF
  2. Conectá-los a dois computadores diferentes usando adaptadores USB
  3. Escrever um programa que se comunique por meio dos transceivers.


A serial modelada como uma classe C++
#ifndef SERIAL_H
#define	SERIAL_H

#include <termios.h>

class Serial {
public:
    Serial();
    Serial(const char * path, int rate);
    Serial(const Serial& orig);
    virtual ~Serial();
    int get() { return tty_fd;}
    bool cca();
    int write(const char * buffer, int len);
    int read(char * buffer, int len);
    int read(char * buffer, int len, bool block);    
    char read_byte();
private:
    int tty_fd;
};

#endif	/* SERIAL_H */
serial.h


#include <iostream>
#include "Serial.h"

using namespace std;

int main() {
  Serial rf("/dev/ttyUSB0", B9600);
  string msg = "um teste ...\r\n";
  char buffer[32];

  int n = rf.write(msg.c_str(), msg.size());

  cout << "Enviou " << n << " bytes" << endl;

  n = rf.read(buffer, 32);

  cout << "Recebeu " << n << " bytes: ";

  cout.write(buffer, n);

  cout << endl;
}
main.cpp: exemplo de uso da classe serial


Para compilar este exemplo, deve-se usar esta linha de comando:

g++ -std=c++11 -o p1 main.cpp Serial.cpp -I.

Emulador de link serial

O programa serialemu emula um link serial com determinada taxa de bits, BER e atraso de propagação. Para usá-lo deve-se fazer o seguinte:

  1. Obtenha o código-fonte do serialemu
  2. Descompacte o arquivo Serialemu.zip, e entre no subdiretório Serialemu.
  3. Compile-o com este comando:
    aluno@M2:~/Serialemu$ make
    
  4. Copie o programa compilado para algum subdiretório mais conveniente ... por exemplo, /home/aluno, e depois mude para esse subdiretório:
    aluno@M2:~/Serialemu$ cp -a dist/Debug/GNU-Linux/serialemu /home/aluno/
    aluno@M2:~/Serialemu$ cd /home/aluno
    aluno@M2:~/$
    
  5. Execute-o de forma que ele apresente suas opções de execução:
    aluno@M2:~$ ./serialemu -h
    Uso: ./serialemu [-b BER][-a atraso][-f][-B taxa_bits] | -h
    
    BER: taxa de erro de bit, que deve estar no intervalo  [0,1]
    atraso: atraso de propagação, em milissegundos.
    taxa_bits: taxa de bits em bits/segundo
    -f: executa em primeiro plano (nao faz fork)
    
  6. Execute-o então da forma desejada, selecionando a taxa de bits (default: ilimitada), BER (default: 0) e atraso de propagação (default: 0). O serialemu automaticamente vai para segundo plano (daemonize), liberando o terminal. Ex:
    aluno@M2:~$ ./serialemu -B 9600
    /dev/pts/17 /dev/pts/2
    aluno@M2:~$
    
    ... e anote os dois caminhos informados pelo serialemu: eles são as duas portas seriais que correspondem às pontas do link serial emulado.
  7. Execute seu protocolo usando essas portas seriais virtuais.


Mesma que se opte pelo uso do emulador de serial, deve-se notar que, ao final, o protocolo deve ser demonstrado na plataforma Linux com o transceiver RF. Assim, o uso do emulador de serial tem por finalidade somente facilitar o desenvolvimento dos mecanismos básicos do protocolo.

O transceiver RF APC 220

O transceiver RF a ser utilizado se chama APC 220. Alguns documentos podem ser úteis:

O transceiver NRF24L01

Comandos AT:

AT Commands
Baudrate : AT+BAUD=n where n =  1-6 (1:4800,2:9600,3:14400,4:19200,5:38400,6:115200) (default 9600Kbps)
NRF Rate : AT+RATE=n where n =  1-3 (1:250K, 2:1M, 3:2M ) (default 2Mbps)
Local Address : AT+RXA=0Xnn,0Xnn,0Xnn,0Xnn,0Xnn where nn are the local receiving address (default 0xff,0xff,0xff,0xff,0xff)
Target Address  : AT+TXA=0Xnn,0Xnn,0Xnn,0Xnn,0Xnn where nn are the target address
Operating Freq. : AT+FREQ=2.nnnG where nnn = 400 / 525 (default 2.400G)
Checksum mode : AT+CRC=n where n = 8 /16 (default : 16 bit)
System info : AT?

Início do protocolo de enlace

  • DICA: Ver capítulo 11 do livro "Comunicação de Dados e Redes de Computadores", de Behrouz Forouzan, ou capítulo 5 do livro "Redes de Computadores" de Andrew Tanenbaum.


O protocolo de enlace a ser desenvolvido se destina a links com baixa taxa de transmissão e suscetíveis a erros frequentes. As mensagens são curtas (<= 1024 bytes), e transmitidas de forma confiável. Antes de de fato especificar esse protocolo, pode ser útil primeiro tentar transmitir mensagens de forma unidirecional, sem qualquer controle de erros. Essa é a função mais básica do protocolo, pois implica delimitar mensagens enviadas e recebidas.

O enquadramento (framing) é uma função do protocolo de enlace responsável por delimitar quadros na interface com a camada física. Deve-se ter em mente que a camada física oferece um serviço de envio e recepção de sequências de bytes sem qualquer estrutura. Cabe à camada de enlace delimitar as unidades de dados de protocolo (PDU) dentro dessas sequências de bytes.

Existe mais de uma abordagem para delimitar quadros (ver mais no capítulo 11 de Data Communications and Computer Networks, de Behoruz Forouzan, e capítulo 5 de Redes de Computadores e a Internet, de James Kurose e Keith Ross):

Abordagem Descrição Exemplos
Quadros de tamanho fixo / duração definida Quadros têm sempre mesmo comprimento ou duração ATM, TDMA-based
Sentinela padrão de bits/bytes delimita quadros PPP, HDLC
Contador / duração Cabeçalho contém duração ou comprimento do quadro IEEE 802.11
Presença/ausência de portadora Ausência de portadora delimita quadros IEEE 802.11, IEEE 802.3

Implementação do enquadramento

A técnica de enquadramento escolhida é a do tipo sentinela. Mais especificamente, escolheu-se a versão dessa técnica implementada pelo protocolo PPP:

  • Usa-se a flag 7E (01111110) como delimitador de quadros
  • Usa-se um byte de escape 7D (01111101) para preenchimento de octeto
  • O transmissor deve fazer pelo menos o escape dos bytes 7E e 7D que aparecerem no conteúdo do quadro
    • Cada byte que sofrer o escape deve ser modificado por meio de um XOR 20. Ex: se o byte a sofrer escape for 7E, ele deve ser modificado para 5E (7E XOR 20 = 5E).


Uma máquina de estados para o receptor é esta:

PTC-20162-Fsm-rcv.jpg

Versão alternativa da MEF

PTC-Sentinela2.jpg


Além disso, uma possível implementação dessa função do protocolo poderia ser esta:

#ifndef FRAMING_H
#define FRAMING_H

#include <cstdint>
#include "Serial.h"

class Enquadramento {
 public:
  Enquadramento(Serial & dev, int bytes_min, int bytes_max);
  ~Enquadramento();
 
  // envia o quadro apontado por buffer
  // o tamanho do quadro é dado por bytes 
  void envia(char * buffer, int bytes);
 
  // espera e recebe um quadro, armazenando-o em buffer
  // retorna o tamanho do quadro recebido
  int recebe(char * buffer);
 
 private:
  int min_bytes, max_bytes; // tamanhos mínimo e máximo de quadro
  Serial & porta;  
  char buffer[4096]; // quadros no maximo de 4 kB (hardcoded)
 
  enum Estados {Ocioso, RX, ESC};
 
  // bytes recebidos pela MEF até o momento  
  int n_bytes; 
 
  // estado atual da MEF
  int estado;
 
  // aqui se implementa a máquina de estados de recepção
  // retorna true se reconheceu um quadro completo
  bool handle(char byte);
 
};

#endif

A implementação da máquina de estados

A declaração acima sugere implementar a MEF a partir do método handle da classe Enquadramento. Esse método deve ser executado para cada byte recebido, representando o tratamento de um evento pela MEF. Uma forma usual e direta de implementar uma MEF faz uso de uma estrutura do tipo switch-case. Essa abordagem se baseia em um modelo de programação estruturada. Basicamente ele depende de um algoritmo que executa um procedimento do sistema dependendo de seu estado atual e do evento. A seleção do procedimento se faz com uma estrutura switch-case. O exemplo abaixo mostra o esqueleto de uma MEF implementada usando essa técnica.

// o tratador de eventos de uma MEF hipotética
// A MEF aqui representada nada faz de útil ... 
bool Enquadramento::handle(char byte) {
  switch (estado) {
    case Ocioso: // estado Ocioso
      estado = RX; // muda para RX
      break;
    case RX: // estado RX
      estado = ESC; // muda para ESC
      break;
    case ESC: // estado ESC
      estado = Ocioso; // muda para Ocioso
      break;
  }
}


Atividade


  1. Implemente a MEF de recepção do enquadramento
  2. Escreva um programa que transmita pela serial sequências de caracteres como estas:
    ~abcedf1234567~
    ~abcedf}^1234567~
    ~abcedf012}]9876~
    ~}]}^}]}]}]}^}^}^}]}]}]~
    
    ... e use seu programa de recepção para recebê-las pela serial e identificar os quadros recebidos. Use o serialemu para conectar as seriais desses dois programas. Você pode usar o seguinte programa para transmitir as sequências de bytes:
    #!/usr/bin/python3
    
    from serial import Serial
    import sys
    
    try:
      porta = sys.argv[1]
      quadro = sys.argv[2]
    except:
      print('Uso: %s porta_serial quadro' % sys.argv[0])
      sys.exit(0)
    
    try:
      p = Serial(porta)
    except Exception as e:
      print(e)
      sys.exit(0)
    
    pld = quadro.encode('ascii')
    
    n = p.write(pld)
    print('Enviou %d bytes: %s' % (n, quadro))
    
    sys.exit(0)
    
  3. Escreva o método envia da classe Enquadramento, e modifique o programa de teste de envio para usá-lo para transmitir sequências arbitrárias de bytes.
  4. Teste a capacidade de recuperar o sincronismo da técnica de enquadramento escolhida. Para isso, modifique o programa de teste para que, propositalmente, transmita quadros com erro de delimitação.