inthehand / 32feet

Personal Area Networking for .NET. Open source and professionally supported
https://inthehand.com/components/32feet/
MIT License
832 stars 209 forks source link

Discover devices doesn't discover device (zebra zd621), but I can find it via microsoft bluetooth #418

Closed markat1 closed 5 months ago

markat1 commented 5 months ago

I'm using

library: InTheHand.Net.Bluetooth version 4.1.43. framework: .net 6 (also possible to use .net 8) target version: 10.0.22621.0 target os: windows application: WPF application framework: prism

I tried discovering my label machine (zebra zd621), but I'm unable to show it via:

  public IReadOnlyCollection<BluetoothDeviceInfo> GetNearbyDevicesViaBluetooth()
  {
      using var client = new BluetoothClient();

      return client.DiscoverDevices(3);
  }

this is what it finds via debugging - it finds some phones, but not the zd621: image

I can find zebra zd621 device on my phone and microsoft bluetooth.

image

I tried setting inqueryLength, but I get an error back saying System.PlatformNotSupportedException: 'Operation is not supported on this platform.':

   public IReadOnlyCollection<BluetoothDeviceInfo> GetNearbyDevicesViaBluetooth()
   {
       using var client = new BluetoothClient();

       client.InquiryLength = TimeSpan.FromSeconds(120);

       return client.DiscoverDevices(3);
   }

Error:

TimeSpan IBluetoothClient.InquiryLength { get => TimeSpan.Zero; set => throw new PlatformNotSupportedException(); }

SydneyOwl commented 5 months ago

maybe zebra zd621 is a ble device so you should use InTheHand.BluetoothLE instead?

markat1 commented 5 months ago

thank you so much for your reply @SydneyOwl !

hmm, I will definately try with BluetoothLE!

If I look in the documentation for zebra zd621 it says that it can both handle bluetooth classic and bluetooth LE

image

In the setup I can choose between classic and bluetooth or use both (classic + bluetooth LE)

image

markat1 commented 5 months ago

@SydneyOwl do you have a sample on howto use BluetoothLE? I'm not sure if the following is using Bluetooth LE.

   public IAsyncEnumerable<BluetoothDeviceInfo> GetNearbyDevicesViaBluetoothAsync()
   {
       using var client = new BluetoothClient();
       return client.DiscoverDevicesAsync();
   }
SydneyOwl commented 5 months ago

Hello @markat1 Seems like you're not using ble since you're using BluetoothDevice instead of BluetoothLEDevice. Here's my example:

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using InTheHand.Bluetooth;
using SenhaixFreqWriter.Constants.BLE;
using SenhaixFreqWriter.Utils.BLE.Interfaces;
using SenhaixFreqWriter.Utils.Serial;

namespace SenhaixFreqWriter.Utils.BLE.Platforms.Generic;

public class GenerticShxble : IBluetooth
{

................  

    public async Task<bool> ScanForShxAsync()
    {
        var filter = new BluetoothLEScanFilter
        {
            Name = BleConst.BtnameShx8800
        };
        try
        {
           // this will call a native windows ble search window
            _shxDevice = await Bluetooth.RequestDeviceAsync(new RequestDeviceOptions { Filters = { filter } });
        }
        catch
        {
           // a fallback for linux
            var cts = new CancellationTokenSource();
            cts.CancelAfter(5000);
            var discoveredDevices = await Bluetooth.ScanForDevicesAsync(new RequestDeviceOptions
            {
                Filters = { filter }
            }, cts.Token);
            foreach (var discoveredDevice in discoveredDevices)
                if (discoveredDevice.Name.Equals(BleConst.BtnameShx8800))
                {
                    _shxDevice = discoveredDevice;
                    break;
                }
        }

        return _shxDevice != null;
    }
.......
}
SydneyOwl commented 5 months ago

by the way, make sure you're using <TargetFramework>netx.0-windows10.0.19041.0</TargetFramework> when building on windows. see #341 for more!

markat1 commented 5 months ago

You are absolutely right @SydneyOwl ! I also tried your example and it worked! Thank you!

will definately check out https://github.com/inthehand/32feet/issues/341 !

SydneyOwl commented 5 months ago

Glad to be of help to you!😃

markat1 commented 5 months ago

