Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CharacteristicValueChanged doesn't work with indicate for Zebra label printer #420

Open
markat1 opened this issue Jun 19, 2024 · 10 comments

Comments

@markat1
Copy link

markat1 commented Jun 19, 2024

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'm trying to receive information from the gatt server via CharacteristicValueChanged, but it doesn't seem to work.

Characteristic is a "indicate", so it need client to "accept" the responses coming from the gattserver before it proceed. Correct me if I'm wrong.

I'm not sure what to do with descriptor here, but maybe this is where I should do my acknowledge?

  var readValueFromDescriptor = await descriptor.ReadValueAsync();
  await descriptor.WriteValueAsync(readValueFromDescriptor);

  readCharacteric!.CharacteristicValueChanged += Characteristic_CharacteristicValueChanged;
  await readCharacteric.StartNotificationsAsync();

Rest of the code

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 markat1 changed the title CharacteristicValueChanged doesn't work with indicate CharacteristicValueChanged doesn't work with indicate for Zebra label printer Jun 19, 2024
@SydneyOwl
Copy link
Contributor

Hello @markat1 !
I don't quite understand the purpose of these two lines of code:

var readValueFromDescriptor = await descriptor.ReadValueAsync();
await descriptor.WriteValueAsync(readValueFromDescriptor);

if I am correct, it seems like you're reading something from a descriptor, then write it back again?
You don't need to explicitly read data from the Characteristic; You just need to register a callback function and wait for device notifications if Characteristic has an 'indicate' property.
by the way, what kind of data are you expecting to read from the printer? Maybe the printer doesn't send any data back at all!

@markat1
Copy link
Author

markat1 commented Jun 24, 2024

Hello @SydneyOwl ! Thank you so much for your reply!

