Передача потоков в функцию и из нее (данные flutter_libserialport в декодер COBS)

Я пытался заставить работать функцию decodeCOBSStream COBS 0.2.0. Я отправляю данные по Bluetooth-соединению ПК из терминальной программы Hercules (значения HEX с отключенным CR/LF) на модуль Bluetooth HC-05, подключенный к порту USB через кабель FTDI. flutter_libserialport отлично справляется с необработанными значениями, я могу вывести их на консоль отладки, и все выглядит нормально.

Однако когда я пытаюсь передать поток в decodeCOBSStream и распечатать его, первое сообщение печатается нормально. Иногда это делает второй. Затем он перестает печатать сообщения, и в конечном итоге процедура чтения flutter_libserialport выдает исключение в операторе возврата.

ArgumentError (Недопустимый аргумент(ы): длина должна находиться в диапазоне [0, 4611686018427387903].)

Это рассматриваемая функция из flutter_libserialport:

static Uint8List read(int bytes, UtilFunc<ffi.Uint8> readFunc) {
    return ffi.using((arena) {
      final ptr = arena<ffi.Uint8>(bytes);
      final len = call(() => readFunc(ptr));
      return Uint8List.fromList(ptr.asTypedList(len));
    });
  }

Вот мой код. Если я создам список, который отправляет значения, поступающие из предстоящих данных, в onDataReceived (изменив его на использование Uint8List в качестве входных данных), все в порядке. Я почти уверен, что именно так я помещаю decodeCOBSStream между ними. flutter_libserialport работает с Uint8List, а decodeCOBSStream работает с ByteData.

import 'dart:typed_data';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_libserialport/flutter_libserialport.dart';
import 'package:cobs2/cobs2.dart';

const kDebugMode = true;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    List<String> availablePort = SerialPort.availablePorts;
    SerialPort port = SerialPort('COM4');
    SerialPortReader reader = SerialPortReader(port);
    final controller = StreamController<ByteData>();

    debugPrint('Available Ports: $availablePort');

/*    
    Stream<ByteData> upcomingData = reader.stream.map((data) {
      ByteData byteData = data.buffer.asByteData();
      return byteData;
    });
*/
    // Received the serial data from the SerialPortReader 'reader'
    Stream<Uint8List> upcomingData = reader.stream.map((data) {
      return data;
    });

    controller.addStream(decodeCOBSStream(upcomingData.byteData));

    // When data is received
    void onDataReceived(ByteData data) {
      var decodeUint8 = data.buffer.asUint8List();
      var decodedPrt =
          getHexString(decodeUint8, offset: 0, length: decodeUint8.length);
      debugPrint('Decoded: $decodedPrt');
    }

/*    // When data is received
    void onDataReceived(Uint8List data) {
      var decodedPrt = getHexString(data, offset: 0, length: data.length);
      debugPrint('Decoded: $decodedPrt');
    }
*/
    try {
      port.openReadWrite();
      initSerial(port);

      // Send data to serial port
      serialSend(port, 'Ready for data!');

      // Creates a listener to receive the decoded data
      //controller.stream.listen((data) => onDataReceived(data));
      controller.stream.listen(onDataReceived).onDone(() {});
      //upcomingData.listen(onDataReceived).onDone(() {});
    } on SerialPortError catch (err, _) {
      debugPrint('${SerialPort.lastError}');
      port.close();
    }

    return Container();
  }
}

extension on Stream<Uint8List> {
  Stream<ByteData> get byteData => transform(Uint8ListToByteData());
}

// Stream transformer that converts Uint8List data to ByteData.
class Uint8ListToByteData extends StreamTransformerBase<Uint8List, ByteData> {
  @override
  Stream<ByteData> bind(Stream<Uint8List> stream) {
    return stream.map((data) => data.buffer.asByteData());
  }
}

Uint8List _stringToUint8List(String data) {
  List<int> codeUnits = data.codeUnits;
  Uint8List uint8list = Uint8List.fromList(codeUnits);
  return uint8list;
}

// Converts a String of multiple integers into HEX
String getHexString(
  List<int> list, {
  required int offset,
  required int length,
}) {
  var sublist = list.getRange(offset, offset + length);
  return [
    for (var byte in sublist)
      byte.toRadixString(16).padLeft(2, '0').toUpperCase()
  ].join();
}

void initSerial(SerialPort port) {
  var config = port.config;
  config.baudRate = 9600;
  config.bits = 8;
  config.stopBits = 1;
  config.parity = 0;
  port.config = config;

  debugPrint('BaudRate = 9600');
}

void serialSend(SerialPort port, String data) {
  debugPrint('Written Bytes: ${port.write(_stringToUint8List(data))}');
}

Я пробовал передавать данные в flutter_libserialport разными способами. Я попытался изменить его с Uint8List на ByteData в функции предстоящих данных и попытался использовать для этого преобразователи потока. Некоторые из опробованных мной вариантов показаны, но закомментированы.

Код cobs, ну, старый и его можно улучшить. Учитывая, что вся библиотека находится в одном исходном файле и умещается примерно на 2 страницах, вы легко можете переписать ее для работы с типизированными данными вместо ByteData. Я бы посоветовал вам добавить полученные байты в ByteDataReader. Затем вы можете легко читать по одному байту за раз, находя разделитель 0x00 и обрабатывая байты между разделителями.

Richard Heap 03.06.2024 23:55

Это то, над чем я изначально работал. У меня был буфер, который я добавлял по мере получения данных. Я раньше не играл с ByteDataReader. Можете ли вы указать мне на хороший пример?

kdesroch1 04.06.2024 00:02

