성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - 32feet.NET을 이용한 PC 간 Bluetooth 통신 예제 코드</h1> <p> 블루투스 통신이 의외로 라이브러리가 많지 않습니다. 검색해 보면, 그나마 가장 많이 나오는 라이브러리가 32feet.NET인데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 32feet.NET ; <a target='tab' href='https://github.com/inthehand/32feet'>https://github.com/inthehand/32feet</a> </pre> <br /> 일단 사용법은 미리 블루투스 기기 간 Pairing을 해두어야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > WPF/WinForm에서 UWP의 기능을 이용해 Bluetooth 기기와 Pairing하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/12001'>http://www.sysnet.pe.kr/2/0/12001</a> </pre> <br /> 이후, Pairing된 기기를 검색하는 방법으로 DiscoverDevices를 사용할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // http://blogs.microsoft.co.il/shair/2009/06/21/working-with-bluetooth-devices-using-c-part-1/ // <a target='tab' href='https://www.nuget.org/packages/InTheHand.Net.Bluetooth/'>Install-Package InTheHand.Net.Bluetooth</a> { BluetoothClient bc = new BluetoothClient(); foreach (BluetoothDeviceInfo bdi in bc.DiscoverDevices()) { Console.WriteLine($"{bdi.DeviceName}"); } } </pre> <br /> 제 경우에, Surface Pro 6와 raspberry pi zero, Galaxy Note 9을 pair시켰을 때 위의 출력 결과는 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > raspberrypi Galaxy Note9 SURF6PRO </pre> <br /> 또 다른 메서드로 DiscoverDevicesInRange가 있는데 이것은 결국 DiscoverDevices 메서드를 내부적으로 옵션만 조정해 호출하는 것과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DiscoverDevices == DiscoverDevices(255, authenticated: true, remembered: true, unknown: true); DiscoverDevicesInRange == DiscoverDevices(255, authenticated: false, remembered: false, unknown: false, discoverableOnly: true); </pre> <br /> 그래서 실제로 호출해 보면 다음과 같은 결과를 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > foreach (BluetoothDeviceInfo bdi in bc.DiscoverDevicesInRange()) { Console.WriteLine($"{bdi.DeviceName}"); } /* 출력 결과: 머신 A의 경우 SURF6PRO */ /* 출력 결과: 동일한 블루투스를 연결한 머신 B의 경우 raspberrypi */ </pre> <br /> DiscoverDevicesInRange의 경우 authenticated: false이므로 인증받지 않은 기기만 열람이 되어야 하는데 (나중에 나오지만 모든 기기는 authenticated == true 상태입니다.) 실제로는 그렇지 않습니다. 음... 규칙을 모르겠군요. ^^; (혹시 아시는 분은 덧글 부탁드립니다.)<br /> <br /> 어쨌든, 이런 이유로 인해 아마 대부분의 경우 DiscoverDevices를 호출하게 될 것입니다. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> DiscoverDevices의 경우 동기 방식으로 실행하는데 discovering을 위한 일정 시간을 반드시 대기하게 되므로 사용 시 좀 불편합니다. 즉, 내부적으로는 paired 블루투스 장치를 이미 발견했는데도 그 시간이 완료될 때까지 실행이 블록킹되므로 사용자 입장에서 좀 답답함을 느끼게 되는데요. 이 문제를 DiscoverDevicesAsync 메서드를 사용하면 해결할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // <a target='tab' href='https://stackoverflow.com/questions/42701793/how-to-programatically-pair-a-bluetooth-device'>https://stackoverflow.com/questions/42701793/how-to-programatically-pair-a-bluetooth-device</a> { BluetoothClient bluetoothClient; BluetoothComponent bluetoothComponent; bluetoothClient = new BluetoothClient(); bluetoothComponent = new BluetoothComponent(bluetoothClient); bluetoothComponent.DiscoverDevicesProgress += _bluetoothComponent_DiscoverDevicesProgress; bluetoothComponent.DiscoverDevicesComplete += _bluetoothComponent_DiscoverDevicesComplete; <span style='color: blue; font-weight: bold'>bluetoothComponent.DiscoverDevicesAsync(255, false, true, true, false, null);</span> } private static void _bluetoothComponent_DiscoverDevicesComplete(object sender, DiscoverDevicesEventArgs e) { Console.WriteLine("_bluetoothComponent_DiscoverDevicesComplete"); } private static void _bluetoothComponent_DiscoverDevicesProgress(object sender, DiscoverDevicesEventArgs e) { foreach (var item in e.Devices) { Console.WriteLine(item.DeviceName); Console.WriteLine($"\tConnected {item.Connected}"); Console.WriteLine($"\tAuthenticated {item.Authenticated}"); Console.WriteLine($"\tDeviceAddress {item.DeviceAddress}"); } } </pre> <br /> 위의 코드를 수행하면 제 경우에 다음과 같은 결과를 얻을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Galaxy Note9 Connected False Authenticated True DeviceAddress 58AFE9B2E35E SURF6PRO Connected True Authenticated True DeviceAddress BEA5C3EF3F7D raspberrypi Connected False Authenticated True DeviceAddress BF32A1CD82EF </pre> <br /> 이것으로 기기 열람은 끝났고, 이제 통신할 수 있는 Bluetooth 서버, 클라이언트 코드만 만들면 됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 우선 서버를 볼까요? 32feet.NET 라이브러리가 이 부분을 잘 추상화했기 때문에 마치 소켓 프로그램처럼 코딩을 할 수 있습니다. 그래서 서버 부분은 다음과 같이 만들 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using InTheHand.Net; using InTheHand.Net.Bluetooth; using InTheHand.Net.Sockets; using System; using System.Net.Sockets; namespace BTServer { class Program { static void Main(string[] args) { Guid serviceUUID = BluetoothService.TcpProtocol; // new Guid("00000004-0000-1000-8000-00805F9B34FB"); BluetoothEndPoint bep = new BluetoothEndPoint(BluetoothAddress.None, BluetoothService.TcpProtocol); <span style='color: blue; font-weight: bold'>BluetoothListener</span> blueListener = new BluetoothListener(bep); <span style='color: blue; font-weight: bold'>blueListener.Start</span>(); Console.WriteLine("accepting..."); while (true) { <span style='color: blue; font-weight: bold'>Socket socket = blueListener.AcceptSocket()</span>; string text = socket.ReadString(); Console.WriteLine("socket accepted: " + text); socket.WriteString("Echo: " + text); <span style='color: blue; font-weight: bold'>socket.Close();</span> Console.WriteLine("Closed"); } } } } </pre> <br /> 보는 바와 같이 BluetoothListener가 AcceptSocket 메서드를 이용해 소켓도 반환해 주므로 이후의 프로그래밍은 기존 소켓 지식대로 통신을 하면 됩니다.<br /> <br /> 클라이언트 프로그램도 방식은 소켓과 유사합니다. 단지 IP 주소가 아닌, Bluetooth 기기의 MAC 주소가 필요하므로 이것을 외우는 방법이 마땅치 않습니다. 바로 이 때문에 위에서 DiscoverDevices / DiscoverDevicesAsync를 이용한 장치 열람을 한 것입니다. 열람했던 각각의 장치에서 다음과 같이 DeviceAddress를 구할 수 있으므로,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Galaxy Note9 - DeviceAddress 58AFE9B2E35E SURF6PRO - DeviceAddress BEA5C3EF3F7D raspberrypi - DeviceAddress BF32A1CD82EF </pre> <br /> 이 값을 (보관해 두고 향후) 그대로 이용하거나,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > BluetoothAddress targetMacAddress = BluetoothAddress.Parse("58AFE9B2E35E"); </pre> <br /> 열람했을 때의 DeviceAddress 속성을 그대로 사용해도 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private static void bluetoothComponent_DiscoverDevicesProgress(object sender, DiscoverDevicesEventArgs e) { foreach (var item in e.Devices) { if (item.DeviceName != "SURF6PRO") { continue; } Guid serviceUUID = BluetoothService.TcpProtocol; // new Guid("00000004-0000-1000-8000-00805F9B34FB"); BluetoothClient bluetoothClient = new BluetoothClient(); bluetoothClient.Connect(<span style='color: blue; font-weight: bold'>item.DeviceAddress</span>, serviceUUID); Console.WriteLine("Connected"); bluetoothClient.Client.WriteString("Hello"); string result = bluetoothClient.Client.ReadString(); Console.WriteLine(result); bluetoothClient.Close(); Console.WriteLine("Closed"); } } </pre> <br /> 끝입니다. 이제 서버 측 코드를 SURF6PRO에 실행시키고, 그 기기와 pairing을 완료한 또 다른 머신에서 클라이언트 측 코드를 실행하면 서로 연결이 되고 소켓 통신을 하게 됩니다.<br /> <br /> 이 정도면 대충 감이 잡히시죠? ^^<br /> <br /> 관련 코드는 서버와 클라이언트 각각 다음의 github에 올려 두었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DotNetSamples/WinConsole/Bluetooth/pc2pc/BTServer ; <a target='tab' href='https://github.com/stjeong/DotNetSamples/tree/master/WinConsole/Bluetooth/pc2pc/BTServer'>https://github.com/stjeong/DotNetSamples/tree/master/WinConsole/Bluetooth/pc2pc/BTServer</a> DotNetSamples/WinConsole/Bluetooth/pc2pc/BTClient ; <a target='tab' href='https://github.com/stjeong/DotNetSamples/tree/master/WinConsole/Bluetooth/pc2pc/BTClient'>https://github.com/stjeong/DotNetSamples/tree/master/WinConsole/Bluetooth/pc2pc/BTClient</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> 참고로, Bluetooth의 서비스 방식이 꽤나 다양합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [안드로이드] 블루투스 프로토콜 UUID 리스트 ; <a target='tab' href='https://dsnight.tistory.com/13'>https://dsnight.tistory.com/13</a> </pre> <br /> 아직 저도 블루투스에는 초보자라, 이 중에서 제 글에서 설명한 것은 단지 TCP_PROTOCOL_UUID 하나에 불과합니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > SDP_PROTOCOL_UUID = '{00000001-0000-1000-8000-00805F9B34FB}'; UDP_PROTOCOL_UUID = '{00000002-0000-1000-8000-00805F9B34FB}'; RFCOMM_PROTOCOL_UUID = '{00000003-0000-1000-8000-00805F9B34FB}'; <span style='color: blue; font-weight: bold'>TCP_PROTOCOL_UUID = '{00000004-0000-1000-8000-00805F9B34FB}';</span> TCSBIN_PROTOCOL_UUID = '{00000005-0000-1000-8000-00805F9B34FB}'; TCSAT_PROTOCOL_UUID = '{00000006-0000-1000-8000-00805F9B34FB}'; OBEX_PROTOCOL_UUID = '{00000008-0000-1000-8000-00805F9B34FB}'; IP_PROTOCOL_UUID = '{00000009-0000-1000-8000-00805F9B34FB}'; FTP_PROTOCOL_UUID = '{0000000A-0000-1000-8000-00805F9B34FB}'; HTTP_PROTOCOL_UUID = '{0000000C-0000-1000-8000-00805F9B34FB}'; WSP_PROTOCOL_UUID = '{0000000E-0000-1000-8000-00805F9B34FB}'; BNEP_PROTOCOL_UUID = '{0000000F-0000-1000-8000-00805F9B34FB}'; UPNP_PROTOCOL_UUID = '{00000010-0000-1000-8000-00805F9B34FB}'; HID_PROTOCOL_UUID = '{00000011-0000-1000-8000-00805F9B34FB}'; HCCC_PROTOCOL_UUID = '{00000012-0000-1000-8000-00805F9B34FB}'; HCDC_PROTOCOL_UUID = '{00000014-0000-1000-8000-00805F9B34FB}'; HN_PROTOCOL_UUID = '{00000016-0000-1000-8000-00805F9B34FB}'; AVCTP_PROTOCOL_UUID = '{00000017-0000-1000-8000-00805F9B34FB}'; AVDTP_PROTOCOL_UUID = '{00000019-0000-1000-8000-00805F9B34FB}'; CMPT_PROTOCOL_UUID = '{0000001B-0000-1000-8000-00805F9B34FB}'; UDI_C_PLANE_PROTOCOL_UUID = '{0000001D-0000-1000-8000-00805F9B34FB}'; L2CAP_PROTOCOL_UUID = '{00000100-0000-1000-8000-00805F9B34FB}'; </pre> <a name='services'></a> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ServiceDiscoveryServerServiceClassID_UUID = '{00001000-0000-1000-8000-00805F9B34FB}'; BrowseGroupDescriptorServiceClassID_UUID = '{00001001-0000-1000-8000-00805F9B34FB}'; PublicBrowseGroupServiceClass_UUID = '{00001002-0000-1000-8000-00805F9B34FB}'; SerialPortServiceClass_UUID = '{00001101-0000-1000-8000-00805F9B34FB}'; LANAccessUsingPPPServiceClass_UUID = '{00001102-0000-1000-8000-00805F9B34FB}'; DialupNetworkingServiceClass_UUID = '{00001103-0000-1000-8000-00805F9B34FB}'; IrMCSyncServiceClass_UUID = '{00001104-0000-1000-8000-00805F9B34FB}'; OBEXObjectPushServiceClass_UUID = '{00001105-0000-1000-8000-00805F9B34FB}'; OBEXFileTransferServiceClass_UUID = '{00001106-0000-1000-8000-00805F9B34FB}'; IrMCSyncCommandServiceClass_UUID = '{00001107-0000-1000-8000-00805F9B34FB}'; HeadsetServiceClass_UUID = '{00001108-0000-1000-8000-00805F9B34FB}'; CordlessTelephonyServiceClass_UUID = '{00001109-0000-1000-8000-00805F9B34FB}'; AudioSourceServiceClass_UUID = '{0000110A-0000-1000-8000-00805F9B34FB}'; AudioSinkServiceClass_UUID = '{0000110B-0000-1000-8000-00805F9B34FB}'; AVRemoteControlTargetServiceClass_UUID = '{0000110C-0000-1000-8000-00805F9B34FB}'; AdvancedAudioDistributionServiceClass_UUID = '{0000110D-0000-1000-8000-00805F9B34FB}'; AVRemoteControlServiceClass_UUID = '{0000110E-0000-1000-8000-00805F9B34FB}'; VideoConferencingServiceClass_UUID = '{0000110F-0000-1000-8000-00805F9B34FB}'; IntercomServiceClass_UUID = '{00001110-0000-1000-8000-00805F9B34FB}'; FaxServiceClass_UUID = '{00001111-0000-1000-8000-00805F9B34FB}'; HeadsetAudioGatewayServiceClass_UUID = '{00001112-0000-1000-8000-00805F9B34FB}'; WAPServiceClass_UUID = '{00001113-0000-1000-8000-00805F9B34FB}'; WAPClientServiceClass_UUID = '{00001114-0000-1000-8000-00805F9B34FB}'; PANUServiceClass_UUID = '{00001115-0000-1000-8000-00805F9B34FB}'; NAPServiceClass_UUID = '{00001116-0000-1000-8000-00805F9B34FB}'; GNServiceClass_UUID = '{00001117-0000-1000-8000-00805F9B34FB}'; DirectPrintingServiceClass_UUID = '{00001118-0000-1000-8000-00805F9B34FB}'; ReferencePrintingServiceClass_UUID = '{00001119-0000-1000-8000-00805F9B34FB}'; ImagingServiceClass_UUID = '{0000111A-0000-1000-8000-00805F9B34FB}'; ImagingResponderServiceClass_UUID = '{0000111B-0000-1000-8000-00805F9B34FB}'; ImagingAutomaticArchiveServiceClass_UUID = '{0000111C-0000-1000-8000-00805F9B34FB}'; ImagingReferenceObjectsServiceClass_UUID = '{0000111D-0000-1000-8000-00805F9B34FB}'; HandsfreeServiceClass_UUID = '{0000111E-0000-1000-8000-00805F9B34FB}'; HandsfreeAudioGatewayServiceClass_UUID = '{0000111F-0000-1000-8000-00805F9B34FB}'; </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DirectPrintingReferenceObjectsServiceClass_UUID = '{00001120-0000-1000-8000-00805F9B34FB}'; ReflectedUIServiceClass_UUID = '{00001121-0000-1000-8000-00805F9B34FB}'; BasicPringingServiceClass_UUID = '{00001122-0000-1000-8000-00805F9B34FB}'; PrintingStatusServiceClass_UUID = '{00001123-0000-1000-8000-00805F9B34FB}'; HumanInterfaceDeviceServiceClass_UUID = '{00001124-0000-1000-8000-00805F9B34FB}'; HardcopyCableReplacementServiceClass_UUID = '{00001125-0000-1000-8000-00805F9B34FB}'; HCRPrintServiceClass_UUID = '{00001126-0000-1000-8000-00805F9B34FB}'; HCRScanServiceClass_UUID = '{00001127-0000-1000-8000-00805F9B34FB}'; CommonISDNAccessServiceClass_UUID = '{00001128-0000-1000-8000-00805F9B34FB}'; VideoConferencingGWServiceClass_UUID = '{00001129-0000-1000-8000-00805F9B34FB}'; UDIMTServiceClass_UUID = '{0000112A-0000-1000-8000-00805F9B34FB}'; UDITAServiceClass_UUID = '{0000112B-0000-1000-8000-00805F9B34FB}'; AudioVideoServiceClass_UUID = '{0000112C-0000-1000-8000-00805F9B34FB}'; PnPInformationServiceClass_UUID = '{00001200-0000-1000-8000-00805F9B34FB}'; GenericNetworkingServiceClass_UUID = '{00001201-0000-1000-8000-00805F9B34FB}'; GenericFileTransferServiceClass_UUID = '{00001202-0000-1000-8000-00805F9B34FB}'; GenericAudioServiceClass_UUID = '{00001203-0000-1000-8000-00805F9B34FB}'; GenericAudioServiceClass_UUID = '{00001203-0000-1000-8000-00805F9B34FB}'; GenericTelephonyServiceClass_UUID = '{00001204-0000-1000-8000-00805F9B34FB}'; UPnPServiceClass_UUID = '{00001205-0000-1000-8000-00805F9B34FB}'; UPnPIpServiceClass_UUID = '{00001206-0000-1000-8000-00805F9B34FB}'; ESdpUPnPIpPanServiceClass_UUID = '{00001300-0000-1000-8000-00805F9B34FB}'; ESdpUPnPIpLapServiceClass_UUID = '{00001301-0000-1000-8000-00805F9B34FB}'; EdpUPnpIpL2CAPServiceClass_UUID = '{00001302-0000-1000-8000-00805F9B34FB}'; </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
7099
(왼쪽의 숫자를 입력해야 합니다.)