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.
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.