I was just experimenting with the descriptor, because I read that "indicate" characteristic require acknowledge by the client. The server does not send indication until it gets the acknowledgement back from the client.
(source: https://bleuio.medium.com/bluetooth-low-energy-indication-and-notification-b4a005f7d51b)

If it was just a "notification" characteristic, then I didn't have to do anything other than register a callback function.

But how I "acknowledge" my client, I'm not sure how yet. Mobile phones does acknowledge "automatically", but I'm using WPF so I kinda have to do that manually I think.

callback function like this right?

readCharacteric!.CharacteristicValueChanged += Characteristic_CharacteristicValueChanged;
  await readCharacteric.StartNotificationsAsync();
   
    public void Characteristic_CharacteristicValueChanged(object? sender, GattCharacteristicValueChangedEventArgs e)
    {

        var t = sender;
        var p = e;
    }

I'm not sure if ZD621 sends any data back, but question is:

How am I able to receive information back from ZD621, that it has been turned off via Device_GattServerDisconnected?

Because this works:

 labelDevice.GattServerDisconnected += Device_GattServerDisconnected;

  private void Device_GattServerDisconnected(object? sender, EventArgs e)
  {
        var informationDisconnected = e;
  }

@SydneyOwl
Copy link
Contributor

Sorry for the late reply!
I don't quite understand what you mean…in fact, receiving data from a disconnected ble device is really unusual, you need to keep it connected.
Do you mean ‘Device_GattServerDisconnected’ is called after device is disconnected? This should be normal

@markat1
Copy link
Author

markat1 commented Jun 28, 2024

No worries @SydneyOwl

I'm wondering:

  • how does Device_GattServerDisconnected work? Does it get a signal from zebra zd621? how?
  • How does Device_GattServerDisconnected get called? how does it know zebra zd621 disconnected?

Maybe answers to these questions would lead me to an answer on how/when Characteristic_CharacteristicValueChanged
gets called.

Next question would be:

  • If I'm able to detect GATT server is disconnected, would I also be capable of detecting when GATT server is accessable again?

because if I'm not able to detect wether GATT server is accessible otherwise, then I would have to make a while loop where I repeately call device.Gatt.ConnectAsync until it's connected?


 _bluetoothDevice.GattServerDisconnected += Device_GattServerDisconnected;

 void Device_GattServerDisconnected(object? sender, EventArgs e)
 {
     var blueConnection = sender as BluetoothDevice;
     //_bluetoothDevice!.Gatt.AutoConnect = true;  // only on android

     while (!_bluetoothDevice!.Gatt.IsConnected)
     {

         Task.Run(() =>  _bluetoothDevice.Gatt.ConnectAsync()).GetAwaiter().GetResult();

     }
 }

Reason why I do this? because when I turn off Zebra zd621 and then turns it on again I want my program to reconnect to the device automatically

@SydneyOwl
Copy link
Contributor

I'm not entirely sure about the underlying principles, but polling the connection status should be feasible...However, continuously using task.run in a loop can incur significant overhead. You can blockingly call the connection method within the while loop instead.
My knowledge on this topic is not sufficient. sorry for not being helpful!

@markat1
Copy link
Author

markat1 commented Jul 3, 2024

no worries @SydneyOwl

I have a couple of extra question - I hope you don't mind :)

So when I write to my device, I can choose between WriteValueWithResponseAsync and WriteValueWithoutResponseAsync.

In the implementation, you can see that both methods doesn't return directly any value other than Task.
Yet one is called WriteValueWithResponseAsync.

Question

  • what does it mean "with response"?
  • who's getting the response when I call WriteValueWithResponseAsync ?

Why do you want a response?
I want the response, in order to see if the device has successfull received my write request.

       /// <summary>
       /// Performs a Characteristic Value write to the Bluetooth LE device.
       /// </summary>
       /// <param name="value">The data to be written to the Bluetooth LE device.</param>
       /// <returns></returns>
       public Task WriteValueWithResponseAsync(byte[] value)
       {
           Bluetooth.ThrowOnInvalidAttributeValue(value);
           return PlatformWriteValue(value, true);
       }

       /// <summary>
       /// Performs a Characteristic Value write to the Bluetooth LE device.
       /// </summary>
       /// <param name="value">The data to be written to the Bluetooth LE device.</param>
       /// <returns></returns>
       public Task WriteValueWithoutResponseAsync(byte[] value)
       {
           Bluetooth.ThrowOnInvalidAttributeValue(value);
           return PlatformWriteValue(value, false);
       }

@peterfoot
Copy link
Member

WriteWithResponse doesn't mean you have to do anything on the receiving side as it's handled in the stack. The operation will require a response from the remote Bluetooth device so may take longer but it should pass through any errors rather than being a fire-and-forget approach. You need to check the Properties of the characteristic to see which methods it supports, and only use the supported write type.

@markat1
Copy link
Author

markat1 commented Jul 4, 2024

Thank you so much for your reply @peterfoot

I have the following 4 properties I can use

image

I was wondering how I can make use of Indicate in order to receive information from the device (here it's zebra zd621).

What is critical for me to know is what state device is in, and wether I'm capable of writing to the device.

Sometimes what I experience is that suddently I lose connection to the read and write characteristics, and I don't know why.

Odd thing is that I can receive the service via _bluetoothDevice!.Gatt.GetPrimaryServiceAsync , but sometimes I can't receive it's characteristics by calling service.GetCharacteristicAsync.

Only way I solve it right now is restarting the device and my application - but that's not ideal for me

Could I write att commands and get reply back?

image
source: bluetooth-le-primer-page-59

@peterfoot
Copy link
Member

If the property lists Write that means a WriteWithResponse. The library abstracts the difference between Notify and Indicate properties - you have to add an event handler for the CharacteristicValueChanged event on the Characteristic and call StartNotificationsAsync().

@markat1
Copy link
Author

markat1 commented Jul 4, 2024

@peterfoot
Yep, did that - unfortunately no response back when writing label to the device - maybe zebra zd621 doesn't respond any data back? or perhaps commands I write to the device doesn't make it respond back?

 _gattCharacteristic = await service.GetCharacteristicAsync(BluetoothDeviceDto.Read) ?? throw new GattServerConnectionException($"characteristic {BluetoothDeviceDto.Read} not found");

 _gattCharacteristic!.CharacteristicValueChanged += GattCharacteristic_CharacteristicValueChanged;
 await _gattCharacteristic.StartNotificationsAsync();

Anyway someone from zebra is suppose to call me this week - hopefully they can tell me if the zebra zd621 is responding back
or not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants