SerialDevice.FromIdAsync() возвращает нулевой последовательный порт

У меня что-то странное происходит с моим кодом при попытке подключения к последовательному порту. При использовании Serial Sample все работает просто отлично, и он подключается с первой попытки. Тем не менее, с моим кодом последовательный порт возвращает ноль.

Я нашел обходной путь, вложив вызов для записи последовательного порта в цикле do, пока последовательный порт больше не будет нулевым, и он не будет работать. В среднем требуется 500 петель для подключения, и он всегда подключен. Я знаю, что это опасно, потому что существует бесконечный цикл, если он всегда равен нулю, поэтому я попытался переключить цикл do на цикл for. С циклом for он всегда возвращается как ноль, даже когда я повторяю его 50000 раз. Кто-нибудь еще сталкивался с этим и нашел лучшее решение? Я подключаюсь к Arduino Uno.

Причина, по которой я не просто использую серийный пример кода, заключается в том, что я хочу использовать выпадающее меню и использовать имя порта вместо имени идентификатора устройства. Мой код ниже. Благодарю.

    //lists com ports
    private async void listPorts()
    {
        try
        {
            string aqs = SerialDevice.GetDeviceSelector();
            var dlist = await DeviceInformation.FindAllAsync(aqs);
            status.Text = "Select a device and connect";

            for (int i = 0; i < dlist.Count; i++)
            {
                var port = await SerialDevice.FromIdAsync(dlist[0].Id);
                ConnectDevices.Items.Add(port.PortName);
            }

            comConnect.IsEnabled = true;
            ConnectDevices.SelectedIndex = 0;
        }
        catch (Exception ex)
        {
            status.Text = ex.Message;
        }
    }

    //connects to the selected com port
    private async void comConnect_Click(object sender, RoutedEventArgs e)
    {
        _key = 0;
        status2.Text = "";
        string aqs = SerialDevice.GetDeviceSelector(ConnectDevices.SelectedItem.ToString());
        var dlist = await DeviceInformation.FindAllAsync(aqs);
        if (dlist.Count <= 0)
        {
            status.Text = "No devices found.";
            return;
        }
        try
        {
            do
            {
                serialPort = await SerialDevice.FromIdAsync(dlist[0].Id);
                status.Text = "Connecting to serial port...";
            } while (serialPort == null);

            // Configure serial settings
            serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
            serialPort.BaudRate = 38400;
            serialPort.Parity = SerialParity.None;
            serialPort.StopBits = SerialStopBitCount.One;
            serialPort.DataBits = 8;
            serialPort.Handshake = SerialHandshake.None;
            status.Text = "Serial port configured successfully.";

Я добавил следующее в package.appmanifest

<Capabilities>
 <DeviceCapability Name="serialcommunication">
  <Device Id="any">
   <Function Type="name:serialPort" />
  </Device>
 </DeviceCapability>
</Capabilities>

2 ответа

В моем проекте последовательный порт работает правильно после добавления следующего кода в package.appmanifest.

    <Capabilities>
     <DeviceCapability Name="serialcommunication">
      <Device Id="any">
       <Function Type="name:serialPort" />
      </Device>
     </DeviceCapability>
    </Capabilities>

Переменная последовательного порта должна быть "serialPort".

Проблема здесь (которую вы правильно определили) в том, что ваш listPorts держит открытым соединение с SerialPort.

Класс SerialDevice управляет последовательными портами за вас и гарантирует, что только один процесс может использовать любой данный порт одновременно. В этом случае вместо генерации исключений несколько вызовов для созданияSerialDevice, through вернет null, пока вы не отпустите устройство.

SerialDevice орудия IDisposable поэтому вы должны обернуть его вызовы в using чтобы правильно освободить соединение, когда ваш код выходит за рамки.

//lists com ports
private async void listPorts()
{
    try
    {
        string aqs = SerialDevice.GetDeviceSelector();
        var dlist = await DeviceInformation.FindAllAsync(aqs);
        status.Text = "Select a device and connect";

        for (int i = 0; i < dlist.Count; i++)
        {
            using(var port = await SerialDevice.FromIdAsync(dlist[0].Id))
            {
                ConnectDevices.Items.Add(port.PortName);
            }
        }

        comConnect.IsEnabled = true;
        ConnectDevices.SelectedIndex = 0;
    }
    catch (Exception ex)
    {
        status.Text = ex.Message;
    }
}

С помощью приведенного выше изменения кода вы можете удалить бесконечный цикл в своем comConnect_Click логика:

    // NOTE: possible infinite loop!
    //do
    //{
    status.Text = "Connecting to serial port...";
    serialPort = await SerialDevice.FromIdAsync(dlist[0].Id);
    //
    //} while (serialPort == null);

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

Если ваш код больше не загружает соединение, то ожидание внешнего процесса, который может блокировать соединение, действительно может быть бесконечным или определенно неопределенным.

  • В этом случае было бы лучше прервать процесс подключения:
        status.Text = "Connecting to serial port...";
        serialPort = await SerialDevice.FromIdAsync(dlist[0].Id);
        if(serialPort == null)
        {
            status.Text = "Port busy, please try again later";
            return;
        }
    

ПРИМЕЧАНИЕ: Выбор устройства

Вам следует избегать выбора массивов устройств жесткого кодирования, например dlist[0].Id если вы уже не использовали очень конкретный запрос выбора устройства, так что в списке будет только один (или ноль) элементов.

Конкретный код OP действительно обеспечивает уникальный выбор, если вы следуете за ним дома, не используйте просто первый элемент в списке, особенно если вы перечислили SerialDevices с помощью такого рода кода.

string aqs = Windows.Devices.SerialCommunication.SerialDevice.GetDeviceSelector();
var dlist = await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(aqs, null);

Этот запрос обычно возвращает локальный компьютер с нулевым индексом, а затем каждое последовательное подключенное устройство после этого.

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

var selectedDevice = dlist[0];

Это более очевидный момент для диагностики проблем с нулевым значением или индексом массива в вашем коде.

Что касается бесконечных циклов

Только в асинхронной среде или приложении, где многим процессам (или многим экземплярам одного и того же процесса) может потребоваться доступ к SerialPort, или вы хотите разрешить внешним процессам в той же ОС обращаться к SerialPort, когда им это нужно, если вы беспокоитесь о структура цикла вокруг создания SerialPort. Однако вам следует добавить несколько проверок, позволяющих прервать процесс.

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

var timer = Stopwatch.StartNew();
int attempts = 0;
do
{
    serialPort = await SerialDevice.FromIdAsync(dlist[0].Id);
    attempts++;
    if(serialPort != null)
        break;
    await Task.Delay(1000); // pause between retries
} while (serialPort == null && timer.ElapsedMilliseconds < 10000 && attempts < 5); 
// max wait 10 seconds, or 5 total retries for serial device coms to come online.
timer.Stop();
Debug.WriteLine($"Connection to SerialPort {(serialPort == null ? "FAILED" : "Successs")} in: {timer.Elapsed} ({attempts} attempts)");

if(serialPort == null)
{
    status.Text = "Port busy, please try again later";
    return;
}

Структура вашего приложения такова, что вы хотите поддерживать эксклюзивное соединение с SerialPort и заблокировать доступ к другим процессам, которые также могут захотеть использовать то же устройство, в этом случае вам не нужно удалять SerialPortкогда вы подключаетесь с помощью кнопки Connect, однако вы должны убедиться, что очистилиserialPort переменная экземпляра в методе удаления вашей формы.

Если бы вместо этого вы структурировали все свои звонки так, чтобы всегда создавать новые SerialPortкогда вам это нужно, вам нужно будет убедиться, что вы удалили объект, когда закончите с ним. В этом шаблоне, если вы реализуете цикл while для подключения к устройству вместоusing заявление, вы можете использовать try-finally шаблон:

//connects to the selected com port
private async void comConnect_Click(object sender, RoutedEventArgs e)
{
    _key = 0;
    status2.Text = "";
    string aqs = SerialDevice.GetDeviceSelector(ConnectDevices.SelectedItem.ToString());
    var dlist = await DeviceInformation.FindAllAsync(aqs);
    if (dlist.Count <= 0)
    {
        status.Text = "No devices found.";
        return;
    }
    try
    {
        // NOTE: possible infinite loop!
        do
        {
            serialPort = await SerialDevice.FromIdAsync(dlist[0].Id);
            status.Text = "Connecting to serial port...";
        } while (serialPort == null);

        // Configure serial settings
        serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
        serialPort.BaudRate = 38400;
        serialPort.Parity = SerialParity.None;
        serialPort.StopBits = SerialStopBitCount.One;
        serialPort.DataBits = 8;
        serialPort.Handshake = SerialHandshake.None;
        status.Text = "Serial port configured successfully.";

        // Perform other serial logic... 
    }
    catch (Exception ex)
    {
        status.Text = ex.Message;
    }
    finally
    {
        if(serialPort != null)
        {
            serialPort.Dispose();
            serialPort = null;
        }
    }
}

Этот код хорошо работает с моим com-портом и Arduino:

string aqs = SerialDevice.GetDeviceSelector("COM3");
DeviceInformationCollection dlist = await DeviceInformation.FindAllAsync(aqs);

if (dlist.Any())
{
 deviceId = dlist.First().Id;
}

using (SerialDevice serialPort = await SerialDevice.FromIdAsync(deviceId))
{
// .....
}

Попробуй указать com порт

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