Plutoberth/SonyHeadphonesClient

macOS Support

Closed this issue ยท 25 comments

Please react to this issue if you want macOS support

Working on it ๐Ÿ‘
(i don't have much experience in c++ but I will try to make it work)

Working on it ๐Ÿ‘
(i don't have much experience in c++ but I will try to make it work)

Awesome! I'm here if you have any questions about the code.

progress update - got the application compiling to .app with the demo ui, so I still need to make the ui implementation. The bluetooth connection seems a bit more challenging, see https://stackoverflow.com/a/59413196/9729726, looks like I can't easily send data using sockets. Any ideas?

progress update - got the application compiling to .app with the demo ui, so I still need to make the ui implementation. The bluetooth connection seems a bit more challenging, see https://stackoverflow.com/a/59413196/9729726, looks like I can't easily send data using sockets. Any ideas?

My friend @Mr-M33533K5 worked on this for a bit, and IIRC he reached the conclusion that it's possible.

See:

https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/Bluetooth/BT_Develop_BT_Apps/BT_Develop_BT_Apps.html

https://developer.apple.com/documentation/iobluetooth/iobluetoothrfcommchannel

https://developer.apple.com/documentation/iobluetooth

progress update - I got the gui working I guess, can't really test it because it crashes on the bluetoothconnector, so the only thing to do is to make the bluetooth connector

progress update there is some working bluetooth yay
image

progress update there is some working bluetooth yay
image

Awesome! Should probably be pretty easy from here unless Apple's RFCOMM support is borked.

image

I am not done yet, but need your help, on the bluetooth connector :) Please check out my pull request. You can build the project using the command `make` in the macOS directory in client
image I am not done yet, but need your help, on the bluetooth connector :) Please check out my pull request. You can build the project using the command `make` in the macOS directory in client

I can't build the project because I don't have a Mac, and AFAIK setting up a mac VM is a pain. I do have some comments over the PR, but I'm pre-occupied with another task ATM so it might take me a week or so before I can dedicate some time for it.

@semvis123
I've built your code on 10.14.6 and can successfully detect my WH-1000XM3.

When I click "connect", the following error message occurred: (It failed when calling openRFCOMMChannelSync().)

2020-11-02 01:49:14.884 sonyheadphonesclient[6904:148304] [establishKernelConnection] Received an error from IOServiceOpen() - 0xe00002c7.  NULLing out io_service_t.

IOServiceOpen() failed with error code 0xe00002c7, which according to apple documents, is kIOReturnUnsupported.
I am not familiar with macOS and the inner working of it, but it seems like this is an error regarding calling from user space to kernel space.

I am not entirely sure what happened, but in your code you set delegate to nil when opening a RFCOMM channel, but according to the document https://developer.apple.com/documentation/iobluetooth/iobluetoothdevice/1431606-openrfcommchannelsync?language=objc :

delegate
the object that will play the role of delegate for the channel. A channel delegate is the object the rfcomm uses as target for data and events. The developer will implement only the the methods he/she is interested in. A list of the possible methods is at the end of the file "IOBluetoothRFCOMMChannel.h" in the definition of the protocol IOBluetoothRFCOMMChannelDelegate.

It doesn't seem like using nil as delegate should really work.

	// create new channel
	IOBluetoothRFCOMMChannel *channel = [[IOBluetoothRFCOMMChannel alloc] init];
	if ([device openRFCOMMChannelSync: &channel withChannelID:1 delegate: nil] == kIOReturnSuccess) {
		const int connectResult = kIOReturnSuccess;
		printf("%d", connectResult);
		this->_connected = true;
	}

Hmmm, it should work though, I saw it in an old file, where it uses nil as delegate: source. I guess it's because the channel id is probably incorrect. But I don't know for sure. Note: saw most people use self for delegate but that didn't compile.

@semvis123
I don't think channel id is likely the case though. IOServiceOpen is used when a user-space app wants to talk to a kernel-space service (in our case, the IOBluetoothDevice). And kIOReturnUnsupported is the default return value when a IOService method that is not implemented is called.

Here's the code of IOService: https://fergofrog.com/code/cbowser/xnu/iokit/IOKit/IOService.h.html
See the return value of callPlatformFunction()

@ result An IOReturn code; kIOReturnSuccess if the function was successfully executed, kIOReturnUnsupported if a service to execute the function could not be found.

