├── .gitignore ├── History.txt ├── Manifest.txt ├── README.rdoc ├── Rakefile ├── ext └── bluetooth │ ├── extconf.rb │ ├── linux │ ├── ruby_bluetooth.c │ └── ruby_bluetooth.h │ ├── macosx │ ├── device.m │ ├── error.m │ ├── host_controller.m │ ├── ruby_bluetooth.h │ ├── ruby_bluetooth.m │ └── scan.m │ └── win32 │ ├── ruby_bluetooth.cpp │ └── ruby_bluetooth.h ├── lib ├── bluetooth.rb └── bluetooth │ └── device.rb └── sample ├── name.rb ├── pair.rb ├── quality.rb └── scan.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | /lib/bluetooth/bluetooth.bundle 3 | /lib/bluetooth/bluetooth.dll 4 | /lib/bluetooth/bluetooth.so 5 | /pkg 6 | /tmp 7 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | === 1.1 2 | 3 | Minor enhancements: 4 | 5 | * Bluetooth::scan now releases the GVL on OS X. 6 | * Device#rssi, Device#link_quality now work for OS X 10.7 and newer. 7 | * Removed use of deprecated methods from the OS X extension. 8 | 9 | Bug fixes: 10 | 11 | * Fixed encoding of device names. Pull request #1 by Aaron Patterson. 12 | 13 | === 1.0 14 | 15 | * Major Enhancements 16 | * Birthday 17 | -------------------------------------------------------------------------------- /Manifest.txt: -------------------------------------------------------------------------------- 1 | History.txt 2 | Manifest.txt 3 | README.rdoc 4 | Rakefile 5 | ext/bluetooth/extconf.rb 6 | ext/bluetooth/linux/ruby_bluetooth.c 7 | ext/bluetooth/linux/ruby_bluetooth.h 8 | ext/bluetooth/macosx/device.m 9 | ext/bluetooth/macosx/error.m 10 | ext/bluetooth/macosx/host_controller.m 11 | ext/bluetooth/macosx/ruby_bluetooth.h 12 | ext/bluetooth/macosx/ruby_bluetooth.m 13 | ext/bluetooth/macosx/scan.m 14 | ext/bluetooth/win32/ruby_bluetooth.cpp 15 | ext/bluetooth/win32/ruby_bluetooth.h 16 | lib/bluetooth.rb 17 | lib/bluetooth/device.rb 18 | sample/name.rb 19 | sample/pair.rb 20 | sample/quality.rb 21 | sample/scan.rb 22 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = ruby-bluetooth 2 | 3 | home :: http://github.com/drbrain/ruby-bluetooth 4 | bugs :: http://github.com/drbrain/ruby-bluetooth/issues 5 | 6 | == DESCRIPTION: 7 | 8 | A bluetooth library for ruby 9 | 10 | == FEATURES/PROBLEMS: 11 | 12 | * Only known to work on OS X 13 | 14 | == INSTALL: 15 | 16 | gem install bluetooth 17 | 18 | == LICENSE: 19 | 20 | Unknown 21 | 22 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'hoe' 3 | 4 | begin 5 | require 'rake/extensiontask' 6 | rescue LoadError => e 7 | warn "\nmissing #{e.path} (for rake-compiler)" if e.respond_to? :path 8 | warn "run: rake newb\n\n" 9 | end 10 | 11 | HOE = Hoe.spec 'bluetooth' do 12 | developer 'Eric Hodel', 'drbrain@segment7.net' 13 | developer 'Jeremie Castagna', '' 14 | developer 'Esteve Fernandez', '' 15 | 16 | self.readme_file = 'README.rdoc' 17 | 18 | dependency 'rake-compiler', '~> 0.9', :development 19 | 20 | self.spec_extras[:extensions] = 'ext/bluetooth/extconf.rb' 21 | end 22 | 23 | if Rake.const_defined? :ExtensionTask then 24 | HOE.spec.files.delete_if { |file| file == '.gemtest' } 25 | 26 | Rake::ExtensionTask.new 'bluetooth', HOE.spec do |ext| 27 | ext.lib_dir = 'lib/bluetooth' 28 | ext.source_pattern = '**/*.{c,m,h,cpp}' 29 | end 30 | 31 | task test: :compile 32 | end 33 | 34 | -------------------------------------------------------------------------------- /ext/bluetooth/extconf.rb: -------------------------------------------------------------------------------- 1 | require 'mkmf' 2 | 3 | have_header 'ruby/thread.h' 4 | 5 | dir_config 'bluetooth' 6 | 7 | name = case RUBY_PLATFORM 8 | when /linux/ then 9 | abort 'could not find bluetooth library' unless 10 | have_library 'bluetooth' 11 | 12 | 'linux' 13 | when /(win32|mingw32)/ 14 | abort 'could not find Ws2bth.h' unless 15 | find_header('Ws2bth.h', 'c:\archiv~1\micros~2\include') 16 | 17 | 'win32' 18 | when /darwin/ then 19 | $LDFLAGS << ' -framework IOBluetooth' 20 | 'macosx' 21 | else 22 | abort "unknown platform #{RUBY_PLATFORM}" 23 | end 24 | 25 | require 'pathname' 26 | 27 | local_dir = Pathname(__FILE__).expand_path.dirname 28 | current_dir = Pathname Dir.pwd 29 | 30 | relative = local_dir.relative_path_from current_dir 31 | 32 | create_makefile 'bluetooth', File.join(relative, name) 33 | 34 | if RUBY_PLATFORM =~ /darwin/ then 35 | open 'Makefile', 'a' do |io| 36 | io.write "\n.m.o:\n\t#{COMPILE_C}\n\n" 37 | end 38 | end 39 | 40 | -------------------------------------------------------------------------------- /ext/bluetooth/linux/ruby_bluetooth.c: -------------------------------------------------------------------------------- 1 | // Include the Ruby headers and goodies 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "ruby_bluetooth.h" 7 | #include 8 | 9 | VALUE bt_module; 10 | VALUE bt_devices_class; 11 | VALUE bt_socket_class; 12 | VALUE bt_rfcomm_socket_class; 13 | VALUE bt_l2cap_socket_class; 14 | VALUE bt_service_class; 15 | VALUE bt_services_class; 16 | VALUE bt_cBluetoothDevice; 17 | 18 | // The initialization method for this module 19 | void Init_bluetooth() 20 | { 21 | bt_module = rb_define_module("Bluetooth"); 22 | 23 | rb_define_singleton_method(bt_devices_class, "scan", bt_devices_scan, 0); 24 | rb_undef_method(bt_devices_class, "initialize"); 25 | 26 | bt_socket_class = rb_define_class_under(bt_module, "BluetoothSocket", rb_cIO); 27 | rb_define_method(bt_socket_class, "inspect", bt_socket_inspect, 0); 28 | rb_define_method(bt_socket_class, "for_fd", bt_socket_s_for_fd, 1); 29 | rb_define_method(bt_socket_class, "listen", bt_socket_listen, 1); 30 | rb_define_method(bt_socket_class, "accept", bt_socket_accept, 0); 31 | rb_undef_method(bt_socket_class, "initialize"); 32 | 33 | bt_rfcomm_socket_class = rb_define_class_under(bt_module, "RFCOMMSocket", bt_socket_class); 34 | rb_define_method(bt_rfcomm_socket_class, "initialize", bt_rfcomm_socket_init, -1); 35 | rb_define_method(bt_rfcomm_socket_class, "connect", bt_rfcomm_socket_connect, 2); 36 | rb_define_method(bt_rfcomm_socket_class, "bind", bt_rfcomm_socket_bind, 1); 37 | 38 | bt_l2cap_socket_class = rb_define_class_under(bt_module, "L2CAPSocket", bt_socket_class); 39 | rb_define_method(bt_l2cap_socket_class, "initialize", bt_l2cap_socket_init, -1); 40 | rb_define_method(bt_l2cap_socket_class, "connect", bt_l2cap_socket_connect, 2); 41 | rb_define_method(bt_l2cap_socket_class, "bind", bt_l2cap_socket_bind, 1); 42 | 43 | bt_services_class = rb_define_class_under(bt_module, "Services", rb_cObject); 44 | //rb_define_singleton_method(bt_services_class, "scan", bt_services_scan, 3); 45 | rb_undef_method(bt_services_class, "initialize"); 46 | 47 | bt_service_class = rb_define_class_under(bt_module, "Service", rb_cObject); 48 | rb_define_singleton_method(bt_service_class, "new", bt_service_new, 4); 49 | rb_define_method(bt_service_class, "register", bt_service_register, 1); 50 | rb_define_method(bt_service_class, "unregister", bt_service_unregister, 0); 51 | rb_define_attr(bt_service_class, "uuid", Qtrue, Qfalse); 52 | rb_define_attr(bt_service_class, "name", Qtrue, Qfalse); 53 | rb_define_attr(bt_service_class, "description", Qtrue, Qfalse); 54 | rb_define_attr(bt_service_class, "provider", Qtrue, Qfalse); 55 | 56 | rb_define_method(bt_service_class, "registered?", bt_service_registered, 0); 57 | 58 | bt_cBluetoothDevice = rb_const_get(mBluetooth, rb_intern("Device")); 59 | } 60 | 61 | static VALUE bt_socket_accept(VALUE self) { 62 | OpenFile *fptr; 63 | VALUE sock2; 64 | char buf[1024]; 65 | socklen_t len = sizeof(buf); 66 | 67 | // struct sockaddr_rc rcaddr; 68 | // addr_len = sizeof(rcaddr); 69 | 70 | GetOpenFile(self, fptr); 71 | //sock2 = s_accept(bt_socket_class, fileno(fptr->f), (struct sockaddr *)&rcaddr, &addr_len); 72 | sock2 = s_accept(bt_socket_class, fileno(fptr->f), (struct sockaddr *)buf, &len); 73 | return rb_assoc_new(sock2, rb_str_new(buf, len)); 74 | } 75 | 76 | 77 | static VALUE 78 | bt_socket_listen(sock, log) 79 | VALUE sock, log; 80 | { 81 | OpenFile *fptr; 82 | int backlog; 83 | 84 | rb_secure(4); 85 | backlog = NUM2INT(log); 86 | GetOpenFile(sock, fptr); 87 | if (listen(fileno(fptr->f), backlog) < 0) 88 | rb_sys_fail("listen(2)"); 89 | 90 | return INT2FIX(0); 91 | } 92 | 93 | 94 | static VALUE bt_service_register(VALUE self, VALUE socket) { 95 | VALUE registered = rb_iv_get(self, "@registered"); 96 | if (registered == Qfalse) { 97 | VALUE port_v = rb_iv_get(socket, "@port"); 98 | if(Qnil == port_v) { 99 | rb_raise (rb_eIOError, "a bound socket must be passed"); 100 | } 101 | 102 | // uint32_t service_uuid_int[] = { 0, 0, 0, 0xABCD }; 103 | const char *service_name = STR2CSTR(rb_iv_get(self, "@name")); 104 | const char *service_dsc = STR2CSTR(rb_iv_get(self, "@description")); 105 | const char *service_prov = STR2CSTR(rb_iv_get(self, "@provider")); 106 | 107 | uuid_t root_uuid, l2cap_uuid, rfcomm_uuid, svc_uuid; 108 | sdp_list_t *l2cap_list = 0, 109 | *rfcomm_list = 0, 110 | *root_list = 0, 111 | *proto_list = 0, 112 | *access_proto_list = 0; 113 | sdp_data_t *channel = 0, *psm = 0; 114 | 115 | sdp_record_t *record = sdp_record_alloc(); 116 | 117 | // set the general service ID 118 | // sdp_uuid128_create( &svc_uuid, &service_uuid_int ); 119 | char *service_id = STR2CSTR(rb_iv_get(self, "@uuid")); 120 | if(str2uuid(service_id, &svc_uuid) != 0) { 121 | rb_raise (rb_eIOError, "a valid uuid must be passed"); 122 | } 123 | sdp_set_service_id( record, svc_uuid ); 124 | 125 | // make the service record publicly browsable 126 | sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); 127 | root_list = sdp_list_append(0, &root_uuid); 128 | sdp_set_browse_groups( record, root_list ); 129 | 130 | // set l2cap information 131 | sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); 132 | l2cap_list = sdp_list_append( 0, &l2cap_uuid ); 133 | if (bt_l2cap_socket_class == CLASS_OF(socket)) { 134 | uint16_t l2cap_port = FIX2UINT(port_v); 135 | psm = sdp_data_alloc(SDP_UINT16, &l2cap_port); 136 | sdp_list_append(l2cap_list, psm); 137 | } 138 | proto_list = sdp_list_append( 0, l2cap_list ); 139 | 140 | // set rfcomm information 141 | sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); 142 | rfcomm_list = sdp_list_append( 0, &rfcomm_uuid ); 143 | if (bt_rfcomm_socket_class == CLASS_OF(socket)) { 144 | uint16_t rfcomm_channel = FIX2UINT(port_v); 145 | channel = sdp_data_alloc(SDP_UINT8, &rfcomm_channel); 146 | sdp_list_append(rfcomm_list, channel); 147 | } 148 | sdp_list_append( proto_list, rfcomm_list ); 149 | 150 | // attach protocol information to service record 151 | access_proto_list = sdp_list_append( 0, proto_list ); 152 | sdp_set_access_protos( record, access_proto_list ); 153 | 154 | // set the name, provider, and description 155 | sdp_set_info_attr(record, service_name, service_prov, service_dsc); 156 | int err = 0; 157 | sdp_session_t *session = 0; 158 | 159 | // connect to the local SDP server, register the service record, and 160 | // disconnect 161 | session = sdp_connect( BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY ); 162 | err = sdp_record_register(session, record, 0); 163 | 164 | // cleanup 165 | if (channel != 0) { 166 | sdp_data_free( channel ); 167 | } 168 | sdp_list_free( l2cap_list, 0 ); 169 | sdp_list_free( rfcomm_list, 0 ); 170 | sdp_list_free( root_list, 0 ); 171 | sdp_list_free( access_proto_list, 0 ); 172 | 173 | struct bluetooth_service_struct *bss; 174 | Data_Get_Struct(self, struct bluetooth_service_struct, bss); 175 | bss->session = session; 176 | // Do something 177 | rb_iv_set(self, "@registered", Qtrue); 178 | } 179 | return Qnil; 180 | } 181 | 182 | static VALUE bt_service_unregister(VALUE self) { 183 | VALUE registered = rb_iv_get(self, "@registered"); 184 | if (registered == Qtrue) { 185 | struct bluetooth_service_struct *bss; 186 | Data_Get_Struct(self, struct bluetooth_service_struct, bss); 187 | sdp_close(bss->session); 188 | bss->session = NULL; 189 | // Do something 190 | rb_iv_set(self, "@registered", Qfalse); 191 | } 192 | return registered; 193 | } 194 | 195 | static VALUE bt_service_registered(VALUE self) { 196 | VALUE registered = rb_iv_get(self, "@registered"); 197 | if (registered == Qtrue) { 198 | // Do something 199 | } 200 | return registered; 201 | } 202 | 203 | static VALUE bt_service_new(VALUE self, VALUE uuid, VALUE name, VALUE description, VALUE provider) { 204 | struct bluetooth_service_struct *bss; 205 | 206 | VALUE obj = Data_Make_Struct(self, 207 | struct bluetooth_service_struct, NULL, 208 | free, bss); 209 | 210 | rb_iv_set(obj, "@uuid", uuid); 211 | rb_iv_set(obj, "@name", name); 212 | rb_iv_set(obj, "@description", description); 213 | rb_iv_set(obj, "@provider", provider); 214 | rb_iv_set(obj, "@registered", Qfalse); 215 | 216 | return obj; 217 | } 218 | 219 | static VALUE 220 | bt_l2cap_socket_connect(VALUE self, VALUE host, VALUE port) 221 | { 222 | OpenFile *fptr; 223 | int fd; 224 | 225 | GetOpenFile(self, fptr); 226 | fd = fileno(fptr->f); 227 | 228 | struct sockaddr_l2 addr = { 0 }; 229 | char *dest = STR2CSTR(host); 230 | 231 | // set the connection parameters (who to connect to) 232 | addr.l2_family = AF_BLUETOOTH; 233 | addr.l2_psm = (uint8_t) FIX2UINT(port); 234 | str2ba( dest, &addr.l2_bdaddr ); 235 | 236 | // connect to server 237 | if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { 238 | rb_sys_fail("connect(2)"); 239 | } 240 | 241 | return INT2FIX(0); 242 | } 243 | 244 | static VALUE 245 | bt_rfcomm_socket_connect(VALUE self, VALUE host, VALUE port) 246 | { 247 | OpenFile *fptr; 248 | int fd; 249 | 250 | GetOpenFile(self, fptr); 251 | fd = fileno(fptr->f); 252 | 253 | struct sockaddr_rc addr = { 0 }; 254 | char *dest = STR2CSTR(host); 255 | 256 | // set the connection parameters (who to connect to) 257 | addr.rc_family = AF_BLUETOOTH; 258 | addr.rc_channel = (uint8_t) FIX2UINT(port); 259 | str2ba( dest, &addr.rc_bdaddr ); 260 | 261 | // connect to server 262 | if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { 263 | rb_sys_fail("connect(2)"); 264 | } 265 | 266 | return INT2FIX(0); 267 | } 268 | 269 | static VALUE 270 | bt_socket_s_for_fd(VALUE klass, VALUE fd) 271 | { 272 | OpenFile *fptr; 273 | VALUE sock = bt_init_sock(rb_obj_alloc(klass), NUM2INT(fd)); 274 | 275 | GetOpenFile(sock, fptr); 276 | return sock; 277 | } 278 | 279 | static VALUE 280 | bt_rfcomm_socket_bind(VALUE self, VALUE port) 281 | { 282 | OpenFile *fptr; 283 | int fd; 284 | 285 | GetOpenFile(self, fptr); 286 | fd = fileno(fptr->f); 287 | 288 | struct sockaddr_rc loc_addr = { 0 }; 289 | loc_addr.rc_family = AF_BLUETOOTH; 290 | loc_addr.rc_bdaddr = *BDADDR_ANY; 291 | loc_addr.rc_channel = (uint8_t) FIX2UINT(port); 292 | 293 | if (bind(fd, (struct sockaddr *)&loc_addr, sizeof(loc_addr)) >= 0) 294 | rb_iv_set(self, "@port", port); 295 | return INT2FIX(0); 296 | } 297 | 298 | static VALUE 299 | bt_l2cap_socket_bind(VALUE self, VALUE port) 300 | { 301 | OpenFile *fptr; 302 | int fd; 303 | 304 | GetOpenFile(self, fptr); 305 | fd = fileno(fptr->f); 306 | 307 | struct sockaddr_l2 loc_addr = { 0 }; 308 | loc_addr.l2_family = AF_BLUETOOTH; 309 | loc_addr.l2_bdaddr = *BDADDR_ANY; 310 | loc_addr.l2_psm = (uint8_t) FIX2UINT(port); 311 | 312 | if (bind(fd, (struct sockaddr *)&loc_addr, sizeof(loc_addr)) >= 0) 313 | rb_iv_set(self, "@port", port); 314 | return INT2FIX(0); 315 | } 316 | 317 | static VALUE bt_socket_inspect(VALUE self) 318 | { 319 | return self; 320 | } 321 | 322 | static int 323 | bt_ruby_socket(int domain, int type, int proto) 324 | { 325 | int fd; 326 | 327 | fd = socket(domain, type, proto); 328 | if (fd < 0) { 329 | if (errno == EMFILE || errno == ENFILE) { 330 | rb_gc(); 331 | fd = socket(domain, type, proto); 332 | } 333 | } 334 | return fd; 335 | } 336 | 337 | static VALUE 338 | bt_init_sock(VALUE sock, int fd) 339 | { 340 | OpenFile *fp = NULL; 341 | 342 | MakeOpenFile(sock, fp); 343 | 344 | fp->f = rb_fdopen(fd, "r"); 345 | fp->f2 = rb_fdopen(fd, "w"); 346 | fp->mode = FMODE_READWRITE; 347 | 348 | rb_io_synchronized(fp); 349 | 350 | return sock; 351 | } 352 | 353 | // Initialization of a RFCOMM socket 354 | static VALUE bt_rfcomm_socket_init(int argc, VALUE *argv, VALUE sock) 355 | { 356 | int fd = bt_ruby_socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); 357 | if (fd < 0) { 358 | rb_sys_fail("socket(2) - bt"); 359 | } 360 | VALUE ret = bt_init_sock(sock, fd); 361 | return ret; 362 | } 363 | 364 | // Initialization of a L2CAP socket 365 | static VALUE bt_l2cap_socket_init(int argc, VALUE *argv, VALUE sock) 366 | { 367 | int fd = bt_ruby_socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); 368 | if (fd < 0) { 369 | rb_sys_fail("socket(2) - bt"); 370 | } 371 | VALUE ret = bt_init_sock(sock, fd); 372 | return ret; 373 | } 374 | 375 | // Scan local network for visible remote devices 376 | static VALUE bt_devices_scan(VALUE self) 377 | { 378 | inquiry_info *ii = NULL; 379 | int max_rsp, num_rsp; 380 | int dev_id, sock, len, flags; 381 | int i; 382 | 383 | dev_id = hci_get_route(NULL); 384 | sock = hci_open_dev( dev_id ); 385 | if (dev_id < 0 || sock < 0) 386 | { 387 | rb_raise (rb_eIOError, "error opening socket"); 388 | } 389 | 390 | len = 8; 391 | max_rsp = 255; 392 | flags = IREQ_CACHE_FLUSH; 393 | ii = (inquiry_info*)malloc(max_rsp * sizeof(inquiry_info)); 394 | 395 | num_rsp = hci_inquiry(dev_id, len, max_rsp, NULL, &ii, flags); 396 | if( num_rsp < 0 ) 397 | rb_raise(rb_eIOError, "hci_inquiry"); 398 | 399 | VALUE devices_array = rb_ary_new(); 400 | 401 | // Iterate over every device found and add it to result array 402 | for (i = 0; i < num_rsp; i++) 403 | { 404 | char addr[19] = { 0 }; 405 | char name[248] = { 0 }; 406 | 407 | ba2str(&(ii+i)->bdaddr, addr); 408 | memset(name, 0, sizeof(name)); 409 | if (hci_read_remote_name(sock, &(ii+i)->bdaddr, sizeof(name), 410 | name, 0) < 0) 411 | strcpy(name, "(unknown)"); 412 | 413 | VALUE bt_dev = rb_funcall(bt_cBluetoothDevice, rb_intern("new"), 2, 414 | rb_str_new(name), rb_str_new2(addr)); 415 | 416 | rb_ary_push(devices_array, bt_dev); 417 | } 418 | 419 | free( ii ); 420 | close( sock ); 421 | return devices_array; 422 | } 423 | 424 | static VALUE 425 | s_accept(VALUE klass, int fd, struct sockaddr *sockaddr, socklen_t *len) { 426 | int fd2; 427 | int retry = 0; 428 | 429 | rb_secure(3); 430 | retry: 431 | rb_thread_wait_fd(fd); 432 | #if defined(_nec_ews) 433 | fd2 = accept(fd, sockaddr, len); 434 | #else 435 | TRAP_BEG; 436 | fd2 = accept(fd, sockaddr, len); 437 | TRAP_END; 438 | #endif 439 | if (fd2 < 0) { 440 | switch (errno) { 441 | case EMFILE: 442 | case ENFILE: 443 | if (retry) break; 444 | rb_gc(); 445 | retry = 1; 446 | goto retry; 447 | case EWOULDBLOCK: 448 | break; 449 | default: 450 | if (!rb_io_wait_readable(fd)) break; 451 | retry = 0; 452 | goto retry; 453 | } 454 | rb_sys_fail(0); 455 | } 456 | if (!klass) return INT2NUM(fd2); 457 | return bt_init_sock(rb_obj_alloc(klass), fd2); 458 | } 459 | // Code from PyBlueZ 460 | int 461 | str2uuid(char *uuid_str, uuid_t *uuid) 462 | { 463 | uint32_t uuid_int[4]; 464 | char *endptr; 465 | 466 | if(strlen(uuid_str) == 36) { 467 | // Parse uuid128 standard format: 12345678-9012-3456-7890-123456789012 468 | char buf[9] = { 0 }; 469 | 470 | if(uuid_str[8] != '-' && uuid_str[13] != '-' && 471 | uuid_str[18] != '-' && uuid_str[23] != '-') { 472 | return -1; 473 | } 474 | // first 8-bytes 475 | strncpy(buf, uuid_str, 8); 476 | uuid_int[0] = htonl(strtoul(buf, &endptr, 16)); 477 | if(endptr != buf + 8) return -1; 478 | 479 | // second 8-bytes 480 | strncpy(buf, uuid_str+9, 4); 481 | strncpy(buf+4, uuid_str+14, 4); 482 | uuid_int[1] = htonl(strtoul( buf, &endptr, 16)); 483 | if(endptr != buf + 8) return -1; 484 | 485 | // third 8-bytes 486 | strncpy(buf, uuid_str+19, 4); 487 | strncpy(buf+4, uuid_str+24, 4); 488 | uuid_int[2] = htonl(strtoul(buf, &endptr, 16)); 489 | if(endptr != buf + 8) return -1; 490 | 491 | // fourth 8-bytes 492 | strncpy(buf, uuid_str+28, 8); 493 | uuid_int[3] = htonl(strtoul(buf, &endptr, 16)); 494 | if(endptr != buf + 8) return -1; 495 | 496 | if(uuid != NULL) sdp_uuid128_create(uuid, uuid_int); 497 | } 498 | 499 | else if(strlen(uuid_str) == 8) { 500 | // 32-bit reserved UUID 501 | uint32_t i = strtoul(uuid_str, &endptr, 16); 502 | if(endptr != uuid_str + 8) return -1; 503 | if(uuid != NULL) sdp_uuid32_create(uuid, i); 504 | } 505 | 506 | else if(strlen(uuid_str) == 6) { 507 | // 16-bit reserved UUID with 0x on front 508 | if(uuid_str[0] == '0' && (uuid_str[1] == 'x' || uuid_str[1] == 'X')) { 509 | // move chars up 510 | uuid_str[0] = uuid_str[2]; 511 | uuid_str[1] = uuid_str[3]; 512 | uuid_str[2] = uuid_str[4]; 513 | uuid_str[3] = uuid_str[5]; 514 | uuid_str[4] = '\0'; 515 | int i = strtol(uuid_str, &endptr, 16); 516 | if(endptr != uuid_str + 4) return -1; 517 | if(uuid != NULL) sdp_uuid16_create(uuid, i); 518 | } 519 | 520 | else return(-1); 521 | } 522 | 523 | else if(strlen(uuid_str) == 4) { 524 | // 16-bit reserved UUID 525 | int i = strtol(uuid_str, &endptr, 16); 526 | if(endptr != uuid_str + 4) return -1; 527 | if(uuid != NULL) sdp_uuid16_create(uuid, i); 528 | } 529 | 530 | else { 531 | return -1; 532 | } 533 | 534 | return 0; 535 | } 536 | 537 | 538 | 539 | 540 | -------------------------------------------------------------------------------- /ext/bluetooth/linux/ruby_bluetooth.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // Prototype for the initialization method - Ruby calls this, not you 12 | void Init_ruby_bluetooth(); 13 | 14 | struct bluetooth_device_struct 15 | { 16 | VALUE addr; 17 | VALUE name; 18 | }; 19 | 20 | struct bluetooth_service_struct 21 | { 22 | VALUE uuid; 23 | VALUE name; 24 | VALUE description; 25 | VALUE provider; 26 | VALUE registered; 27 | sdp_session_t *session; 28 | }; 29 | 30 | static VALUE bt_device_new(VALUE self, VALUE name, VALUE addr); 31 | 32 | static VALUE bt_devices_scan(VALUE self); 33 | 34 | static int bt_ruby_socket(int domain, int type, int proto); 35 | 36 | static VALUE bt_init_sock(VALUE sock, int fd); 37 | 38 | static VALUE bt_socket_inspect(VALUE self); 39 | 40 | static VALUE bt_socket_s_for_fd(VALUE klass, VALUE fd); 41 | 42 | static VALUE bt_socket_listen(VALUE klass, VALUE backlog); 43 | 44 | static VALUE bt_socket_accept(VALUE sock); 45 | 46 | static VALUE bt_rfcomm_socket_init(int argc, VALUE *argv, VALUE sock); 47 | 48 | static VALUE bt_rfcomm_socket_connect(VALUE sock, VALUE host, VALUE port); 49 | 50 | static VALUE bt_rfcomm_socket_bind(VALUE sock, VALUE port); 51 | 52 | static VALUE bt_l2cap_socket_init(int argc, VALUE *argv, VALUE sock); 53 | 54 | static VALUE bt_l2cap_socket_connect(VALUE sock, VALUE host, VALUE port); 55 | 56 | static VALUE bt_l2cap_socket_bind(VALUE sock, VALUE port); 57 | 58 | static VALUE bt_service_new(VALUE self, VALUE uuid, VALUE name, VALUE description, VALUE provider); 59 | 60 | static VALUE bt_service_register(VALUE self, VALUE sock); 61 | 62 | static VALUE bt_service_unregister(VALUE self); 63 | 64 | static VALUE bt_service_registered(VALUE self); 65 | 66 | int str2uuid(char *uuid_str, uuid_t *uuid); 67 | 68 | static VALUE s_accept(VALUE klass, int fd, struct sockaddr *sockaddr, socklen_t *len); 69 | -------------------------------------------------------------------------------- /ext/bluetooth/macosx/device.m: -------------------------------------------------------------------------------- 1 | #import "ruby_bluetooth.h" 2 | 3 | #import 4 | 5 | static IOBluetoothDevice *rbt_device_get(VALUE self) { 6 | BluetoothDeviceAddress address; 7 | IOBluetoothDevice *device; 8 | VALUE address_bytes; 9 | char * tmp = NULL; 10 | 11 | address_bytes = rb_funcall(self, rb_intern("address_bytes"), 0); 12 | 13 | if (RSTRING_LEN(address_bytes) != 6) { 14 | VALUE inspect = rb_inspect(address_bytes); 15 | rb_raise(rb_eArgError, "%s doesn't look like a bluetooth address", 16 | StringValueCStr(inspect)); 17 | } 18 | 19 | tmp = StringValuePtr(address_bytes); 20 | 21 | memcpy(address.data, tmp, 6); 22 | 23 | device = [IOBluetoothDevice deviceWithAddress: &address]; 24 | 25 | return device; 26 | } 27 | 28 | VALUE rbt_device_link_quality(VALUE self) { 29 | IOBluetoothDevice *device; 30 | BluetoothHCIRSSIValue RSSI; 31 | VALUE rssi; 32 | NSAutoreleasePool *pool; 33 | 34 | pool = [[NSAutoreleasePool alloc] init]; 35 | 36 | device = rbt_device_get(self); 37 | 38 | RSSI = [device RSSI]; 39 | 40 | [pool release]; 41 | 42 | rssi = INT2NUM(RSSI); 43 | 44 | rb_iv_set(self, "@link_quality", rssi); 45 | 46 | return rssi; 47 | } 48 | 49 | VALUE rbt_device_open_connection(VALUE self) { 50 | IOBluetoothDevice *device; 51 | IOReturn status; 52 | NSAutoreleasePool *pool; 53 | VALUE result; 54 | 55 | pool = [[NSAutoreleasePool alloc] init]; 56 | 57 | device = rbt_device_get(self); 58 | 59 | if (![device isConnected]) { 60 | status = [device openConnection]; 61 | 62 | rbt_check_status(status, pool); 63 | } 64 | 65 | result = rb_yield(Qundef); 66 | 67 | status = [device closeConnection]; 68 | 69 | [pool release]; 70 | 71 | rbt_check_status(status, nil); 72 | 73 | return result; 74 | } 75 | 76 | VALUE rbt_device_pair(VALUE self) { 77 | PairingDelegate *delegate; 78 | IOBluetoothDevice *device; 79 | IOBluetoothDevicePair *device_pair; 80 | IOReturn status; 81 | NSAutoreleasePool *pool; 82 | 83 | pool = [[NSAutoreleasePool alloc] init]; 84 | 85 | device = rbt_device_get(self); 86 | 87 | delegate = [[PairingDelegate alloc] init]; 88 | delegate.device = self; 89 | 90 | device_pair = [IOBluetoothDevicePair pairWithDevice: device]; 91 | [device_pair setDelegate: delegate]; 92 | 93 | status = [device_pair start]; 94 | 95 | rbt_check_status(status, pool); 96 | 97 | CFRunLoopRun(); 98 | 99 | [pool release]; 100 | 101 | status = (IOReturn)NUM2INT(rb_iv_get(self, "@pair_error")); 102 | 103 | rbt_check_status(status, nil); 104 | 105 | return Qtrue; 106 | } 107 | 108 | VALUE rbt_device_request_name(VALUE self) { 109 | IOBluetoothDevice *device; 110 | IOReturn status; 111 | VALUE name; 112 | NSAutoreleasePool *pool; 113 | 114 | pool = [[NSAutoreleasePool alloc] init]; 115 | 116 | device = rbt_device_get(self); 117 | 118 | status = [device remoteNameRequest: nil]; 119 | 120 | rbt_check_status(status, pool); 121 | 122 | name = rb_str_new2([[device name] UTF8String]); 123 | rb_enc_associate(name, rb_utf8_encoding()); 124 | 125 | [pool release]; 126 | 127 | return name; 128 | } 129 | 130 | VALUE rbt_device_rssi(VALUE self) { 131 | IOBluetoothDevice *device; 132 | BluetoothHCIRSSIValue rawRSSI; 133 | VALUE raw_rssi; 134 | NSAutoreleasePool *pool; 135 | 136 | pool = [[NSAutoreleasePool alloc] init]; 137 | 138 | device = rbt_device_get(self); 139 | 140 | rawRSSI = [device rawRSSI]; 141 | 142 | [pool release]; 143 | 144 | raw_rssi = INT2NUM(rawRSSI); 145 | 146 | rb_iv_set(self, "@rssi", raw_rssi); 147 | 148 | return raw_rssi; 149 | } 150 | 151 | @implementation PairingDelegate 152 | 153 | - (VALUE) device { 154 | return device; 155 | } 156 | 157 | - (void) setDevice: (VALUE)input { 158 | device = input; 159 | } 160 | 161 | - (void) devicePairingConnecting: (id)sender { 162 | } 163 | 164 | - (void) devicePairingStarted: (id)sender { 165 | } 166 | 167 | - (void) devicePairingFinished: (id)sender 168 | error: (IOReturn)error { 169 | CFRunLoopStop(CFRunLoopGetCurrent()); 170 | 171 | rb_iv_set(device, "@pair_error", INT2NUM(error)); 172 | } 173 | 174 | - (void) devicePairingPasskeyNotification: (id)sender 175 | passkey: (BluetoothPasskey)passkey { 176 | printf("passkey %ld! I don't know what to do!", (unsigned long)passkey); 177 | } 178 | 179 | - (void) devicePairingPINCodeRequest: (id)sender { 180 | puts("PIN code! I don't know what to do!"); 181 | } 182 | 183 | - (void) devicePairingUserConfirmationRequest: (id)sender 184 | numericValue: (BluetoothNumericValue)numericValue { 185 | BOOL confirm; 186 | VALUE result = Qtrue; 187 | VALUE numeric_value = ULONG2NUM((unsigned long)numericValue); 188 | VALUE callback = rb_iv_get(device, "@pair_confirmation_callback"); 189 | 190 | if (RTEST(callback)) 191 | result = rb_funcall(callback, rb_intern("call"), 1, numeric_value); 192 | 193 | if (RTEST(result)) { 194 | confirm = YES; 195 | } else { 196 | confirm = NO; 197 | } 198 | 199 | [sender replyUserConfirmation: confirm]; 200 | } 201 | 202 | @end 203 | 204 | -------------------------------------------------------------------------------- /ext/bluetooth/macosx/error.m: -------------------------------------------------------------------------------- 1 | #import "ruby_bluetooth.h" 2 | #import 3 | 4 | extern VALUE rbt_mBluetooth; 5 | extern VALUE rbt_cBluetoothError; 6 | 7 | VALUE errors; 8 | 9 | void rbt_check_status(IOReturn status, NSAutoreleasePool *pool) { 10 | if (status != kIOReturnSuccess || status != noErr) { 11 | [pool release]; 12 | 13 | rb_funcall(rbt_cBluetoothError, rb_intern("raise"), 1, INT2NUM(status)); 14 | } 15 | } 16 | 17 | void add_error(IOReturn status, const char *name, const char *message) { 18 | VALUE klass; 19 | VALUE value; 20 | 21 | klass = rb_define_class_under(rbt_mBluetooth, name, rbt_cBluetoothError); 22 | value = rb_ary_new3(2, klass, rb_str_new2(message)); 23 | rb_hash_aset(errors, INT2NUM(status), value); 24 | } 25 | 26 | void init_rbt_error() { 27 | VALUE tmp; 28 | 29 | errors = rb_const_get(rbt_mBluetooth, rb_intern("ERRORS")); 30 | 31 | tmp = rb_ary_new3(2, rbt_cBluetoothError, rb_str_new2("general error")); 32 | rb_hash_aset(errors, INT2NUM(kIOReturnError), tmp); 33 | 34 | // IOKit 35 | add_error(kIOReturnNoMemory, "NoMemoryError", 36 | "can't allocate memory"); 37 | add_error(kIOReturnNoResources, "NoResourcesError", 38 | "resource shortage"); 39 | add_error(kIOReturnIPCError, "IPCError", 40 | "error during IPC"); 41 | add_error(kIOReturnNoDevice, "NoDeviceError", 42 | "no such device"); 43 | add_error(kIOReturnNotPrivileged, "NotPrivilegedError", 44 | "privilege violation"); 45 | add_error(kIOReturnBadArgument, "BadArgumentError", 46 | "invalid argument"); 47 | add_error(kIOReturnLockedRead, "LockedReadError", 48 | "device read locked"); 49 | add_error(kIOReturnLockedWrite, "LockedWriteError", 50 | "device write locked"); 51 | add_error(kIOReturnExclusiveAccess, "ExclusiveAccessError", 52 | "exclusive access and device already open"); 53 | add_error(kIOReturnBadMessageID, "BadMessageIDError", 54 | "sent/received messages had different msg_id"); 55 | add_error(kIOReturnUnsupported, "UnsupportedError", 56 | "unsupported function"); 57 | add_error(kIOReturnVMError, "VMError", 58 | "misc. VM failure"); 59 | add_error(kIOReturnInternalError, "InternalError", 60 | "internal error"); 61 | add_error(kIOReturnIOError, "IOError", 62 | "General I/O error"); 63 | add_error(kIOReturnCannotLock, "CannotLockError", 64 | "can't acquire lock"); 65 | add_error(kIOReturnNotOpen, "NotOpenError", 66 | "device not open"); 67 | add_error(kIOReturnNotReadable, "NotReadableError", 68 | "read not supported"); 69 | add_error(kIOReturnNotWritable, "NotWritableError", 70 | "write not supported"); 71 | add_error(kIOReturnNotAligned, "NotAlignedError", 72 | "alignment error"); 73 | add_error(kIOReturnBadMedia, "BadMediaError", 74 | "Media Error"); 75 | add_error(kIOReturnStillOpen, "StillOpenError", 76 | "device(s) still open"); 77 | add_error(kIOReturnRLDError, "RLDError", 78 | "rld failure"); 79 | add_error(kIOReturnDMAError, "DMAError", 80 | "DMA failure"); 81 | add_error(kIOReturnBusy, "BusyError", 82 | "Device Busy"); 83 | add_error(kIOReturnTimeout, "TimeoutError", 84 | "I/O Timeout"); 85 | add_error(kIOReturnOffline, "OfflineError", 86 | "device offline"); 87 | add_error(kIOReturnNotReady, "NotReadyError", 88 | "not ready"); 89 | add_error(kIOReturnNotAttached, "NotAttachedError", 90 | "device not attached"); 91 | add_error(kIOReturnNoChannels, "NoChannelsError", 92 | "no DMA channels left"); 93 | add_error(kIOReturnNoSpace, "NoSpaceError", 94 | "no space for data"); 95 | add_error(kIOReturnPortExists, "PortExistsError", 96 | "port already exists"); 97 | add_error(kIOReturnCannotWire, "CannotWireError", 98 | "can't wire down physical memory"); 99 | add_error(kIOReturnNoInterrupt, "NoInterruptError", 100 | "no interrupt attached"); 101 | add_error(kIOReturnNoFrames, "NoFramesError", 102 | "no DMA frames enqueued"); 103 | add_error(kIOReturnMessageTooLarge, "MessageTooLargeError", 104 | "oversized msg received on interrupt port"); 105 | add_error(kIOReturnNotPermitted, "NotPermittedError", 106 | "not permitted"); 107 | add_error(kIOReturnNoPower, "NoPowerError", 108 | "no power to device"); 109 | add_error(kIOReturnNoMedia, "NoMediaError", 110 | "media not present"); 111 | add_error(kIOReturnUnformattedMedia, "UnformattedMediaError", 112 | "media not formatted"); 113 | add_error(kIOReturnUnsupportedMode, "UnsupportedModeError", 114 | "no such mode"); 115 | add_error(kIOReturnUnderrun, "UnderrunError", 116 | "data underrun"); 117 | add_error(kIOReturnOverrun, "OverrunError", 118 | "data overrun"); 119 | add_error(kIOReturnDeviceError, "DeviceError", 120 | "the device is not working properly!"); 121 | add_error(kIOReturnNoCompletion, "NoCompletionError", 122 | "a completion routine is required"); 123 | add_error(kIOReturnAborted, "AbortedError", 124 | "operation aborted"); 125 | add_error(kIOReturnNoBandwidth, "NoBandwidthError", 126 | "bus bandwidth would be exceeded"); 127 | add_error(kIOReturnNotResponding, "NotRespondingError", 128 | "device not responding"); 129 | add_error(kIOReturnIsoTooOld, "IsoTooOldError", 130 | "isochronous I/O request for distant past!"); 131 | add_error(kIOReturnIsoTooNew, "IsoTooNewError", 132 | "isochronous I/O request for distant future"); 133 | add_error(kIOReturnNotFound, "NotFoundError", 134 | "data was not found"); 135 | add_error(kIOReturnInvalid, "InvalidError", 136 | "should never be seen"); 137 | 138 | // Bluetooth 139 | add_error(kBluetoothHCIErrorUnknownHCICommand, "UnknownHCICommandError", 140 | "unknown HCI command"); 141 | add_error(kBluetoothHCIErrorNoConnection, "NoConnectionError", 142 | "no connection"); 143 | add_error(kBluetoothHCIErrorHardwareFailure, "HardwareFailureError", 144 | "hardware failure"); 145 | add_error(kBluetoothHCIErrorPageTimeout, "PageTimeoutError", 146 | "page timeout"); 147 | add_error(kBluetoothHCIErrorAuthenticationFailure, 148 | "AuthenticationFailureError", "authentication failure"); 149 | add_error(kBluetoothHCIErrorKeyMissing, "KeyMissingError", "key missing"); 150 | add_error(kBluetoothHCIErrorMemoryFull, "MemoryFullError", "memory full"); 151 | add_error(kBluetoothHCIErrorConnectionTimeout, "ConnectionTimeoutError", 152 | "connection timeout"); 153 | add_error(kBluetoothHCIErrorMaxNumberOfConnections, 154 | "MaxNumberOfConnectionsError", "maximum number of connections"); 155 | add_error(kBluetoothHCIErrorMaxNumberOfSCOConnectionsToADevice, 156 | "MaxNumberOfSCOConnectionsToADeviceError", 157 | "maximum number of synchronous connections to a device"); 158 | add_error(kBluetoothHCIErrorACLConnectionAlreadyExists, 159 | "ACLConnectionAlreadyExistsError", 160 | "ACL connection already exists"); 161 | add_error(kBluetoothHCIErrorCommandDisallowed, "CommandDisallowedError", 162 | "command disallowed"); 163 | add_error(kBluetoothHCIErrorHostRejectedLimitedResources, 164 | "HostRejectedLimitedResourcesError", 165 | "host rejected, limited resources"); 166 | add_error(kBluetoothHCIErrorHostRejectedSecurityReasons, 167 | "HostRejectedSecurityReasonsError", 168 | "host rejected, security reasons"); 169 | add_error(kBluetoothHCIErrorHostRejectedRemoteDeviceIsPersonal, 170 | "HostRejectedRemoteDeviceIsPersonalError", 171 | "host rejected, remote device is personal"); 172 | add_error(kBluetoothHCIErrorHostTimeout, "HostTimeoutError", 173 | "host timeout"); 174 | add_error(kBluetoothHCIErrorUnsupportedFeatureOrParameterValue, 175 | "UnsupportedFeatureOrParameterValueError", 176 | "unsupported feature or parameter value"); 177 | add_error(kBluetoothHCIErrorInvalidHCICommandParameters, 178 | "InvalidHCICommandParametersError", 179 | "invalid HCI command parameters"); 180 | add_error(kBluetoothHCIErrorOtherEndTerminatedConnectionUserEnded, 181 | "OtherEndTerminatedConnectionUserEndedError", 182 | "the other end terminated the connection, by user"); 183 | add_error(kBluetoothHCIErrorOtherEndTerminatedConnectionLowResources, 184 | "OtherEndTerminatedConnectionLowResourcesError", 185 | "the other end terminated the connection, low resources"); 186 | add_error(kBluetoothHCIErrorOtherEndTerminatedConnectionAboutToPowerOff, 187 | "OtherEndTerminatedConnectionAboutToPowerOffError", 188 | "the other end terminated the connection, about to power off"); 189 | add_error(kBluetoothHCIErrorConnectionTerminatedByLocalHost, 190 | "ConnectionTerminatedByLocalHostError", 191 | "connection terminated by local host"); 192 | add_error(kBluetoothHCIErrorRepeatedAttempts, "RepeatedAttemptsError", 193 | "repeated attempts"); 194 | add_error(kBluetoothHCIErrorPairingNotAllowed, "PairingNotAllowedError", 195 | "pairing is not allowed"); 196 | add_error(kBluetoothHCIErrorUnknownLMPPDU, "UnknownLMPPDUError", 197 | "unknown LMP PDU"); 198 | add_error(kBluetoothHCIErrorUnsupportedRemoteFeature, 199 | "UnsupportedRemoteFeatureError", "unsupported remote feature"); 200 | add_error(kBluetoothHCIErrorSCOOffsetRejected, "SCOOffsetRejectedError", 201 | "SCO offset rejected"); 202 | add_error(kBluetoothHCIErrorSCOIntervalRejected, "SCOIntervalRejectedError", 203 | "SCO interval rejected"); 204 | add_error(kBluetoothHCIErrorSCOAirModeRejected, "SCOAirModeRejectedError", 205 | "SCO air mode rejected"); 206 | add_error(kBluetoothHCIErrorInvalidLMPParameters, 207 | "InvalidLMPParametersError", 208 | "invalid LMP parameters"); 209 | add_error(kBluetoothHCIErrorUnspecifiedError, "UnspecifiedError", 210 | "unspecified error"); 211 | add_error(kBluetoothHCIErrorUnsupportedLMPParameterValue, 212 | "UnsupportedLMPParameterValueError", 213 | "unsupported LMP parameter value"); 214 | add_error(kBluetoothHCIErrorRoleChangeNotAllowed, 215 | "RoleChangeNotAllowedError", 216 | "role change not allowed"); 217 | add_error(kBluetoothHCIErrorLMPResponseTimeout, "LMPResponseTimeoutError", 218 | "LMP response timeout"); 219 | add_error(kBluetoothHCIErrorLMPErrorTransactionCollision, 220 | "LMPErrorTransactionCollisionError", 221 | "LMP error transaction collision"); 222 | add_error(kBluetoothHCIErrorLMPPDUNotAllowed, "LMPPDUNotAllowedError", 223 | "LMP DU not allowed"); 224 | add_error(kBluetoothHCIErrorEncryptionModeNotAcceptable, 225 | "EncryptionModeNotAcceptableError", 226 | "encryption mode not acceptable"); 227 | add_error(kBluetoothHCIErrorUnitKeyUsed, "UnitKeyUsedError", 228 | "unit key used"); 229 | add_error(kBluetoothHCIErrorQoSNotSupported, "QoSNotSupportedError", 230 | "QoS not supported"); 231 | add_error(kBluetoothHCIErrorInstantPassed, "InstantPassedError", 232 | "instant passed"); 233 | add_error(kBluetoothHCIErrorPairingWithUnitKeyNotSupported, 234 | "PairingWithUnitKeyNotSupportedError", 235 | "pairing with unit key not supported"); 236 | add_error(kBluetoothHCIErrorHostRejectedUnacceptableDeviceAddress, 237 | "HostRejectedUnacceptableDeviceAddressError", 238 | "host rejected, unacceptable device address"); 239 | add_error(kBluetoothHCIErrorDifferentTransactionCollision, 240 | "DifferentTransactionCollisionError", 241 | "different transaction collision"); 242 | add_error(kBluetoothHCIErrorQoSUnacceptableParameter, 243 | "QoSUnacceptableParameterError", 244 | "Qos unacceptable parameter"); 245 | add_error(kBluetoothHCIErrorQoSRejected, "QoSRejectedError", 246 | "QoS rejected"); 247 | add_error(kBluetoothHCIErrorChannelClassificationNotSupported, 248 | "ChannelClassificationNotSupportedError", 249 | "channel classification not supported"); 250 | add_error(kBluetoothHCIErrorInsufficientSecurity, 251 | "InsufficientSecurityError", 252 | "insufficient security"); 253 | add_error(kBluetoothHCIErrorParameterOutOfMandatoryRange, 254 | "ParameterOutOfMandatoryRangeError", 255 | "parameter out of mandatory range"); 256 | add_error(kBluetoothHCIErrorRoleSwitchPending, "RoleSwitchPendingError", 257 | "role switch pending"); 258 | add_error(kBluetoothHCIErrorReservedSlotViolation, 259 | "ReservedSlotViolationError", "reserved slot violation"); 260 | add_error(kBluetoothHCIErrorRoleSwitchFailed, "RoleSwitchFailedError", 261 | "role switch failed"); 262 | add_error(kBluetoothHCIErrorExtendedInquiryResponseTooLarge, 263 | "ExtendedInquiryResponseTooLargeError", 264 | "extended inquiry response too large"); 265 | add_error(kBluetoothHCIErrorSecureSimplePairingNotSupportedByHost, 266 | "SecureSimplePairingNotSupportedByHostError", 267 | "secure simple pairing not supported by host"); 268 | } 269 | 270 | -------------------------------------------------------------------------------- /ext/bluetooth/macosx/host_controller.m: -------------------------------------------------------------------------------- 1 | #import "ruby_bluetooth.h" 2 | 3 | @implementation HCIDelegate 4 | 5 | - (VALUE) device { 6 | return device; 7 | } 8 | 9 | - (void) setDevice: (VALUE)input { 10 | device = input; 11 | } 12 | 13 | 14 | - (void) controllerClassOfDeviceReverted: (id)sender { 15 | printf("class of device reverted!\n"); 16 | } 17 | 18 | - (void) readLinkQualityForDeviceComplete: (id)controller 19 | device: (IOBluetoothDevice*)bt_device 20 | info: (BluetoothHCILinkQualityInfo*)info 21 | error: (IOReturn)error { 22 | CFRunLoopStop(CFRunLoopGetCurrent()); 23 | 24 | rb_iv_set(device, "@link_quality_error", INT2NUM(error)); 25 | rb_iv_set(device, "@link_quality", UINT2NUM(info->qualityValue)); 26 | } 27 | 28 | - (void) readRSSIForDeviceComplete: (id)controller 29 | device: (IOBluetoothDevice*)bt_device 30 | info: (BluetoothHCIRSSIInfo*)info 31 | error: (IOReturn)error { 32 | CFRunLoopStop(CFRunLoopGetCurrent()); 33 | 34 | rb_iv_set(device, "@rssi_error", INT2NUM(error)); 35 | rb_iv_set(device, "@rssi", INT2NUM(info->RSSIValue)); 36 | } 37 | 38 | @end 39 | 40 | -------------------------------------------------------------------------------- /ext/bluetooth/macosx/ruby_bluetooth.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import 4 | #import 5 | #import 6 | #import 7 | 8 | #import 9 | #import 10 | 11 | #ifdef HAVE_RUBY_THREAD_H 12 | #import 13 | #else 14 | void *rb_thread_call_with_gvl(void *(*func)(void *), void *data1); 15 | 16 | void *rb_thread_call_without_gvl(void *(*func)(void *), void *data1, 17 | rb_unblock_function_t *ubf, void *data2); 18 | #endif 19 | 20 | void init_rbt_error(); 21 | 22 | void rbt_check_status(IOReturn status, NSAutoreleasePool *pool); 23 | 24 | VALUE rbt_scan(VALUE); 25 | 26 | VALUE rbt_device_link_quality(VALUE); 27 | VALUE rbt_device_open_connection(VALUE); 28 | VALUE rbt_device_pair(VALUE); 29 | VALUE rbt_device_request_name(VALUE); 30 | VALUE rbt_device_rssi(VALUE); 31 | 32 | @interface BluetoothDeviceScanner : NSObject { 33 | IOBluetoothDeviceInquiry * _inquiry; 34 | BOOL _busy; 35 | VALUE _devices; 36 | } 37 | 38 | - (void) stopSearch; 39 | - (IOReturn) startSearch; 40 | - (VALUE) devices; 41 | @end 42 | 43 | @interface PairingDelegate : NSObject { 44 | VALUE device; 45 | } 46 | 47 | - (VALUE) device; 48 | - (void) setDevice: (VALUE)input; 49 | 50 | - (void) devicePairingConnecting: (id)sender; 51 | - (void) devicePairingStarted: (id)sender; 52 | - (void) devicePairingFinished: (id)sender 53 | error: (IOReturn)error; 54 | 55 | - (void) devicePairingPasskeyNotification: (id)sender 56 | passkey: (BluetoothPasskey)passkey; 57 | - (void) devicePairingPINCodeRequest: (id)sender; 58 | - (void) devicePairingUserConfirmationRequest: (id)sender 59 | numericValue: (BluetoothNumericValue)numericValue; 60 | @end 61 | 62 | @interface HCIDelegate : NSObject { 63 | VALUE device; 64 | } 65 | 66 | - (VALUE) device; 67 | - (void) setDevice: (VALUE)input; 68 | 69 | - (void) controllerClassOfDeviceReverted: (id)sender; 70 | - (void) readLinkQualityForDeviceComplete: (id)controller 71 | device: (IOBluetoothDevice*)bt_device 72 | info: (BluetoothHCILinkQualityInfo*)info 73 | error: (IOReturn)error; 74 | - (void) readRSSIForDeviceComplete: (id)controller 75 | device: (IOBluetoothDevice*)bt_device 76 | info: (BluetoothHCIRSSIInfo*)info 77 | error: (IOReturn)error; 78 | @end 79 | 80 | -------------------------------------------------------------------------------- /ext/bluetooth/macosx/ruby_bluetooth.m: -------------------------------------------------------------------------------- 1 | #import "ruby_bluetooth.h" 2 | 3 | VALUE rbt_mBluetooth = Qnil; 4 | 5 | VALUE rbt_cBluetoothDevice = Qnil; 6 | VALUE rbt_cBluetoothERRORS = Qnil; 7 | VALUE rbt_cBluetoothError = Qnil; 8 | 9 | void Init_bluetooth() { 10 | rbt_mBluetooth = rb_define_module("Bluetooth"); 11 | 12 | rbt_cBluetoothError = rb_const_get(rbt_mBluetooth, rb_intern("Error")); 13 | 14 | rb_define_singleton_method(rbt_mBluetooth, "scan", rbt_scan, 0); 15 | 16 | rbt_cBluetoothDevice = rb_const_get(rbt_mBluetooth, rb_intern("Device")); 17 | 18 | rb_define_method(rbt_cBluetoothDevice, "connect", 19 | rbt_device_open_connection, 0); 20 | rb_define_method(rbt_cBluetoothDevice, "_link_quality", 21 | rbt_device_link_quality, 0); 22 | rb_define_method(rbt_cBluetoothDevice, "pair", rbt_device_pair, 0); 23 | rb_define_method(rbt_cBluetoothDevice, "request_name", 24 | rbt_device_request_name, 0); 25 | rb_define_method(rbt_cBluetoothDevice, "_rssi", rbt_device_rssi, 0); 26 | 27 | init_rbt_error(); 28 | } 29 | 30 | -------------------------------------------------------------------------------- /ext/bluetooth/macosx/scan.m: -------------------------------------------------------------------------------- 1 | #import "ruby_bluetooth.h" 2 | 3 | struct scan_device_args { 4 | IOBluetoothDevice *device; 5 | VALUE devices; 6 | }; 7 | 8 | extern VALUE rbt_cBluetoothDevice; 9 | 10 | static void 11 | scan_interrupt(void *ptr) { 12 | BluetoothDeviceScanner *bds = (BluetoothDeviceScanner *)ptr; 13 | 14 | [bds stopSearch]; 15 | } 16 | 17 | static void * 18 | scan_add_device(void *ptr) { 19 | struct scan_device_args *args = (struct scan_device_args *)ptr; 20 | IOBluetoothDevice *device = args->device; 21 | VALUE name = Qnil; 22 | const char * device_name = [[device name] UTF8String]; 23 | 24 | VALUE address = rb_str_new2([[device addressString] UTF8String]); 25 | rb_enc_associate(address, rb_utf8_encoding()); 26 | 27 | if (device_name) { 28 | name = rb_str_new2(device_name); 29 | rb_enc_associate(name, rb_utf8_encoding()); 30 | } 31 | 32 | VALUE dev = rb_funcall(rbt_cBluetoothDevice, rb_intern("new"), 2, 33 | address, name); 34 | 35 | rb_ary_push(args->devices, dev); 36 | 37 | return NULL; 38 | } 39 | 40 | static void * 41 | scan_no_gvl(void *ptr) { 42 | CFRunLoopRun(); 43 | 44 | return NULL; 45 | } 46 | 47 | VALUE 48 | rbt_scan(VALUE self) { 49 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 50 | BluetoothDeviceScanner *bds = [BluetoothDeviceScanner new]; 51 | 52 | [bds startSearch]; 53 | 54 | rb_thread_call_without_gvl(scan_no_gvl, NULL, scan_interrupt, (void *)bds); 55 | 56 | [pool release]; 57 | 58 | return [bds devices]; 59 | } 60 | 61 | @implementation BluetoothDeviceScanner 62 | 63 | - (void) deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender 64 | error:(IOReturn)error aborted:(BOOL)aborted { 65 | CFRunLoopStop(CFRunLoopGetCurrent()); 66 | } 67 | 68 | - (void) deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender 69 | device:(IOBluetoothDevice*)device { 70 | struct scan_device_args args; 71 | 72 | args.device = device; 73 | args.devices = _devices; 74 | 75 | rb_thread_call_with_gvl(scan_add_device, (void *)&args); 76 | } 77 | 78 | - (void) deviceInquiryDeviceNameUpdated:(IOBluetoothDeviceInquiry*)sender 79 | device:(IOBluetoothDevice*)device 80 | devicesRemaining:(uint32_t)devicesRemaining { 81 | // do something 82 | } 83 | 84 | - (void) deviceInquiryUpdatingDeviceNamesStarted:(IOBluetoothDeviceInquiry*)sender 85 | devicesRemaining:(uint32_t)devicesRemaining { 86 | // do something 87 | } 88 | 89 | - (IOReturn) startSearch { 90 | IOReturn status; 91 | 92 | [self stopSearch]; 93 | 94 | _inquiry = [IOBluetoothDeviceInquiry inquiryWithDelegate:self]; 95 | _devices = rb_ary_new(); 96 | 97 | [_inquiry setUpdateNewDeviceNames: TRUE]; 98 | 99 | status = [_inquiry start]; 100 | 101 | if (status == kIOReturnSuccess) { 102 | [_inquiry retain]; 103 | 104 | _busy = TRUE; 105 | } 106 | 107 | return status; 108 | } 109 | 110 | - (void) stopSearch { 111 | if (_inquiry) { 112 | [_inquiry stop]; 113 | 114 | [_inquiry release]; 115 | _inquiry = nil; 116 | } 117 | } 118 | 119 | - (VALUE) devices { 120 | return _devices; 121 | } 122 | @end 123 | 124 | -------------------------------------------------------------------------------- /ext/bluetooth/win32/ruby_bluetooth.cpp: -------------------------------------------------------------------------------- 1 | // Include the Ruby headers and goodies 2 | #include 3 | #include 4 | #include 5 | #include 6 | //#include 7 | //#include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "ruby_bluetooth.h" 14 | 15 | #pragma comment(lib, "irprops.lib") 16 | 17 | #pragma comment(lib, "ws2_32.lib") 18 | 19 | 20 | VALUE bt_module; 21 | VALUE bt_devices_class; 22 | VALUE bt_cBluetoothDevice; 23 | 24 | // The initialization method for this module 25 | void Init_bluetooth() 26 | { 27 | bt_module = rb_define_module("Bluetooth"); 28 | bt_devices_class = rb_define_class_under(bt_module, "Devices", rb_cObject); 29 | rb_define_singleton_method(bt_devices_class, "scan", RUBY_METHOD_FUNC(bt_devices_scan), 0); 30 | rb_undef_method(bt_devices_class, "initialize"); 31 | 32 | bt_cBluetoothDevice = rb_const_get(mBluetooth, rb_intern("Device")); 33 | } 34 | 35 | // Scan local network for visible remote devices 36 | static VALUE bt_devices_scan(VALUE self) 37 | { 38 | WORD wVersionRequested = 0x202; 39 | HANDLE hRadio; 40 | BLUETOOTH_FIND_RADIO_PARAMS btfrp = { sizeof(btfrp) }; 41 | 42 | HBLUETOOTH_RADIO_FIND hFind = BluetoothFindFirstRadio( &btfrp, &hRadio ); 43 | 44 | VALUE devices_array = rb_ary_new(); 45 | if ( NULL != hFind ) 46 | { 47 | do 48 | { 49 | BLUETOOTH_DEVICE_INFO_STRUCT deviceInfo; 50 | 51 | deviceInfo.dwSize = sizeof(deviceInfo); 52 | 53 | BLUETOOTH_DEVICE_SEARCH_PARAMS deviceSearchParams; 54 | 55 | memset(&deviceSearchParams, 0, sizeof(deviceSearchParams)); 56 | 57 | deviceSearchParams.dwSize = sizeof(deviceSearchParams); 58 | 59 | deviceSearchParams.fReturnAuthenticated = true; 60 | deviceSearchParams.fReturnRemembered = false; 61 | deviceSearchParams.fReturnUnknown = true; 62 | deviceSearchParams.fReturnConnected = true; 63 | deviceSearchParams.fIssueInquiry = true; 64 | deviceSearchParams.cTimeoutMultiplier = 1; 65 | 66 | deviceSearchParams.hRadio = hRadio; 67 | 68 | HANDLE hDeviceFind = BluetoothFindFirstDevice(&deviceSearchParams, &deviceInfo); 69 | 70 | if (NULL != hDeviceFind) 71 | { 72 | do 73 | { 74 | BYTE *rgBytes = deviceInfo.Address.rgBytes; 75 | //BluetoothDisplayDeviceProperties(0, &deviceInfo); 76 | char addr[19] = { 0 }; 77 | char name[248] = { 0 }; 78 | wcstombs(name, deviceInfo.szName, sizeof(name)); 79 | 80 | snprintf(addr, sizeof(addr), "%02x:%02x:%02x:%02x:%02x:%02x", 81 | rgBytes[5], 82 | rgBytes[4], 83 | rgBytes[3], 84 | rgBytes[2], 85 | rgBytes[1], 86 | rgBytes[0]); 87 | 88 | VALUE bt_dev = rb_funcall(bt_cBluetoothDevice, 89 | rb_intern("new"), 2, 90 | rb_str_new2(name), rb_str_new2(addr)); 91 | rb_ary_push(devices_array, bt_dev); 92 | } 93 | while(BluetoothFindNextDevice(hDeviceFind, &deviceInfo)); 94 | 95 | BluetoothFindDeviceClose(hDeviceFind); 96 | } 97 | 98 | GUID guidServices[10]; 99 | 100 | DWORD numServices = sizeof(guidServices); 101 | 102 | DWORD result = BluetoothEnumerateInstalledServices(hRadio, &deviceInfo, &numServices, guidServices); 103 | 104 | CloseHandle( hRadio ); 105 | } while( BluetoothFindNextRadio( hFind, &hRadio ) ); 106 | 107 | BluetoothFindRadioClose( hFind ); 108 | } 109 | 110 | return devices_array; 111 | 112 | } 113 | -------------------------------------------------------------------------------- /ext/bluetooth/win32/ruby_bluetooth.h: -------------------------------------------------------------------------------- 1 | // Prototype for the initialization method - Ruby calls this, not you 2 | void Init_ruby_bluetooth(); 3 | 4 | struct bluetooth_device_struct 5 | { 6 | VALUE addr; 7 | VALUE name; 8 | }; 9 | 10 | static VALUE bt_device_new(VALUE self, VALUE name, VALUE addr); 11 | 12 | static VALUE bt_devices_scan(VALUE self); 13 | 14 | static VALUE bt_socket_s_for_fd(VALUE klass, VALUE fd); 15 | 16 | static VALUE bt_socket_inspect(VALUE self); 17 | 18 | static VALUE bt_init_sock(VALUE sock, int fd); 19 | 20 | static int bt_ruby_socket(int domain, int type, int proto); 21 | 22 | static VALUE bt_rfcomm_socket_init(int argc, VALUE *argv, VALUE sock); 23 | 24 | static VALUE bt_rfcomm_socket_connect(VALUE self, VALUE host, VALUE port); 25 | -------------------------------------------------------------------------------- /lib/bluetooth.rb: -------------------------------------------------------------------------------- 1 | module Bluetooth 2 | 3 | VERSION = '1.0' 4 | 5 | ERRORS = {} 6 | 7 | class Error < RuntimeError 8 | def self.raise status 9 | err = Bluetooth::ERRORS[status] 10 | super(*err) if err 11 | 12 | super self, "unknown error (#{status})" 13 | end 14 | end 15 | 16 | autoload :Device, 'bluetooth/device' 17 | 18 | end 19 | 20 | require 'bluetooth/bluetooth' 21 | 22 | -------------------------------------------------------------------------------- /lib/bluetooth/device.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # A bluetooth device 3 | 4 | class Bluetooth::Device 5 | 6 | ## 7 | # The address of this device in XX-XX-XX-XX-XX-XX format 8 | 9 | attr_accessor :address 10 | 11 | ## 12 | # Sets the name of this device 13 | 14 | attr_writer :name 15 | 16 | ## 17 | # Creates a new Device with an +address+ and an optional +name+ 18 | 19 | def initialize address, name = nil 20 | @address = address 21 | @name = name 22 | 23 | @pair_error = nil 24 | @pair_confirmation_callback = nil 25 | end 26 | 27 | ## 28 | # The bytes of this address 29 | 30 | def address_bytes 31 | @address.split('-').map { |c| c.to_i(16) }.pack 'C*' 32 | end 33 | 34 | ## 35 | # Returns the link quality for the device. 36 | 37 | def link_quality 38 | connect do 39 | _link_quality 40 | end 41 | end 42 | 43 | ## 44 | # The name of this Device. It will be automatically looked up if not 45 | # already known. 46 | 47 | def name 48 | return @name if @name 49 | 50 | @name = request_name 51 | 52 | return '(unknown)' unless @name 53 | 54 | @name 55 | end 56 | 57 | ## 58 | # Called during pairing if user confirmation is required with a number 59 | # to match with the device. Return true if the number matches. 60 | 61 | def pair_confirmation &block 62 | @pair_confirmation_callback = block 63 | end 64 | 65 | ## 66 | # Returns the RSSI for the device 67 | 68 | def rssi 69 | connect do 70 | _rssi 71 | end 72 | end 73 | 74 | def to_s # :nodoc: 75 | "#{name} at #{address}" 76 | end 77 | 78 | end 79 | 80 | -------------------------------------------------------------------------------- /sample/name.rb: -------------------------------------------------------------------------------- 1 | require 'bluetooth' 2 | 3 | address = ARGV.shift || abort("#{$0} address # look up a device with scan.rb") 4 | 5 | device = Bluetooth::Device.new address 6 | 7 | puts device 8 | 9 | -------------------------------------------------------------------------------- /sample/pair.rb: -------------------------------------------------------------------------------- 1 | require 'bluetooth' 2 | 3 | address = ARGV.shift || abort("#{$0} address pin") 4 | 5 | device = Bluetooth::Device.new address 6 | 7 | device.pair_confirmation do |number| 8 | puts "The device should say %06d" % number 9 | true 10 | end 11 | 12 | paired = device.pair ? "paired" : "didn't pair" 13 | 14 | puts paired 15 | 16 | -------------------------------------------------------------------------------- /sample/quality.rb: -------------------------------------------------------------------------------- 1 | require 'bluetooth' 2 | 3 | address = ARGV.shift || abort("#{$0} address # look up a device with scan.rb") 4 | 5 | device = Bluetooth::Device.new address 6 | 7 | begin 8 | device.connect do 9 | loop do 10 | puts 'link quality: %3d RSSI: %4d dB' % [ 11 | device._link_quality, device._rssi 12 | ] 13 | 14 | sleep 2 15 | end 16 | end 17 | rescue Interrupt 18 | exit 19 | rescue Bluetooth::OfflineError 20 | abort 'you need to enable bluetooth' 21 | rescue Bluetooth::Error 22 | puts "#{$!} (#{$!.class})" 23 | retry 24 | end 25 | 26 | -------------------------------------------------------------------------------- /sample/scan.rb: -------------------------------------------------------------------------------- 1 | require 'bluetooth' 2 | 3 | devices = Bluetooth.scan 4 | 5 | devices.each do |device| 6 | puts device 7 | end 8 | 9 | --------------------------------------------------------------------------------