Create and send a bluetooth command frame in Cocoa

て烟熏妆下的殇ゞ 提交于 2019-12-07 18:52:18

问题


I'm using the IOBluetooth Cocoa framework to communicate with a bluetooth device. So far I have gotten down the whole process of discovering the device and its services, pairing with it, connecting to it and now I want to send some actual commands, but I'm having some trouble with it. Below is an graphic from the spec for the AVRCP profile that I'm trying to use. You can view the pdf here.

I believe I need to write an 5 byte value as shown by the image:

Here is the method I have right now that writes the data:

- (void)l2capChannelOpenComplete:(IOBluetoothL2CAPChannel*)l2capChannel status:(IOReturn)error {
    NSLog(@"Open Complete");
    NSMutableData *playData = [[NSMutableData alloc] initWithCapacity:5];

    unsigned char ctype = 0x0;
    unsigned char subunit = 0x90;
    unsigned char opcode = 0x7C;
    unsigned char opid = 0x44;
    unsigned char opdata = 0x0;

    [playData appendBytes:&ctype length:8];
    [playData appendBytes:&subunit length:8];
    [playData appendBytes:&opcode length:8];
    [playData appendBytes:&opid length:8];
    [playData appendBytes:&opdata length:8];

    usleep(1000);

    [l2capChannel writeAsync:[playData mutableBytes] length:40 refcon:nil];
}

When that function runs the devices responds with the following hexadecimal value 0x400010.

  1. I'm not even sure I'm approaching this correctly!
  2. Are the values I'm sending correct according the example in the image?
  3. Any help in my endeavor to learn here would be much appreciated!

回答1:


If you're going to be working with AV/C frames a lot, rather than creating a struct (which won't really help with the partial-byte packing), you should create an AVCFrame class that makes it easy to set up these frames, sanity checks the values you give it, has a debugging description, and will handle all the grungy details for you.

Your code can then look like this:

AVCFrame *frame = [AVCFrame frameWithCommandType:AVCCommandTypePlay
                                     subunitType:mySubunitType
                                       subunitID:mySubunitID];
// You likely won't actually be writing to the L2CAPChannel. See below.
[l2capChannel writeAsync:[frame mutableBytes] length:[frame length] refcon:nil];

That's not the best interface. You'll want to read through the AV/C Digital Interface Command Set General Specification.

As far as the byte packing goes (and it will have to happen eventually), you'll want to use something like:

// Returns |subunitType| shifted and masked appropriately for bit_oring
// with subunit ID to create an address octet.
inline UInt8
AVRCAddressSubunitType(UInt8 subunitType) {
   const UInt8 kLeastThreeBytes = 0x07;
   UInt8 shiftedType = (subunitType << 3) & ~kLeastThreeBytes;
   return shiftedType;
}

// Returns |subunitID| masked appropriately for bit_oring with subunit type
// to create an address octet.
inline UInt8
AVRCAddressSubunitID(UInt8 subunitID) {
   const UInt8 kLeastThreeBytes = 0x07;
   UInt8 maskedID = subunitID & kLeastThreeBytes;
   if (subunitID & ~kLeastThreeBytes) {
      NSLog(@"*** %s: subunit ID %#hhx > 0x07 cannot be represented "
            "in the 3 bits allotted. Truncating to %#hhx.",
            __PRETTY_FUNCTION__, subunitID, maskedID);
   }
   return maskedID;
}

- (void)l2capChannelOpenComplete:(IOBluetoothL2CAPChannel *)l2capChannel
                          status:(IOReturn)error {
  /* might be worth looking at the error... */
  NSLog(@"%s: open complete - "
        "error: (system: %#x; subsystem: %#x; code: %#x)",
         __PRETTY_FUNCTION__,
         err_get_system(error), err_get_sub(error), err_get_code(error));

  /* to send, first pack your data into byte-sized variables */
  // some variables...
  // address byte layout is [3:7] = 9 = PANEL; [0:2] = 0 = subunit ID
  UInt8 address = (AVRCAddressSubunitType(0x09) | AVRCAddressSubunitID(0x00));
  // some more variables...

  /* create a mutable data and append the bytes in sequence */
  // some appending...
  [playData appendBytes:&address length:sizeof(address)];
  // more appending...

  /* finally, send all the bytes */
  [l2capChannel writeAsync:[playData mutableBytes]
                    length:[playData length]
                    refcon:NULL];
}

For more details on IOWhatever, look to the extensive IOKit documentation. At least in 10.5, the reference docs (as opposed to programming guides) in the docset were kind of screwy, so you'll do well to look at the headers themselves.

You'll need to consult more documentation than you've looked at so far. The AV/C command frame whose diagram you've included is actually the payload (borne in the Command/Response Message Information field) of an AVCTP frame, which is what you actually have to send over the L2CAP transport. The AVCTP spec sketches a rudimentary API in "Appendix A, AVCTP Upper Interface".

You'll need to either locate or write yourself an AVCTP library in order to send the AV/C command frames. You'll want to have the AVCTP library wrap the L2CAP channel, so that you actually send your command frames via it and receive your command frames from it. Good luck! Interfacing with hardware can be a lot of fun, and you'll learn a lot.




回答2:


That's an awful lot of work just to fill out an array of bytes. Also, you're trying to tack eight bytes onto playData with each of your -appendBytes:length: messages.

For a situation like this, I'd just declare a struct for your BT command frame. NSData doesn't really offer you much here.



来源:https://stackoverflow.com/questions/1386598/create-and-send-a-bluetooth-command-frame-in-cocoa

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!