So it seems like our app cannot talk to the kernel and bluetooth driver properly at all. (notice the original error message is during establishKernelConnection).

I've saw some apps that use these api and I've seem some complain about their app not working (same error IOServiceOpen() - 0xe00002c7) after upgrading to Sierra.

I would guess that apple changed the kernel in a way that breaks these super old apis? (bluetooth related functionalities is added to OSX back in 2002). Or apple just dropped the support deliberately. I don't know for sure.

Considering that Sony Headphones Connect has an iOS app, which surely uses RFCOMM as well, I'm sure that the kernel still supports it though. I might give Swift a try and see if things are different.

@Miigon From what I read, RFCOMM is available on iOS only for companies that got approval from Apple (otherwise you only have GATT), and that it's available for everyone on macOS. Docs linked above seem to imply that this is the case. I know very little about macOS though, so take that with a grain of salt.

Found this on Github, but when it connects to the wh-1000-xm3 it gives an error which indicates that the device isn't an SPP/RFCOMM device. Is this correct or is this something Sierra(and up) related?

@semvis123

Well, it expects the rfcomm device to be the first device that you Mac have connected to. But in my case my 1000xm3 is the second device(I know it from Xcode debugger).

I changed the code so that it will try to connect to my 1000xm3, instead of connecting to my mouse, which is the first device.

// line 75, MacosBT.mm
IOBluetoothDevice *device = [deviceArray objectAtIndex:1]; // objectAtIndex:0  ->  objectAtIndex:1

Another thing I changed is this line:

// line 78, MacosBT.mm  and  line 96, MacosBT.mm
IOBluetoothSDPUUID *sppServiceUUID = [IOBluetoothSDPUUID uuid16: kBluetoothSDPUUID16RFCOMM];
// uuid16: kBluetoothSDPUUID16ServiceClassSerialPort   ->  uuid16: kBluetoothSDPUUID16RFCOMM

The original code is looking for a ServiceClassSerialPort service, which the 1000xm3 doesn't have.
By changing it to look for kBluetoothSDPUUID16RFCOMM, it detects the RFCOMM service of the device successfully. (which means the Mac surely knows the 1000xm3 is a RFCOMM device)

After these changes, the program can run past the two "not SPP/RFCOMM device" checks and can actually get rfcommChannelID(btw, for me it's 0x10)

And guess what? openRFCOMMChannelSync() now actually returns kIOReturnSuccess!

Hello, Bluetooth RFcomm reception demo.
Attempting to connect
We have 4 paired device(s).
device name = WH-1000XM3
Successfully connected

No NULLing, no nothing. it just connects!

I suspect that the channelID is the cause of problem, so I put withChannelID: 0x10 to your code(MacOSBluetoothConnector.mm).

Sure enough, it connects.

screenshot

So a channelID of 0x10 works, but I think it would still be a good idea to use getRFCOMMChannelID() to get the correct channelID dynamically.

Wow that's some good information!
I adjusted my fork and pull request, now it chooses the channelID dynamically, and thus successfully connects.

The next step is to make the opened channel globally accessible, so it can be accessed in the other functions (like disconnect).

Note: the headphones can currently only connect once because it probably doesn't disconnect (if you turn the headphones off and back on it will work again).

Got some work done! The connection works and disconnect works kinda, when the channel is closed it cannot be opened again (currently solved by disconnecting from the device).

Send data and receive data are now also correctly structured, send command doesn't work though ( do I need to use the byte magic file? or is this maybe cause the function doesn't return anything? or another possibility, is this because of the receive function not working?), receive always prints null (string conversion error maybe?).

pull request and fork have been updated

  • working channel
  • correct file structure
  • working send function
  • working receive function
  • reopening channel without disconnecting

@semvis123, The recv function has to work since the protocol has some synchronization built in, and that requires our side to receive some data. I'll take a look at everything on Monday.

how's everything going buddies?

@Miigon Some progress has been made!
The receive function has still a few problems, and there are some problems rewriting the bleutooth connector to not use an extra (probably unneeded) thread.

Check my pull request for the most up to date information.

@Miigon Could you test the new version please?

@Plutoberth Sure, I've done testing it. It isn't quite working yet. I've detailed everything in the pull request comment.

@Miigon thanks!

Forgot to close this :)