Отправка данных в Arduino через Nodejs: Arduino не возвращает данные

У меня есть следующий скетч, который работает в Arduino UNO:

/*
* Авторские права принадлежат Димитриосу Десилласу, 2018 г.
*
* Настоящим предоставляется разрешение любому лицу, получающему копию этого программного обеспечения и связанных с ним файлов документации («Программное обеспечение»), безвозмездно использовать Программное обеспечение без ограничений.
* включая без ограничений права на использование, копирование, изменение, объединение, публикацию, распространение, сублицензирование и/или продажу копий Программного обеспечения,
* и разрешать лицам, которым предоставляется Программное обеспечение, делать это при соблюдении следующих условий:
*
* Вышеуказанное уведомление об авторских правах и настоящее уведомление о разрешении должны быть включены во все копии или существенные части Программного обеспечения.
*
* ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ, ГАРАНТИИ ТОВАРНОЙ ПРИГОДНОСТИ,
* ПРИГОДНОСТЬ ДЛЯ КОНКРЕТНОЙ ЦЕЛИ И НЕНАРУШЕНИЕ ПРАВ. АВТОРЫ ИЛИ ОБЛАДАТЕЛИ АВТОРСКИХ ПРАВ НИ ПРИ КАКИХ ОБСТОЯТЕЛЬСТВАХ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ ПО КАКИМ-ЛИБО ПРЕТЕНЗИЯМ, УЩЕРБУ ИЛИ ИНОЙ ОТВЕТСТВЕННОСТИ,
* Будь то в результате иска по контракту, деликта или иного правонарушения, возникающего из-за или в связи с программным обеспечением или использованием или другими действиями с программным обеспечением.
*/

String inputString = "";         // строка для хранения входящих данных
bool seralReadEnded=false;


void setup() {
  Serial.begin(19200);
  while (!Serial);
}

void loop() {
  if(seralReadEnded){
    sendDataOverSerial((byte [])inputString.c_str(),inputString.length());
    inputString="";
    seralReadEnded=false;
    delay(5000);
  }
}

String concatHex(byte value,String string){
  string+="\\x";
  if(value<16){
   string+="0";
  }
  string+=String(value,HEX);
  return string;
}

void sendDataOverSerial(byte byteArray[],size_t arrayLength){
  String returnVal="BEGIN:";
  for(int i=0;i<arrayLength;i++){
    returnVal=concatHex(byteArray[i],returnVal);
  }
  returnVal+="\n";
  Serial.println(returnVal);
}

void serialEvent() {
  while (Serial.available()) {
    // получаем новый байт:
    char inChar = (char)Serial.read();
    if(inChar == 0x11){
       char inChar = (char)Serial.read();
    } else if (inChar == 0x03) {
      seralReadEnded = true;
    } else {
     inputString+=inChar; 
    }
  }
 }

И я создал следующее программное обеспечение Node.js для чтения и отправки данных через USB на Arduino:

const SerialPort = require('serialport');
const Readline = require('parser-readline');
const Ready = require('parser-ready');
const Regex = require('parser-regex');
const crypto = require('crypto')

const valueIntoHex=function(value){
  switch(value){
    case 0x11:
      return "0x11";
    case 0x03:
      return "0x03";
    case 0x02:
      return "0x02";
    default:
      return value;
  }
}

/**
* Escaping NonPrintable Ascii Characters (bytes) of a buffer data
* @param <Buffer> data The data to read
*/
const formatDataForSerialSend=function(data){
  //Я выделяю вдвое большую длину, потому что предполагаю наихудший сценарий: все байты будут теми, которые мне нужно экранировать
  const returnBuffer =  Buffer.alloc(2*data.length);
  indexToWrite=0;
  //Я использую индекс indexToWrite, потому что во время экранирования позиция, в которой я записываю байт, отличается от той, которую я читаю.
  for(let i=0;i<data.length;i++){
    const byte=data.readUInt8(i);

    if(byte == 0x11 || byte == 0x03 || byte == 0x02){
      console.log("Escaped Ascii: "+valueIntoHex(byte));
      indexToWrite=returnBuffer.writeUInt8(0x11,indexToWrite);
    }
    indexToWrite=returnBuffer.writeUInt8(byte,indexToWrite);
  }
  returnBuffer.writeUInt8(0x03,indexToWrite);
  //Потому что мне не нужен ненужный размер данных в моем буфере
  const lastIndex=returnBuffer.lastIndexOf(0x03);
  return returnBuffer.slice(0,lastIndex+1);//Мне нужен размер, а не последний индекс. (Size=lastIndex+1)
}


/**
* Application Specific send logic
* @param <Buffer> data Data te send
*/
const sendData=function(data){
  console.log("Escaping dirty Ascii");
  data=formatDataForSerialSend(data);
  console.log("SEND DATA: "+data.toString('hex'));
  port.write(data,'binary',(err)=>{
    if(err){
      console.err("Could not send data",err);
    }
  });
}

const portName = '/dev/ttyACM0';

const port = new SerialPort(portName, {baudRate: 19200});

//Просмотр: https://regex101.com/r/QcBTTj/1
const hexRegex=/((\\|0)(x|X)[0-9A-Fa-f]{2})+/g;
const parser = port.pipe(new Ready({ delimiter: 'BEGIN:' })).pipe(new Readline({ delimiter: '\r\n' }));


console.log("################# SEND ####################");
sendData(Buffer.from('hello','ascii'));
console.log("############# END OF SEND #################");



parser.on('data', function(input) {
    console.log("################# RECIEVE ##################");
    console.log("Message: "+input);

    const input2=input.replace(/^BEGIN:|(\r|\n|(\\|0)(x|X))/g,"");
    console.log("Sanitized Input: ",input2);

    try{
        const data=Buffer.from(input2,'hex')
        console.log("REPRINT HEX FOR VERIFICATION: "+data.toString('hex'));
        console.log("############ END RECIEVE ##############");
    } catch(e) {
        console.error(e);
    }
});

Но по какой-то причине мне удалось заставить его отправлять данные, а Arduino их не возвращает. Поэтому я хочу его отладить, но не знаю, как это сделать. Я имею в виду, через код Arduino. Как мне запустить отладчик, чтобы это сделать?

Как я могу одновременно просматривать и анализировать USB-трафик между приложением и Arduino, чтобы определить логическую ошибку?

Также я не смог понять, почему Arduino не возвращает некоторые данные. Если я использую этот код для этого скетча, приложение node.js прекрасно считывает данные:

