[Dúvida] *Protocolo* de comunicação

Estou querendo criar um mini protocolo de comunicação entre meu arduino e o C++ onde estou a desenvolver uma interface básica para testes, só que estou com algum problema na hora de ler valores 'string' para poder mandar pro arduino.

Eu vou ter uma interface que vai ter que ficar lendo a temperatura do arduino, MAS o arduino atuaria como um terminal, ele recebe um comando e devolve um valor, então eu mandaria pro arduino
"temp" e ele responderia o valor lido pelo sensor, ...

o problema é que ele esta lendo t,e,m,p ... até aí beleza dava pra criar um "buffer" de leitura, só que cada comando vai ter uma quatidade de caracteres, então eu não sei a largura do buffer, dai estou tentando implementar algo que funcione assim:

comando - palavra sem espaços "temp","led" ...
parametros - "numeros ou palavras separados por 1 espaço"
caracater final - caracter que define o fim do parametro ";"

um exemplo:
ligaled 13; (arduino não devolve nada, só vai ligar o led na porta 13)
desligaled 13; (arduino não devolve nada, só vai desligar o led na porta 13)
temperatura; (arduino envia o valor da temperatura);

Com este mini protocolo eu sei dos 2 lados qual o caracter que define o fim de um comando, e também fica mais fácil pra saber no C++ quando vem a temperatura, e talz...

eu fiz esta função para retornar uma string com o valor lido, mas ainda retorna quebrando linha por estar ...

String serialRead(){
  char actualChar;
  String finalString;
  while (Serial.available() > 0) {
     actualChar = Serial.read();
     finalString.concat(actualChar);
  }
  return finalString;
}

Finalmente eu andei estudando um pouco esta lib: http://interactive-matter.eu/how-to/ajson-arduino-json-library/
Pra trabalhar com JSON, minha ideia era ter alguns comandos neste meu protocolo, que retornassem json pela serial, tipo
"read -general;"
iria retornar uma leitura dos sensores catalogados:
{"temperature":27.8,"trimpot":300}

Mas também não estou conseguindo mandar esta para serial a string final montada, como eu mostrei...
Só consegui fazer ao contrário, ao interpretar a string e separar os valores...

Muita gente gosta de pensar que o arduino é humano... Não é! Entre temp ou AB a diferença é abismal para ti, memória e tamanho de programa e para o arduino não faz diferença nenhuma. Logo, se o que pretendes é comunicar entre o pc e o arduino o mais fácil que tens é mesmo fazer essa mudança.

Agora se pretendes ter o protocolo todo em ascii e compreensivel por humanos, vais ter mais dificuldades.
Um conselho, deixa a String de lado. Usar strings de C para isso sair de forma decente.

Não entendi em concreto o teu problema. Importas-te de explicar melhor?

ligaled 13; (arduino não devolve nada, só vai ligar o led na porta 13)

Muitos colegas aparecem com esta duvida.A minha resposta é para quê lhe mandar ligaled se o poderias fazer enviando para o arduino apenas 3 bytes : 0x0D 0X6F 0x3B por exemplo.Como o Bubilindo te disse o microcontrolador nao fala como nós.
Se olhares para a ascii table (ja que pelo que dizes queres basear o teu protocolo em códigos ASCII) 0x0d representa 13 em decimal, 0x6F representa o carácter 'o' de on e para carácter terminador o 0x3B que equivale a ';'
Assim passando esses 3 bytes apenas consegues ligar e desligar o led 13, ou outros pinos, manipulando os byte.(para desligar podias usar o carácter que representa um ´f´
Repara que isto e muito mais fácil de lidar e é mais eficiente do que mandares ligaled13;
Do lado do c++ envias os comandos hex e do lado do arduino reconstroes a instruçao.Do lado do arduino a coisa é fácil de se fazer com uns cast ...
Idealmente devias ter um carácter também para marcar o inicio do comando ...

bubulindo:
Não entendi em concreto o teu problema. Importas-te de explicar melhor?

Meu problema é a interrupção de código, pois ele manda por exemplo a letra A e ele "encerra" o envio, dai abre outro "envio" ´para enviar a próxima letra, tipo ele não fica preso no

 while (Serial.available() > 0) {   }

Daí ele não 'junta' a string para me enviar o comando, eu queria montar tipo uma string inteira de comando ex: 'meucomando parametro1 parametroN', dai depois disto em iria separar os 'blocos' de string pelo espaço ficando com um array de strings sendo:

[0] -> comando
[1] -> parametro 1
[N] -> parametro N

Depois de separados o comando e os parâmetros eu iria procurar o método dele em um switch, e só ia chamar os parâmetros existentes, tipo,

  switch code[0]:
     case 'ligaled':
        digitalWrite(code[1].toInt(), HIGH);
        break;
     case 'desligaled':
       digitalWrite(code[1].toInt(), LOW);
       break;

e assim por diante...

mas isso e o computador, correcto? não o arduino, ou estyou a confundir?

Na verdade, o software do computador escreve este parâmetro, monta o código e manda para o arduino, pois eu não sei quando vou precisar de tal coisa, e não sei se seria correto ficar enviando a todo momento...

Por exemplo:
Um Arduino + LM35 (Sensor de Temperatura), e no PC Software(C++) mostra temperatura, e tem um botão atualizar, cada vez que eu clico no botão atualizar eu mandaria via serial para o arduino:

 getTemperature

O Arduino iria passar este código pelo switch até encontrar um "match" e devolver para o software, via serial, o valor da temperatura:

 28.7

Dai o software quando mandou o getTemperature já sabe que o arduino vai responder a temperatura, e fica esperando, é como se fosse um identificador...

A ideia é que eles 'conversem', um pede algo, o outro responde, para mim saber o que o arduino irá mandar, meio orientado a evento, só responde quando pedir, ...

Outra forma seria o arduino enviar a string em XML ou JSON com um resultado parcial de tudo que ele estivesse fazendo, só que eu não consegui fazer ele montar isso, até achei inviável ficar mandando tudo ...
MAS em alguns casos de informações que precisam ser atualizadas a todo momento, o arduino teria que simplesmente mandar em de alguma forma um array de informações, em algum padrão, no caso JSON ou XML...

{
 temperature:{
    sensor1:28.7,
    sensor2:29.2
  },
 outputs:{
   1:0,
   2:1,
   3:1
  }
}

ou

<xml>
<temperature>
    <sensor1>28.7</sensor1>
    <sensor2>29.2</sensor2>
</temperature>
<outputs>
    <1>0</1>
    <2>1</2>
    <3>1</3>
</outputs>
</xml>

Eu também estava com problema de comunicação nesse mesmo modelo (Master / Slave), e descobri que boa parte dos meus problemas estavam ligados a não ter um protocolo robusto o suficiente. Dá uma olhada no meu tópico.

Resolvi o problema utilizando o protocolo descrito neste link. O protocolo é dessa forma:

Byte 1: Start Byte ( 0 hexadecimal ).
Byte 2-3: ASCII Arduino's address.
Byte 4: Byte ENQ, ACK or NAK (0x05h, 0x06h y 0x15h) .
Byte 5: ASCII Requested command.
Byte 6 y 7: ASCII Function number.
Byte 8: Sign byte (Positive 0x20h y Negative 2D)
Byte 9-12: ASCII of data bytes (0x00h-0xFFFFh)
Byte 13: Byte EOT (End of Text) (0x03h)
Byte 14-15: Checksum (addition from 2nd byte to 13th byte)

Em resumo: o protocolo define que cada mensagem tem 15 bytes. Isso é fechado, toda mensagem tem que caber em 15 bytes. E no Arduino, só leio os valores do Serial quando Serial.available == 15. Daí leio tudo, e cada byte tem o seu significado.

O que o HughPT falou é válido: define um protocolo mais simples, e com o número de bytes sempre igual (3, 4, 5, quantos você conseguir fazer), e deixa as regras do seu protocolo escritas. Esse protocolo que postei acima, de 15 bytes, tem uma possibilidade quase infinita de comandos, e ainda conta com verificação de dados (checksum).

O que eu estou a fazer varia, pois eu quero passar uma string do código, a fins de criar uma interface do tipo 'terminal', então varia quantidade de caracteres de cada operação.

Eu estava pensando e vou fazer algo assim:

void readSerialPort() {
  unsigned int bytesAmount = Serial.available();  
  
  if (bytesAmount> 0) {
    char buffer[bytesAmount];
    for(int i = 0; i < bytesAmount; i++) {
        buffer[i] = Serial.read();
    }
    // Separar o buffer em  um array de comando e parâmetros, onde o espaço vai ser o caracter separador, o comando vai ser o índice 0 e os outros índices vão ser parâmetros
    // http://blog.prsolucoes.com/c/separando-ou-explodindo-uma-string-em-c/


    //varrer o switch com todos os parâmetros, e executar o necessário
    switch (command) {
      case "getTemperature":
            Serial.print(analogRead(A0);
            break;
      case "setLedPWM": // primeiro parâmetro deve ser o pino do led e o segundo deve ser o valor do pwm (0-255)
            analogWrite(params[0].toInt(),params[1].toInt());
            break;
      ... outros comandos ...

    }

  }
}

Claro que futuramente penso em jogar cada comando para uma void, e o switch só vai chamar o método daquele comando com os parâmetros corretos ...


O que eu estou na dúvida é, o Serial.available() já define mesmo a quantidade de caracteres enviados? tipo se eu enviar o comando "getTemperature" o Serial.available() seria 14 ?
Se for desta forma eu não preciso necessariamente usar um carácter que vai me definir o fim do comando ...

MarceloBoeira:
O que eu estou na dúvida é, o Serial.available() já define mesmo a quantidade de caracteres enviados? tipo se eu enviar o comando "getTemperature" o Serial.available() seria 14 ?
Se for desta forma eu não preciso necessariamente usar um carácter que vai me definir o fim do comando ...

Sim, desde que você não use o Serial.read(). Quando você usa o Serial.read(), o Serial.available() diminui em 1, porque você já leu o primeiro. No próximo Serial.read(), ele vai pegar o próximo char.

No seu caso, acho que o readBytesUntil é o mais apropriado.

Claro que futuramente penso em jogar cada comando para uma void

O que é para ti uma void?

void readSerialPort() {
unsigned int bytesAmount = Serial.available();

if (bytesAmount> 0) {
char buffer[bytesAmount];

Ao ler o teu excerto de código fiquei a coçar a cabeça ao pensar o que ira acontecer na criação desse array. Nao devias usar antes um malloc ?
Nunca vi tal coisa, não vou dizer que isso não funcione porque nunca usei uma declaração assim mas estou com muitas duvidas que isso aloque memoria correctamente já que para criares um array tens de saber à partida quantos elementos necessitas.Nessa função isso não é conhecido pois irá depender do que seja retornado pela funçao Serial.available()
Ja testaste isso?
Nao sei que comportamento o compilador ira gerar ...

HugoPT:

Claro que futuramente penso em jogar cada comando para uma void

O que é para ti uma void?

void readSerialPort() {
unsigned int bytesAmount = Serial.available();

if (bytesAmount> 0) {
char buffer[bytesAmount];

Ao ler o teu excerto de código fiquei a coçar a cabeça ao pensar o que ira acontecer na criação desse array. Nao devias usar antes um malloc ?
Nunca vi tal coisa, não vou dizer que isso não funcione porque nunca usei uma declaração assim mas estou com muitas duvidas que isso aloque memoria correctamente já que para criares um array tens de saber à partida quantos elementos necessitas.Nessa função isso não é conhecido pois irá depender do que seja retornado pela funçao Serial.available()
Ja testaste isso?
Nao sei que comportamento o compilador ira gerar ...

Void:
Eu penso em ter voids para não deixar o código 'bruto' dos comandos no meio do switch ficando bagunçado o código ....
dentro do switch

case 'getTemperature':
      getTemperature();
      break;

E a void separada:

void getTemperature(){
   Serial.print(analogRead(A0));
}

Não testei nada ainda, são só suposições, e o código que coloquei é apenas um esboço, justamente para identificar as falhas e pensar antes de sair gravando código.

Como seria uma declaração correta HugoPT ?

Eu penso em ter voids para não deixar o código 'bruto' dos comandos no meio

Eu fiz te essa pergunta porque da maneira que tu te referes a keyword void é como que void fosse a criaçao de uma funçao, o que nao é sabes disso correcto?
void é apenas um tipo de retorno de uma funçao( void quer dizer que a funçao declarada nao retorna nenhum valor )

void readSerialPort() {
unsigned int bytesAmount = Serial.available();

if (bytesAmount> 0) {
char buffer[bytesAmount];

Nesta funçao que chamaste readSerialPort que nao retorna nenhum valor (porque tens la void ) estas tentar criar um array dinamicamente, que do que eu sei nao é possivel, para isso tens de usar o malloc e depois o free para alocar memoria e depois a libertar.

Eu me refiro a void no sentido de "procedure"/procedimento, justamente por ela não ter retorno.

Vou tentar procurar um exemplo, mas eu já criei arrays assim 'dinâmicos' em C para desktop e eu inicializava ele com o tamanho do array de origem sempre, vou dar uma pesquisada...

Testei este código, mas não funcionou corretamente:

void setup() {
  Serial.begin(9600);
}

void loop() {
  readSerialPort();
  delay(10);
}

void readSerialPort() {
  unsigned int bytesAmount = Serial.available();  
  if (bytesAmount > 0) {
    Serial.print("Starting ReadPortSerial with: ");   
    Serial.println(bytesAmount);    
    char buffer[bytesAmount];
    for(int i = 0; i < bytesAmount; i++) {
        buffer[i] = Serial.read();
    }
    for(int i = 0; i < bytesAmount; i++){
       Serial.println("buffer[" + (String)i + "] = " + (String)buffer[i]);
    }
  }
}

Em teoria este código deveria pegar a quantidade de caracteres enviadas por serial, mostrar e depois listar o array que contém estes mesmos caracteres, o problema é que ele quebra na maioria das vezes a palavra e eu não faço ideia por que.

Eu enviei a palavra teste e ele quebrou e me retornou isto:

Starting ReadPortSerial with: 3
buffer[0] = t
buffer[1] = e
buffer[2] = s
Starting ReadPortSerial with: 3
buffer[0] = t
buffer[1] = e
buffer[2] =

O pior de tudo é que ele não quebra a palavra sempre no mesmo lugar, ele quebra aleatoriamente, tem vezes que ele mostra certo a palavra, e tem vezes que não.

Alguém sabe o motivo?

Consegui dar uma melhorada, agora ele não quebra mais a string, MAS ele deixa sujeira no buffer, tipo se eu envio uma palavra de tipo AMARELO, e depois TESTE ele me manda na segunda vez TESTELO, acredito eu que o LO tenha ficado no buffer da Serial. E eu estou chamando Serial.flush para tentar limpar, mas nada.

Segue o código:

const int SERIAL_BUFFER_MAX_SIZE = 64;

void setup() {
  lcd.begin(16,2);
  Serial.begin(9600);
}

void loop() {
  readSerialPort();
  delay(10);
}

void readSerialPort() {
  unsigned int bytesAmount = Serial.available();  
  if (bytesAmount > 0) {
    char buffer[SERIAL_BUFFER_MAX_SIZE];
    Serial.readBytesUntil('\n', buffer, SERIAL_BUFFER_MAX_SIZE);
    Serial.println(buffer);
    Serial.flush();
  }
}

Após umas merecidas férias pelos Algarves, cá estou com muita pena minha pois esta discussão tornou-se interessante.

@Marcelo, tens de compreender as limitações do Arduino. Ele pode facilmente enviar o resultado em JSON ou XML, mas vais ver que isso te vai trazer dores de cabeça e vai comer imenso código sem tornar o sistema mais robusto ou rápido. Claro que isto pode facilitar as coisas no lado do PC, mas tendo em conta a diferença de poder de processamento, eu escolheria libertar o Arduino do maior peso possível.

O teu problema está aqui...

 Serial.readBytesUntil('\n', buffer, SERIAL_BUFFER_MAX_SIZE);

Porque é que lês o máximo? Em vez do número de bytes disponíveis? Provavelmente a função manda-te os bytes que ela tem no buffer. A definição do readbytesUntil está no ficheiro Stream.cpp. Lá podes perceber o que acontece... nota que o flush não apaga o buffer. Ele apenas iguala o apontador de escrita e leitura do buffer.

E porque é que usas o readBytesUntil dentro dum loop? Isso não confunde as coisas?

Como eu e mais gente referiu isto dá uma trabalheira e é algo que nunca fica perfeito... mas é muito bom para aprender comunicações, programação e microcontroladores. Vai colocando aqui o progresso.

Acerca do void, creio que o Marcelo se estaria a referir a um void * para uma função?

@LegendBR, esse é um link muito bom. :wink: