MacからWindows Phoneのテザリングをオンにする
Windows 10 Mobileには、MS-TCCという、bluetoothでリモートからテザリング(Windows phone用語的にはホットスポット)をオンにする機能があります。Windows 10 Mobileのスマホをカバンの中にいれたまま、ノートPCからスマホのテザリングをオンにできて便利、という機能です。
ただこちら、基本的にはWindows 10のPCからでないと使えないのですが、MS-TCCのプロトコルは公開されています。
そこで、Mac OSから、Windows 10 Mobileのテザリングをオンにするツールを自作してみました。
(Macbook + Windows phoneなんて組み合わせで使っている人いない気はしますが・・)
以下のリンクからDLできます。
MS-TCC for Mac
上記appを起動するとステータスバーに「H」というマークが追加されます。すでにテザリング済みのWindows 10 mobile端末があれば、Hをクリックすると表示されます。
テザリングをオンにしたい端末を選択することで、テザリングがオンになります。
(処理の結果は、メッセージボックスで表示されます)
ソースコードについて
このツールですが、swift初心者がswiftを勉強しながら書いたのですが、Mac osでswiftでBluetoothをさわっているコードの例が少なく苦労しました。参考に、UI部分以外のソースコードはりつけておきます。
import Foundation import IOBluetooth struct MSTCCResult{ var targetdevice: IOBluetoothDevice? = nil var iserror: Bool = false var errorMessage: String? = nil // 成功した時のssidの情報 var ssid: String? = nil var passcode: String? = nil } class MSTCCManager: NSObject, IOBluetoothRFCOMMChannelDelegate, IOBluetoothDeviceAsyncCallbacks { var channel: IOBluetoothRFCOMMChannel? var isopen: Bool var writebuffer: NSMutableData var callback: ((MSTCCResult) -> Void)? var targetdevice: IOBluetoothDevice? override init() { self.channel = IOBluetoothRFCOMMChannel() isopen = false writebuffer = NSMutableData(length: 16)! callback = nil targetdevice = nil super.init() } func listMSTCCDevice() -> Array<IOBluetoothDevice>{ var ans = Array<IOBluetoothDevice>() for i in IOBluetoothDevice.pairedDevices() { let device = i as! IOBluetoothDevice let uuid : [UInt8] = [0x23, 0x2e, 0x51, 0xd8, 0x91, 0xff, 0x4c, 0x24, 0xac, 0x0f, 0x9e, 0xe0, 0x55, 0xda, 0x30, 0xa5] let spduuid = IOBluetoothSDPUUID(bytes: uuid, length: 16) let sr = device.getServiceRecord(for: spduuid) if( sr != nil ) { ans.append(device) } } return ans } func connect(devicename: String, callback: ((MSTCCResult) -> Void)? = nil) -> Bool{ for i in IOBluetoothDevice.pairedDevices() { let device = i as! IOBluetoothDevice if (device.name == devicename) { return connect(device: device, callback: callback) } } simpleerror("the tarfet device is not paired") return false } func connect(device: IOBluetoothDevice, callback: ((MSTCCResult) -> Void)? = nil) -> Bool{ if( self.isopen ) { simpleerror( "Error: in operating", callback: callback ) return false; } let ret1 = device.openConnection() if( ret1 == kIOReturnSuccess) { self.isopen = true self.callback = callback self.targetdevice = device device.performSDPQuery(self) } else { simpleerror("open connect failed. code = \(ret1)", callback: callback) } return self.isopen } func sdpQueryComplete(_ device: IOBluetoothDevice!, status: IOReturn) { // SPDの情報更新完了 if( status != kIOReturnSuccess ) { simpleerror("SPD Query Failed. code = \(status)") device.closeConnection() self.isopen = false return } // MS-TCCのGUID let uuid : [UInt8] = [0x23, 0x2e, 0x51, 0xd8, 0x91, 0xff, 0x4c, 0x24, 0xac, 0x0f, 0x9e, 0xe0, 0x55, 0xda, 0x30, 0xa5] let spduuid = IOBluetoothSDPUUID(bytes: uuid, length: 16) // MS-TCCのサービス取得 let sr = device.getServiceRecord(for: spduuid) if( sr == nil ) { // 更新したらMS-TCCのサービスが見つからなかった simpleerror("This Device doesn't support MS-TCC now.") device.closeConnection() self.isopen = false return } var rfcommid = BluetoothRFCOMMChannelID() sr?.getRFCOMMChannelID(&rfcommid) let ret2 = device.openRFCOMMChannelAsync( &channel, withChannelID: rfcommid, delegate: self) if( ret2 != kIOReturnSuccess) { simpleerror("open RFCOMM Channel Error. code = \(ret2)") device.closeConnection() self.isopen = false return } } func rfcommChannelData(_ rfcommChannel: IOBluetoothRFCOMMChannel!, data dataPointer: UnsafeMutableRawPointer!, length dataLength: Int) { let buf = UnsafeBufferPointer(start: dataPointer.assumingMemoryBound(to: UInt8.self), count: dataLength) let datas = Array(buf) var index = 0 if( datas[0] == 2 ) { var result = MSTCCResult() result.iserror = false result.errorMessage = "" index = 3 while UInt8(index) < datas[2] { if( datas[index] == 2 ) { // SSID result.ssid = getStringFromArray( datas, start: index + 3, length: Int(datas[index + 2])) // System.Text.Encoding.ASCII.GetString() } else if( datas[index] == 4 ) { result.passcode = getStringFromArray( datas, start: index + 3, length: Int(datas[index + 2])) // passphrase } index = index + Int(datas[index + 2]) + 3 } if( self.callback != nil ) { callback!(result) } self.isopen = false } else { simpleerror("MS-TCC Error!\(datas[0])") // todo } channel!.close() self.targetdevice!.closeConnection() } func rfcommChannelWriteComplete(_ rfcommChannel: IOBluetoothRFCOMMChannel!, refcon: UnsafeMutableRawPointer!, status error: IOReturn) { if( error != kIOReturnSuccess) { simpleerror("Failed to send data. code = \(error)") self.targetdevice!.closeConnection() self.isopen = false return } } func rfcommChannelOpenComplete(_ rfcommChannel: IOBluetoothRFCOMMChannel!, status error: IOReturn) { if( error != kIOReturnSuccess) { simpleerror("open RFCOMM Channel Error. code = \(error)") self.targetdevice!.closeConnection() self.isopen = false return } if( channel!.isOpen() ) { let cmdata = Data(bytes: [1,0,0]) writebuffer.setData(cmdata) let ret3 = channel!.writeAsync(writebuffer.mutableBytes, length: 3, refcon : nil) if( ret3 != kIOReturnSuccess) { simpleerror("Failed to send data. code = \(ret3)") self.targetdevice!.closeConnection() self.isopen = false return } } else { // 多分こないはず simpleerror("Channel is not opened") self.targetdevice!.closeConnection() self.isopen = false return } } func simpleerror(_ errormessage: String, callback: ((MSTCCResult) -> Void)? = nil ) { if( callback != nil) { var result = MSTCCResult() result.iserror = true result.errorMessage = errormessage callback!( result ) } else if( self.callback != nil ) { var result = MSTCCResult() result.iserror = true result.errorMessage = errormessage self.callback!( result ) self.callback = nil } } func getStringFromArray(_ datas: Array<UInt8>, start: Int, length: Int) -> String { let strarray = datas[start..<(start+length)] // strarray.append(0) var byteBuffer = [UInt8]() strarray.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) in byteBuffer += bytes } let str = String(bytes: byteBuffer, encoding: String.Encoding.ascii) return str! } func connectionComplete(_ device: IOBluetoothDevice!, status: IOReturn) { } func remoteNameRequestComplete(_ device: IOBluetoothDevice!, status: IOReturn) { } }