Добрый день.

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



Но для начала пару пафосных слов, для чего это вообще нужно. Современный мир все больше погружается в идеологию интернет вещей. Сейчас всё друг с другом взаимодействует, холодильник с плитой, утюг с пылесосом и т.д. Много шума подняли из-за ЭКГ в Apple Watch, но современные тонометры, пульсометры, термометры уже давно умеют передавать данные через Bluetooth. И вот это всё нужно как-то соединить в единую сеть. И ключевым элементом в этой сети является, как ни крути, компьютер. В связи с этим возникла задача получать данные с определённого устройства через Bluetooth.

Начну с того, что уже у нас было и что усугубило поиск решения. А у нас было приложение написанное на .Net Core. В чем суть приложения неважно, для простоты будем считать, что у нас просто консоль на .Net Core. Ну а устройство буду называть литерой N.

Первые попытки найти что-то работающее с Bluetooth через C# приведут к библиотеке 32feet.

В NuGet пакетах она звучит так 32feet.NET.

И она, кстати, в своей последней продуктовой редакции, даже находит устройства Bluetooth, но не стандарта BLE [как выяснилось гораздо позже]. Например тот же OnePlus 5T стабильно отыскивался, но необходимое устройство N нет. Параллельно с этим удалось отыскать и официальный ответ автора, что его библиотека с BLE не взаимодействует в принципе, и даже нет смысла пробовать. Хотя на Github и есть предварительная версия InTheHand.Devices.Bluetooth, которая должна поддерживать BLE, но в ней так много все подменено, а документации вовсе никакой нет, что даже скомпилировать проект с идеями взятыми с 32feet.NET не сложилось.

Новые изыскания привели меня к более стандартным решениям, а именно к Universal Windows Platform (UWP). При разработке данной платформы, Microsoft, охваченная идеей универсальности и единым приложением для компьютера и телефона, постаралась и сделала взаимодействие с Bluetooth. И вот здесь как раз всё хорошо работает, но… У нас проект на .Net Core… И этим уже ничего не поделаешь.

Сразу скажу, что решения по взаимодействию библиотек UWP с .Net Core отыскать не удалось и проект пришлось переключать на 4.7.1., благо это не сложно. Хотя были мысли как оставить проект на .Net Core и например сделать отдельный Windows сервис с передачей данных по именованный каналам (named pipe) или поднять WCF сервис и с ним уже наладить взаимодействие, но в нашем случае это не имело практического смысла.



Так что в итоге мы имеем перед стартом:

  • Проект на 4.7.1.
  • Win10 обновлен до версии Version 10.0.17134 Build 17134.

Для начала нужно подключить пару библиотек ручками, а именно

  • «Windows от Universal Windows Platform»
    C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.17134.0\Windows.winmd
  • «System.Runtime.WindowsRuntime»
    C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\System.Runtime.WindowsRuntime.dll

И собственно всё, далее сама работа с устройством идет по документация без проблем.
Взаимодействовать с BLE осуществляется через класс BluetoothLEAdvertisementWatcher
Но нужно не забывать, что не имея API самого устройства что-то толковое сделать не получиться.

Вот мой пример кода, как получить данные с устройства.

Данный код рассчитан на то, что устройство уже добавлено (соединено).

 public class BluetoothObserver
  {
    BluetoothLEAdvertisementWatcher Watcher { get; set; }
    public void Start()
    {
      Watcher = new BluetoothLEAdvertisementWatcher()
      {
        ScanningMode = BluetoothLEScanningMode.Active
      };
      Watcher.Received += Watcher_Received;
      Watcher.Stopped += Watcher_Stopped;
      Watcher.Start();
    }
    private bool isFindDevice { get; set; } = false;
    private async void Watcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
    {
      if (isFindDevice)
        return;
      if (args.Advertisement.LocalName.Contains("deviceName"))
      {
        isFindDevice = true;
        BluetoothLEDevice bluetoothLeDevice = await BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress);
        GattDeviceServicesResult result = await bluetoothLeDevice.GetGattServicesAsync();
        if (result.Status == GattCommunicationStatus.Success)
        {
          var services = result.Services;
          foreach (var service in services)
          {
            if (!service.Uuid.ToString().StartsWith("serviceName"))
            {
              continue;
            }
            GattCharacteristicsResult characteristicsResult = await service.GetCharacteristicsAsync();
            if (characteristicsResult.Status == GattCommunicationStatus.Success)
            {
              var characteristics = characteristicsResult.Characteristics;
              foreach (var characteristic in characteristics)
              {
                if (!characteristic.Uuid.ToString().StartsWith("characteristicName"))
                {
                  continue;
                }
                GattCharacteristicProperties properties = characteristic.CharacteristicProperties;
                if (properties.HasFlag(GattCharacteristicProperties.Indicate))
                {
                  characteristic.ValueChanged += Characteristic_ValueChanged;
                  GattWriteResult status = await characteristic.WriteClientCharacteristicConfigurationDescriptorWithResultAsync(GattClientCharacteristicConfigurationDescriptorValue.Indicate);
                  return;
                }
                if (properties.HasFlag(GattCharacteristicProperties.Read))
                {
                  GattReadResult gattResult = await characteristic.ReadValueAsync();
                  if (gattResult.Status == GattCommunicationStatus.Success)
                  {
                    var reader = DataReader.FromBuffer(gattResult.Value);
                    byte[] input = new byte[reader.UnconsumedBufferLength];
                    reader.ReadBytes(input);
                    //Читаем input
                  }
                }
              }
            }
          }
        }
      }
    }
    private void Characteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
    {
      var reader = DataReader.FromBuffer(args.CharacteristicValue);
      byte[] input = new byte[reader.UnconsumedBufferLength];
      reader.ReadBytes(input);
      //Читаем input
    }
    private void Watcher_Stopped(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementWatcherStoppedEventArgs args)
    {
      ;
    }
  }

Спасибо за внимание.

Комментарии (19)


  1. Imbecile
    20.09.2018 15:05

    У вас какой-то специальный Гугл? ble.net


    1. RussianDragon Автор
      20.09.2018 15:13

      Из документации он работает на «Windows 10 UWP 1511+». Какой смысл его использовать, если есть стандартное решение от Microsoft на том же UWP?
      Была идея найти решение для .Net Core, но как выяснялось — это проблема.


      1. Imbecile
        20.09.2018 16:02

        Был неправ, поторопился. Практически всё, что нашёл, если под Винду, то UWP. Аж странно.


        1. RussianDragon Автор
          20.09.2018 16:07

          В этом и фокус. Ну да ладно, не оценили сути статьи, так не оценили. Тоже опыт в копилку.


  1. Krey
    20.09.2018 16:51

    С BT от UWP засада в том что все взаимодействие должно идти в UI потоке. Т.е. чисто гуишное применение, на что собственно весь UWP и рассчитан. Если нужно сервисное приложение\сборщик данных — лучше в сторону линукса смотреть.


    1. ad1Dima
      21.09.2018 11:18

      UWP умеет в фоновые процессы и сервисы, в консольные приложения, в обработку событий от драйвера.


  1. agat366
    20.09.2018 18:40

    «Сути статьи», действительно, "… не оценили".
    Я, безусловно, приветствую старания автора привнести что-то новое и осознанно-заключенное, но первое что я подумал при прочтении заголовка (а особенно при упоминании .Net Core) — это кросс-платформенный доступ к Bluetooth с использованием C#.
    Потому как, ИМХО, подключению к ГолубомуЗубу через UWP (читай, «более-менее классический Виндовз») — извините, ни на секунду не новинка. UWP, действительно, был задуман как «новый шаг на встречу к мобильным устройствам», и поддержа (в т.ч., довольно удобная) Bluetooth в нем не вызывает удивления.
    Что же, действительно, очень «exciting» — это потенциальное кросс-платформенное обращение к такого рода девайсам из C#. Такое на данный момент (с точки зрения спецификации) выглядит возможным только через Xamarin.



    Но если ближе к делу, то одной из главных непоняток цели статьи для меня стало вот это:

    Hello World для получения данных с Bluetooth (BLE) устройства через C#

    C# — это спецификация языка программирования. Конкретная же его реализация уже определяет, что доступно на этой платформе, а что — нет. (Вы знали, что C# используется в Unity-движке, что не имеет никакого отношения к .Net?)
    Так какова Ваша целевая платформа?

    И…
    Сразу скажу, что решения по взаимодействию библиотек UWP с .Net Core отыскать не удалось и проект пришлось переключать на 4.7.1., благо это не сложно.


    Цели «взаимодействия» UWP и Core в данном контексте я не понимаю: если у нас имеется УВП, значит мы — на Виндовсе, значит нам Кор не нужен для такого рода операций.
    Более того, спецификация .NET Core и не предусматривает непосредственное общение с такого рода устройствами. Это все равно что пытаться написать статью «Генерация веб-страниц с использованием .NET Core» (для этого есть, снова таки, ASP.NET Core).

    Или это я что-то не понял?


    1. RussianDragon Автор
      20.09.2018 20:02
      +1

      Или это я что-то не понял?

      И да и нет. Сейчас поясню.:)

      (Вы знали, что C# используется в Unity-движке, что не имеет никакого отношения к .Net?)

      Знаю:) Более того я экспериментировал с разработкой под Unity с учетом портации кода на отображения в браузере. Там тоже много чего интересное всплывает, но об этом хотя бы есть одна нормальная или пара коротеньких статьей. А вот с Bluetooth для .Net была полная засада.

      но первое что я подумал при прочтении заголовка (а особенно при упоминании .Net Core) — это кросс-платформенный доступ к Bluetooth с использованием C#.

      Данная статья писалась скорее не для минутного хайпа на habr, а для её индексации на яндекс или google поиске.
      Я пытался вспомнить, что я искал в поиске и какие ключевые слова использовал, чтобы найти хоть какую-то информацию. И после уже использовал их в данной статье, для людей, которые также будут искать хоть какую-то информацию в сети по Bluetooth 4.0.

      Всё, что сейчас выдается при попытке поиска «Bluetooth .net», на русском или англиском языке, датировано 3 — 5 годами ранее. А самое страшное, что если вопрос о bluetooth и имеет хоть какой-то ответ, то поголовно ведет на библиотеку 32feet.NET.

      Кстати о 32feet.NET. Я не стал это описывать в статье, т.к. старался сделать её максимально лаконичной, но вот Вам пара интересных фактов об работе 32feet.NET.
      В сети в основном имеется два подхода к написанию watcher-а на 32feet.NET. И если самый распространённый из них не ищет устройство вовсе
      client = new BluetoothClient();
      devices = client.DiscoverDevices();
      if (devices.Length > 0)
      {
          foreach (var device in devices)
          {
              lstBTDevices.Items.Add(device.DeviceName);
          }
      }
      else
      {
          MessageBox.Show("Unable to detect any bluetooth devices");
      }
      

      то со вторым все не так однозначно.
      BluetoothAdapter localComponent = new BluetoothAdapter();
      
      localComponent.DiscoverDevicesProgress += new EventHandler<DiscoverDevicesEventArgs>(component_DiscoverDevicesProgress);
      localComponent.DiscoverDevicesComplete += new EventHandler<DiscoverDevicesEventArgs>((s, e) =>{
              Console.WriteLine(">>>");
              localComponent.DiscoverDevicesAsync(255, true, true, true, false, null);
      });
      localComponent.DiscoverDevicesAsync(255, true, true, true, false, null);


      Второй также не ищет устройство (которое находится в режиме обнаружения), но до тех пор, пока не включишь стандартный windows поиск… Как только включается windows поиск происходит магия и я вижу устройство N. Но только пока идет windows поиск… Естественно я матерился, но пытался понять, что я делаю не так с устройством, тем более пресловутый OnePlus 5T виделся с 32feet.NET стабильно.
      Потом я копался в исходниках самой библиотеки 32feet чтобы найти решение. Нашел там кучу костылей и unsafe кода или кода который просто лезет в реестры и прочую магию. Но всё это я решил «опустить» в статье дабы облегчить её восприятия, для человека, который будет «быстро» искать решение.
      Я, так же, как и Imbecile, не мог поверить, что на C# нет решения, но правда оказалась суровей чем я ожидал.
      Более того я сторонился любого упомненная об UWP, т.к. считал, что это уведет меня сторону от решения мой задачи.

      значит нам Кор не нужен для такого рода операций.

      Тут ответ простой. Проект относительно новый, а Core дает больше возможностей, а как следствие создавать его под устаревающие технологии смысла не было.

      Как-то так


    1. RussianDragon Автор
      20.09.2018 20:31

      Потому как, ИМХО, подключению к ГолубомуЗубу через UWP (читай, «более-менее классический Виндовз») — извините, ни на секунду не новинка.

      Возможно. Но все сталкиваются с чем-то в первый раз и пытаются найти решение.

      Я сильно постарался чтобы статья не воспринималась как «мега открытие», а скорее, как подсказка — что именно делать.

      Написал полноценный пример кода дабы полностью оправдать фразу «Hello World» в заголовке. А не просто текст — «ребята, не получилось, можете и не пробовать.».

      Более того найти официальный и нормальный пример работы с устройством через UWP-шный BluetoothLEAdvertisementWatcher без знания ключевых слов — целая проблема.
      Например, на эту ссылку, официального msdn, выйти по запросу BluetoothLEAdvertisementWatcher практически не реально

      А даже если и выйдете, то примера для случия Indicate characteristic там нет. И опять же в инете только пара (а может вообще всего одна годная ссылка) с адекватным примером по этому поводу.

      это кросс-платформенный доступ к Bluetooth с использованием C#.

      Именно по этой причине я не стал выносить вразу .Net в заголовок.


      1. agat366
        20.09.2018 22:53

        При всем уважении к Вашим стараниям, я б, все же, Вам посоветовал хорошенько разобраться в терминах и .NET-related технологиях.

        Говорить…

        Именно по этой причине я не стал выносить вразу .Net в заголовок.


        … (т.е., обобщать .NET) — все равно что говорить «напишу что-то на C#».
        Давно прошли те дни, когда говорилось «С#», подразумевалось ".NET", и наоборот.
        И про Unity я упоминал не для того, чтоб «расширить» область применения Вашей статьи, а наоборот. Я это к тому говорил, что на месте C# сегодня можно было бы подставить что угодно другое: F#, VB.NET или даже Python for .NET. Они все (в той или иной мере) используются для .NET, но в то же время .NET — это слишком широкое понятие.

        Данная статья писалась скорее не для минутного хайпа на habr, а для её индексации на яндекс или google поиске.


        Мне кажется, Вы этой статьей наоборот усложнили поиск решения. Потому как я до сих пор не могу до конца понять, что именно Вы хотели этим решить: сначала велось про .NET Core, потом оказалось, что это не сработает, и закончили тем, что «ну да, попробуем стандартное UWP».

        Но возможность реализации той или другой функциональности в наши дни исходит, в первую очередь от платформы (фреймвока), который что-то поддерживает или нет.
        Я б еще понял, если б статья называлась «Попытка подключения к BluetoothLE-устройству средствами .NET Core», но… см. выше.

        Ко всему этому, можете объяснить, чем вот эта библиотека Вам не подходит?
        Там есть и .NET Standard (следовательно, и .NET Core), и всякие другие платформы.
        Разве что, Вам нужно это на Линуксе, и там поддержи ее нет, да. А все остальное — пожалуйста.


        1. RussianDragon Автор
          21.09.2018 10:33

          Ко всему этому, можете объяснить, чем вот эта библиотека Вам не подходит?

          Ну например, по заголовком
          Easy to use, cross platform, REACTIVE BluetoothLE Plugin for Xamarin
          Более того на Windows платформе приведенная библиотека использует «Windows UWP 16299+».

          Мне кажется, Вы этой статьей наоборот усложнили поиск решения.

          Усложнил? Я вроде именно написал, что единственное, на данный момент решение использовать UWP. И рассказал как это сделать, если уже есть готовый проект на .Net Core или 4.7.1

          И опять же это не переписывание какого-то одного ответа с другого форума, а объединение разрознены поисковых результатов в единый совет на простой вопрос
          — «Как получить данные с Bluetooth 4.0 LE c .Net Core?»
          Формально — никак, но есть описанный мной обходной путь.


  1. IL_Agent
    20.09.2018 19:25

    Статья о том, как человек искал BLE для net core и не нашёл. Вот и вся суть.


  1. GeMir
    20.09.2018 23:01

    всё друг с другом взаимодействует, холодильник с плитой, утюг с пылесосом и т.д.
    Даже так бывает?


    1. RussianDragon Автор
      21.09.2018 10:34

      А почему бы и нет. :)


      1. GeMir
        21.09.2018 10:52

        Пример хотелось бы.


        1. RussianDragon Автор
          21.09.2018 11:01

          Модуль Wi-Fi позволяет следить за процессом приготовления со смартфона и настроить взаимодействие плиты с другими бытовыми приборами Samsung.

          www.iguides.ru/main/accessories/flex_duo_umnaya_elektroplita_samsung_s_podderzhkoy_wi_fi

          То есть, холодильник управляет освещением, температурой, камерами наблюдения и тому подобными штуками.

          wylsa.com/samsung-family-hub

          В удобном приложении Ready for Sky вы можете включить или отключить SkyIron RI-C250S, отследить статус работы утюга и даже его положение в пространстве.

          redmond.company/ru/products/umnye-utyugi/utyug-redmond-skyiron-c250s


  1. windrider
    21.09.2018 01:06

    У меня недавно была схожая задачка, только девайс был просто bluetooth 4.0 (не LE): ловить нажатия кнопок на девайсе windows service'ом. Happy end не наступил. 32feet девайс (и его сервисы) видел, но подключаться не хотел. Winmd вообще никакой реакции не проявлял (событие Received никогда не наступало). К сожалению LE девайса под рукой не оказалось (чтобы уточнить кто виноват).

    Кстати, вопрос по представленому коду:

    if (!service.Uuid.ToString().StartsWith("serviceName"))
    

    if (!characteristic.Uuid.ToString().StartsWith("characteristicName"))
    

    Как это? Ведь оба Uuid'a (1, 2) имеют тип Guid.


    1. RussianDragon Автор
      21.09.2018 01:15

      Да, там guid-ы. Но тут я уже перестраховался исходя из API устройста. В ней, в описывалась только стартовая часть guid-а как «якорь», то решил проверять не весь его, а только первую часть. Фиг знает, как там производитель мудрит с ними. В нормальном коде там часть guid-а


  1. ioppoi
    22.09.2018 23:51

    спасибо