just one last thing @SydneyOwl - do you also have a sample of writing to a BluetoothLEDevice ? (seems like I have to do it via a GATT server? - https://software-dl.ti.com/lprf/sdg-latest/html/ble-stack-3.x/gatt.html)

SydneyOwl commented 5 months ago

@markat1 here's my sample code:

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using InTheHand.Bluetooth;
using SenhaixFreqWriter.Constants.BLE;
using SenhaixFreqWriter.Utils.BLE.Interfaces;
using SenhaixFreqWriter.Utils.Serial;

namespace SenhaixFreqWriter.Utils.BLE.Platforms.Generic;

public class GenerticShxble : IBluetooth
{
    private GattCharacteristic _shxCharacteristic;
    private BluetoothDevice _shxDevice;

    private GattService _shxService;

    public Task<bool> GetBleAvailabilityAsync()
    {
       ...
    }

    public async Task<bool> ScanForShxAsync()
    {
      ....
    }

    public Task ConnectShxDeviceAsync()
    {
        return _shxDevice.Gatt.ConnectAsync();
    }

    public async Task<bool> ConnectShxRwServiceAsync()
    {
        _shxService = await _shxDevice.Gatt.GetPrimaryServiceAsync(
            BluetoothUuid.FromShortId(Convert.ToUInt16(BleConst.RwServiceUuid.ToUpper(), 16)));
        return _shxService != null;
    }

    public async Task<bool> ConnectShxRwCharacteristicAsync()
    {
        _shxCharacteristic = await _shxService.GetCharacteristicAsync(
            BluetoothUuid.FromShortId(Convert.ToUInt16(BleConst.RwCharacteristicUuid.ToUpper(), 16)));
        return _shxCharacteristic != null;
    }

    public async void RegisterSerial()
    {
        _shxCharacteristic.CharacteristicValueChanged += Characteristic_CharacteristicValueChanged;
        MySerialPort.GetInstance().BtDeviceMtu = _shxDevice.Gatt.Mtu;
        await _shxCharacteristic.StartNotificationsAsync();
        MySerialPort.GetInstance().WriteBle = _shxCharacteristic.WriteValueWithoutResponseAsync;
    }

    private void Characteristic_CharacteristicValueChanged(object sender, GattCharacteristicValueChangedEventArgs e)
    {
        foreach (var b in e.Value) MySerialPort.GetInstance().RxData.Enqueue(b);
    }

    public void Dispose()
    {
        ...
    }

}

For a BLE (Bluetooth Low Energy) device, there may be multiple services and characteristics. You need to find the service UUID (in my code, RwServiceUuid) and characteristic UUID(in my code, RwCharacteristicUuid) corresponding to the characteristic used for data interaction. You may be able to find this information in the device's development documentation. then you should connect to the target characteristic of the ble device at first(in my sample, ConnectShxDeviceAsync->ConnectShxRwServiceAsync->ConnectShxRwCharacteristicAsync), after that bind a callback function CharacteristicValueChanged ( in my code i put all data received into a queue) then call StartNotificationsAsync, finally you can call WriteValueWithoutResponseAsync or WriteValueAsync ( depends on your device)to write data to the device!

SydneyOwl commented 5 months ago

I‘m not sure whether proper characteristic is connected or not.. Here’s an example of the zebra iOS sdk: https://github.com/ZebraDevs/LinkOS-iOS-Samples/tree/ZebraPrinterBLEDemo/ZebraPrinterBLEDemo

And here’re the uuids which may possible works: ZPRINTER_DIS_SERVICE_UUID = "0000180A-0000-1000-8000-00805F9B34FB" // Or "180A". ZPRINTER_SERVICE_UUID="38EB4A80-C570-11E3-9507-0002A5D5C51B" / / TARGET SERVICE READ_FROM_ZPRINTER_CHARACTERISTIC_UUID = "38EB4A81-C570-11E3-9507-0002A5D5C51B" //USE THIS TO READ WRITE_TO_ZPRINTER_CHARACTERISTIC_UUID = "38EB4A82-C570-11E3-9507-0002A5D5C51B" //USE THIS TO WRITE

maybe you should try to connect to services and characteristics directly from the uuids? If it doesn’t work, try using writewithresponse instead of writewithoutresponse?

markat1 commented 5 months ago

@SydneyOwl Ups sorry I might have deleted my question - but here is where I am right now.

  1. So first I choose ZD621 In the list of devices I get back from Bluetooth.ScanForDevicesAsync()

image

  1. Then I choose my primary service - right now choosing index 3. image
  2. Then I connect to the gatt server via await labelDevice!.Gatt.ConnectAsync()
  3. Then I call service.GetCharacteristicsAsync and receives only back charactericstics with property "Indicate" image
  4. I set event on characteristicValueChanged and then ran characteric.StartNotificationsAsync()
  5. I then get my byte message and checks if the byte range is within the limit of MTu. Mtu here is 247, so it's seems fine with 138 I suppose. image image
  6. Lastly I run await characteric.WriteValueWithResponseAsync(message) and gets back an error that doesn't really saying thing other than COM exception.

image

SydneyOwl commented 5 months ago

This is really confusing... Have you consulted the SDK mentioned above? It seems like the Zebra printer uses two different characterics for reading and writing respectively, but it appears you're using the same one.

READ_FROM_ZPRINTER_CHARACTERISTIC_UUID = "38EB4A81-C570-11E3-9507-0002A5D5C51B" //USE THIS TO READ
WRITE_TO_ZPRINTER_CHARACTERISTIC_UUID = "38EB4A82-C570-11E3-9507-0002A5D5C51B" //USE THIS TO WRITE

one start with 38EB4A81 and the otherstart with 38EB4A82

markat1 commented 5 months ago

@SydneyOwl Hmm okay - so I guess It's better to show you what I can choose from of characteristics - properties confuses me - and if I try not to use indicate it would throw an exception

image

Due to the following image

SydneyOwl commented 5 months ago

image

I don't quite understand what you mean; in fact, as indicated by the red circle in the diagram, the characteristic 38EB4A81 can only be used for indication, and you should bind the callback function for received data to this characteristic and call startnotifyasync on this characteristic; whereas the characteristic 38EB4A82 can be used for read and write, and you should just write data to this characteristic and shouldn't use it for indication.

markat1 commented 5 months ago

@SydneyOwl Sorry for being a bit rookie here :) - what confuses me is the following expression marked with a red circle - should I comment it out? If I only want to do writes, then I can't really use indication right? image

