MoveUpwards/Gormsson

Peripheral writeValue data format problem

LeeCenY opened this issue · 15 comments

How to use the [UInt8] format to use Gormsson?

let cmd: [UInt8] = [0xFB, 0xFA, 0xF1, 0xFB, UInt8.random(in: 0..<255)]
peripheral.writeValue(Data(cmd), for: characteristic, type:.withResponse)

The approache we take is to create a "Characteristic" with the [UInt8] format.

Then you can use:
manager.write(YourCharacteristic(), value: cmd, type: .withResponse)

For the moment, we didn't make [UInt8] as DataConvertible, but there is an untested possibility:

extension Array: DataConvertible where Element == UInt8 {
    public func toData() -> Data {
        return Data(self)
    }
}

Let me know if it works or if you wanted something else.

image

Is it normal for me to write this way? Learning in swift

For the format, you have to use:

public var format: DataInitializable.Type {
    return [UInt8].self
}

But you have to define:

extension Array: DataInitializable where Element == UInt8 {
    public init?(with octets: [UInt8]) {
        self = octets
    }
}

If the 2 extensions are working, I can ask to add it to Nevanlinna to avoid simple type extension in your code.

Report an error:Ambiguous reference to member 'write(_:value:type:result:)'

image

image

Do you have multiple project?
On #16 you do have a GPSControl with UInt8 format. In our exemple, we use it to control the gps with start and stop call.
Here you do have again a GPSControl but with an [UInt8] so you want to write some informations. I also can see you still try to cast the value's result as GPSControlEnum (an UInt8) that will always fail because the GPSControl here should return an [UInt8].
So what I guess is a mismatch on the expected type form xCode and you get this error, but it is just a guess.

Sorry, at #16 is the beginning of the Demo operation (learning), this is to add Gormsson to the project to operate, I want manager.write(YourCharacteristic(), value: [UInt8], type: .withResponse), I want to Send the [UInt8] or Data type, just like the two images above

public final class GPSArrayData: CharacteristicProtocol {
    public var uuid: CBUUID {
        return CBUUID(string: "E001")
    }

    public var service: GattService {
        return .custom("1010")
    }

    public var format: DataInitializable.Type {
        return Array<UInt8>.self
    }
}

You have two ways to manage it:

With extension like shown by @damien-nd:

extension Array: DataInitializable where Element == UInt8 {
    public init?(with octets: [UInt8]) {
        self = octets
    }
}

extension Array: DataConvertible where Element == UInt8 {
    public func toData() -> Data {
        return Data(self)
    }
}

Then you call write on manager as below

let cmd: [UInt8] = [0xFB, 0xFA, 0xF1, 0xFB, UInt8.random(in: 0..<255)]
manager?.write(GPSArrayData(), value: cmd) { result in

}

With custom type:

public final class ArrayType: DataInitializable, DataConvertible {
    private let characteristicData: [UInt8]

    /// DataInitializable init.
    required public init(with octets: [UInt8]) {
        characteristicData = octets
    }

    /// Return Data of the object.
    public func toData() -> Data {
        return Data(characteristicData)
    }
}

Then you call write on manager as below

let cmd: [UInt8] = [0xFB, 0xFA, 0xF1, 0xFB, UInt8.random(in: 0..<255)]
manager?.write(GPSArrayData(), value: ArrayType(with: cmd)) { result in

}

@LeeCenY We have updated GPS demo with an GPSArrayType for reference on master

Thank, it turns out that this method is missing.

extension Array: DataConvertible where Element == UInt8 {
    public func toData() -> Data {
        return Data(self)
    }
}

But I used random,

let cmd: [UInt8] = [0xFB, 0xFF, 0xE8, 0xA5, UInt8.random(in: 0..<255)]
self.manager?.write(GPSControlsss(), value: cmd) { result in
                switch result {
                case .success(let value):
                     print("GPSControl custom:", value)
                case .failure(let error):
                    print("GPSControl start error:", error)
                }
            }

The result print is the same

image

@LeeCenY can you please include your code as plain text instead of screenshot so it's easier to provide support. Also, is it possible to provide your project to have a closer look and be able to do some debug ?

random has changed, and the Bluetooth device has changed, but the response value print output has not changed.

case .success(let value):
                     print("GPSControl custom:", value)

What is the use case to write your command ? Do you send command in a for loop ? Do you wait for the write response before you send a new write command ?

With so few inputs it's quite difficult to provide support. We actually use the lib for an IoT project and do not experienced such problem so i suspect something around the library, but to go further we will need a sample projet to dig into the problem.

Sorry, I didn't provide much feedback before. I made a timed loop send and didn't wait for a response before sending a new write command.

        let queue = DispatchQueue(label: "com.lee.ble", attributes: .concurrent)
        timer = DispatchSource.makeTimerSource(flags: [], queue: queue)
        timer?.schedule(deadline: DispatchTime.now(), repeating: .seconds(1), leeway: .milliseconds(100))
        timer?.setEventHandler {
            let cmd: [UInt8] = [0xFB, 0xFF, 0xE8, 0xA5, UInt8.random(in: 0..<255)]
            manager.write(GPSControl(), value: cmd) { result in
                switch result {
                case .success(let value):
                    print("GPSControl start:", value)
                case .failure(let error):
                    print("GPSControl start error:", error)
                }
            }
        }
        timer?.resume()

No problem. Your problem is due to the fact that you stack all your write command. They will be consume on a FIFO (First In, First Out) only after the writResponse has responded. So when you go step by step, you see only the top command in the queue. If you want to go this way you shall not use writeWithResponse since you just want to queue all request no matter the response is.

More over if i'm not mistaken, since you use concurrent queue for the timer, you can't be sure of the write command order received by the device.

If you use writeWithResponse send a new write command once you get the result callback.

Otherwise use the writeWithoutResponse but same send the new command after the result callback is received.

I understand, thank you very much for your help.