/*
 * Copyright 2018 Dimitrios Desyllas
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 
 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

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

void loop() {
  byte byteArray[]={1,10,255,3,4,2,1,55};
  for(int i=0;i<sizeof(byteArray);i++){
    Serial.print("\\x");
    if(byteArray[i]<16){Serial.print(0);}
    Serial.print(byteArray[i],HEX);
  }
  Serial.println("\n");
  delay(3000);
}

Редактировать1

Пока что я изменил набросок вот так:

String inputString = "";         // строка для хранения входящих данных
bool seralReadEnded=false;


void setup() {
  Serial.begin(19200);
  while (!Serial);
}

void loop() {
  if(seralReadEnded){
    sendDataOverSerial((byte [])inputString.c_str(),inputString.length());
    inputString="";
    seralReadEnded=false;
  }
}

String concatHex(byte value,String string){
  string+="\\x";
  if(value<16){
   string+="0";
  }
  string+=String(value,HEX);
  return string;
}

void sendDataOverSerial(byte byteArray[],size_t arrayLength){
  String returnVal="BEGIN:";
  for(int i=0;i<arrayLength;i++){
    returnVal=concatHex(byteArray[i],returnVal);
  }
  returnVal+="\n";
  Serial.println(returnVal);
}

void serialEvent() {
  while (Serial.available()) {
    // получаем новый байт:
    char inChar = (char)Serial.read();
    if(inChar == 0x11){
       char inChar = (char)Serial.read();
    } else if (inChar == 0x03) {
      seralReadEnded = true;
    } else {
     inputString+=inChar; 
    }
  }
 }

И я проверил это, запустив свой терминал GNU/Linux вот так:

screen /dev/ttyACM0 19200

Я набрал кое-что и, нажав сочетание клавиш CTRL+C, мне удалось вернуть их обратно (каким-то образом выводится символ 0x03, который является символом ASCII ETX, который в моем случае обозначал поток данных).

Но как мне получить случайные двоичные данные, экранировать их символами 0x11 и отправить в Arduino через Bash, если, насколько я знаю, в системах GNU/Linux я могу использовать /dev/random для генерации данных?

, 👍1

Обсуждение

1. Не используйте delay(). 2. Не используйте объекты String, так как они неэффективны для памяти. Вместо этого используйте простые байтовые массивы. 3. Не используйте Node.js для тестирования своего скетча. Используйте последовательную консоль или эмулятор терминала (например, putty), а затем добавьте компонент Node, как только убедитесь, что скетч работает нормально., @Edgar Bonet

Не используйте 0x11 (DC1) в качестве экранирующего символа, так как драйвер терминала Linux интерпретирует его как «старт» (Xon). Вы можете использовать 0x10 (DLE), который можно набрать на клавиатуре как Control-P. Кроме того, ваша обработка экранирующего символа некорректна: вы вызываете Serial.read(), который чаще всего возвращает -1 (что означает отсутствие доступного символа), и не используете результат., @Edgar Bonet


1 ответ


1

В вашем коде слишком много копий String. Это требует... проблемы, так как это может привести к фрагментации памяти и, в конечном итоге, к Истощение памяти. Вот почему я рекомендовал в комментарии буферизировать данные в простых байтовых массивах.

Однако, похоже, ваш скрипт Node.js будет выполнять буферизацию В любом случае (это то, что делает парсер Readline). Поэтому нет смысла в буферизации также на Arduino, которая имеет один миллион (или более) раз меньше памяти, чем компьютер, на котором запущен код Node.js. Если Arduino просто выводит байты по мере их получения, программа становится очень простой:

void setup()
{
    Serial.begin(19200);
    Serial.print("BEGIN:");
}

// Отправляем байт через последовательный порт, отформатированный в шестнадцатеричном формате.
static void send_hex(uint8_t value)
{
    Serial.print("\\x");
    if (value < 16)
        Serial.print('0');
    Serial.print(value, HEX);
}

void loop()
{
    static bool escaped;  // следующий символ экранирован?
    if (Serial.available()) {
        char c = Serial.read();
        if (c == '\x10' && !escaped) {  // DLE (экранирование канала передачи данных)
            escaped = true;
        } else if (c == '\x03' && !escaped) {  // ETX (конец текста)
            Serial.print("\r\nBEGIN:");
        } else {
            send_hex(c);
            escaped = false;
        }
    }
}

Обратите внимание, что при получении «конца текста» программа завершает работу. текущая строка (отправляя "\r\n") и немедленно отправляет преамбулу для следующий ("BEGIN:"). На принимающей стороне парсер Readline позаботьтесь о доставке только полных партий.

Если по какой-либо причине вам действительно нужна буферизация внутри Arduino, то удалите строку Serial.print("BEGIN:"); из setup() и измените цикл следующим образом:

void loop()
{
    static char buffer[1024];
    static size_t pos;    // текущая позиция в буфере
    static bool escaped;  // следующий символ экранирован?
    if (Serial.available()) {
        char c = Serial.read();
        if (c == '\x10' && !escaped) {  // DLE (экранирование канала передачи данных)
            escaped = true;
        } else if (c == '\x03' && !escaped) {  // ETX (конец текста)
            Serial.print("BEGIN:");
            for (size_t i = 0; i < pos; i++)
                send_hex(buffer[i]);
            Serial.println();
            pos = 0;
        } else {
            if (pos < sizeof buffer)
                buffer[pos++] = c;
            escaped = false;
        }
    }
}

Обратите внимание, что размер буфера составляет половину оперативной памяти вашего Uno. Это безопасно, поскольку это очень простая программа, которая использует очень мало динамической памяти (только стек, без кучи) и не подвержены риску фрагментации. Обратите внимание также, что Ваша исходная программа хранит как необработанные данные, так и отформатированный шестнадцатеричный код (что в 4 раза больше) в памяти одновременно, что означает, что ваш Буферизация будет ограничена 1/5 доступной оперативной памяти.

,

Я разделил их, потому что хочу посмотреть, как последовательный ввод-вывод будет реализован в https://arduinoprosto.ru/q/52151/how-to-indicate-an-end-of-a-stream-on-usb. Поэтому я хотел, чтобы они были отдельными функциями: отправка и прием., @Dimitrios Desyllas

Также проблема в приложении Node.js, а не в Arduino,, @Dimitrios Desyllas