then do: image

SydneyOwl commented 5 months ago

In fact, it's not like that... Could you paste function ConnectToZd621 at here?

markat1 commented 5 months ago
   private void Characteristic_CharacteristicValueChanged(object? sender, GattCharacteristicValueChangedEventArgs e)
   {

       var t = sender;
       var p = e;
   }

   private static string GetMessageByItemNumber()
   {

       return @"^XA:^PR2:^LRY:^LH30,30:^FO0,0^GB400,300,300^FS:^FO20,10^AF^FDZEBRA^FS:^FO20,60^B3,,40^FDAAA001^FS:^PF50:^FO20,160^AF^FDSLEW EXAMPLE^FS:^XZ";
   }

   public async Task ConnectToZd621()
   {

       try
       {
           BluetoothDevice? labelDevice = (await _bluetoothService.GetNearbyDevicesViaBluetoothAsync()).FirstOrDefault(device => device.Name == "ZD621");
           IEnumerable<BluetoothDevice>? labelDevices = await _bluetoothService.GetNearbyDevicesViaBluetoothAsync();

           var service = (await labelDevice!.Gatt.GetPrimaryServicesAsync()).LastOrDefault(s => s.Device.Name == "ZD621");
           var services = await labelDevice!.Gatt.GetPrimaryServicesAsync();

           await labelDevice!.Gatt.ConnectAsync();

           var isConnnected = labelDevice!.Gatt.IsConnected;

           var characterics = (await service!.GetCharacteristicsAsync()).Where(s => s.Properties.ToString() == "Read | Write");

           var characteric = characterics.FirstOrDefault();

           characteric!.CharacteristicValueChanged += Characteristic_CharacteristicValueChanged;

           await characteric.StartNotificationsAsync();

           byte[] message = Encoding.ASCII.GetBytes(GetMessageByItemNumber());

           var mtu = labelDevice.Gatt.Mtu;

           await characteric.WriteValueWithResponseAsync(message);
       }
       catch (Exception ex)
       {
           var t = ex;

           throw;
       }
   }
SydneyOwl commented 5 months ago

maybe this works

public async Task ConnectToZd621()
    {

         try
        {
            BluetoothDevice? labelDevice =
                (await _bluetoothService.GetNearbyDevicesViaBluetoothAsync()).FirstOrDefault(device =>
                    device.Name == "ZD621");
            IEnumerable<BluetoothDevice>? labelDevices = await _bluetoothService.GetNearbyDevicesViaBluetoothAsync();

            await labelDevice!.Gatt.ConnectAsync();

            var service = await labelDevice!.Gatt.GetPrimaryServiceAsync(BluetoothUuid.FromGuid(Guid.Parse("38EB4A80-C570-11E3-9507-0002A5D5C51B")));

            var isConnnected = labelDevice!.Gatt.IsConnected;

            var read_characteric = await service
                .GetCharacteristicAsync(BluetoothUuid.FromGuid(Guid.Parse("38EB4A81-C570-11E3-9507-0002A5D5C51B")));

            var write_characteric = await service
                .GetCharacteristicAsync(BluetoothUuid.FromGuid(Guid.Parse("38EB4A82-C570-11E3-9507-0002A5D5C51B")));

            read_characteric!.CharacteristicValueChanged += Characteristic_CharacteristicValueChanged;

            await read_characteric.StartNotificationsAsync();

            byte[] message = Encoding.ASCII.GetBytes(GetMessageByItemNumber());

            var mtu = labelDevice.Gatt.Mtu;

            await write_characteric.WriteValueWithResponseAsync(message);
        }
        catch (Exception ex)
        {
            var t = ex;

            throw;
        }
    }
markat1 commented 5 months ago

Yes that worked! thank you so much @SydneyOwl !

markat1 commented 5 months ago

Okay one final question ! @SydneyOwl

mtu is not something that I can make bigger? 247 bytes is all I got?

SydneyOwl commented 5 months ago

congratulations! My English is not very good, so you may not fully understand what I mean.. yes mtu is negotiated by the operation system and the device so you can't modify it if the data you want to send is larger than mtu-2 (maybe, data header counts in mtu) you have to split it and send multiple times

markat1 commented 5 months ago

your english are great @SydneyOwl ! Thank you so much for your help - I couldn't have done it without you!

SydneyOwl commented 5 months ago

you're welcome! actually zebra printer has their own sdk here:https://techdocs.zebra.com/link-os/ maybe you can directly use them

oh you're using wpf...they only have maui one so maybe incompatible

markat1 commented 5 months ago

tried that :) - doesn't work - much better using 32feet, because it seperates me from the zebra product :)