Имеет ли смысл для этого просто использовать пакет Buffer. Кажется, используется ByteDataReader.

kdesroch1 04.06.2024 00:12

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

Richard Heap 04.06.2024 13:10
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
67
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

ByteDataReader — полезный класс для сбора байтов, которые вы будете постепенно обрабатывать. Это позволяет добавлять байты по мере их поступления по сети. У него есть еще одна функция «блокировки», если для формирования полного сообщения недостаточно байтов, то есть в вашем случае, если еще нет нулевого байтового разделителя.

Это позволяет нам взять пример кода C из Википедии и быстро реализовать метод async, который возвращает полное сообщение, как только оно станет доступным. Обертывание этого в метод async* дает поток таких сообщений по мере добавления байтов.

Полный рабочий пример показан ниже:

import 'dart:typed_data';

import 'package:buffer/buffer.dart';
import 'package:convert/convert.dart';

void main() async {
  final reader = ByteDataReader(copy: true);
  cobsDecode(reader).listen((o) => print(o));

  reader.add(hex.decode('01010100'));
  reader.add(hex.decode('031122023300'));
  reader.add(hex.decode('031122')); // the above block split in 2
  reader.add(hex.decode('023300'));

  // add some more bytes later
  Future.delayed(
    Duration(seconds: 2),
    () => reader.add(hex.decode('031122023300')),
  );
}

Stream<Uint8List> cobsDecode(ByteDataReader reader) async* {
  while (true) {
    yield await _readBlock(reader);
  }
}

Future<Uint8List> _readBlock(ByteDataReader reader) async {
  final output = BytesBuilder();
  var code = 0xff;
  var block = 0;
  while (true) {
    await reader.readAhead(1); // ensure at least one byte available
    if (block != 0) {
      output.addByte(reader.readUint8()); // todo - add non-zero check
      block--;
    } else {
      block = reader.readUint8();
      if (block != 0 && code != 0xff) {
        output.addByte(0);
      }
      code = block;
      if (code == 0) {
        break;
      }
      block--;
    }
  }
  return output.toBytes();
}

Обратите внимание, что в этом простом примере нет типичной обработки ошибок, например проверки отсутствия нулей внутри блока.

Повышение эффективности, например чтение block байт из устройства чтения за один раз, оставлено в качестве упражнения для чтения.

Спасибо! Это хороший пример! Мне нужно было переупорядочить несколько основных вещей и распечатать результат в шестнадцатеричном формате, чтобы понять его, но я получаю его (в основном фон C... поэтому я все еще думаю последовательно). Мне нужно немного поиграть с _readBlock, чтобы полностью усвоить это, прежде чем пытаться интегрировать концепции в свой код.

kdesroch1 05.06.2024 07:01

Как проверить отдельные элементы в ByteBuilder на наличие значения? Типа 0 в конце? Кроме того, есть ли способ удалить элементы, например, методы RemoveAt или RemoveLast для Uint8List?

kdesroch1 05.06.2024 08:06

Итак, ждите readAhead(1); делает так, чтобы одновременно обрабатывался только один байт? Или именно так работает ByteReader? Функция, принимающая его в качестве входных данных, получает для обработки только 1 байт за раз?

kdesroch1 05.06.2024 08:54

Обратите внимание, что между ByteBuilder и BytesBuilder есть разница. Я просто использовал встроенный в него Дарт. В любом случае нет возможности проверять элементы при создании байтов в построителе байтов. Вы просто их засовываете, а в конце звоните toBytes. В этом коде я просто использую построитель как способ сохранить выходные данные при анализе входных данных. По сравнению с Wikipedia C Impl это эквивалент @param data Pointer to decoded output data.

Richard Heap 05.06.2024 12:11

При декодировании меня интересует не то, что я уже создал, а только следующий входной байт. Если вы хотите манипулировать конечным результатом, подождите, пока после вызова toBytes у вас не появится Uint8List.

Richard Heap 05.06.2024 12:16

Да, я обрабатываю по одному байту от входа к выходу — точно так же, как код Википедии. (Я предполагаю, что вы не обрабатываете мегабайты данных! Поэтому нет необходимости преждевременно оптимизировать.) Коду Dart нужен какой-то способ «подождать», если полное сообщение еще не получено. В этом и заключается цель await reader.readAhead(1);, которая гласит: подождите, пока во входных данных не будет хотя бы один байт. Фактический байт считывается из входных данных с помощью reader.readUint8(), который считывает следующий байт из входных данных (и выдает ошибку, если входные данные пусты).

Richard Heap 05.06.2024 12:21

Вероятно, общий объем данных в пакете будет около 1 тыс. Скорее всего, половина этого. Ах, значит, readUint8() ограничивает количество байтов, извлекаемых за раз? Я предполагаю, что есть способ извлечь блоки байтов, чтобы оптимизировать код, как вы намекаете в этом примере.

kdesroch1 05.06.2024 14:29

Правильно — сравните это с read, которое считывает N байт из входных данных. Итак, если вы знаете, что хотите получить следующие 10 байт за один раз (и вы ожидаете readAhead(10), поэтому знаете, что они доступны), просто вызовите reader.read(10). Честно говоря, с несколькими килобайтами данных оно того не стоит. Хорошая новость в том, что у вас есть функционально правильная версия для сравнения, а также код package:cobs, который можно читать и учиться. (Кстати, на этом этапе вам следует «принять» ответ, если он был полезен.)

Richard Heap 05.06.2024 15:24

Да, это было очень информативно и указало мне правильное направление. Спасибо!

kdesroch1 05.06.2024 19:20

Другие вопросы по теме