func (d *Device) conn(m msg) *conn { // Convert xpc.UUID to ble.UUID. a := ble.MustParse(m.deviceUUID().String()) c, ok := d.conns[a.String()] if !ok { c = newConn(d, a) d.conns[a.String()] = c } return c }
// DiscoverDescriptors finds all the descriptors within a characteristic. [Vol 3, Part G, 4.7.1] // If filter is specified, only filtered descriptors are returned. func (cln *Client) DiscoverDescriptors(ds []ble.UUID, c *ble.Characteristic) ([]*ble.Descriptor, error) { rsp := cln.conn.sendReq(70, xpc.Dict{ "kCBMsgArgDeviceUUID": cln.id, "kCBMsgArgCharacteristicHandle": c.Handle, "kCBMsgArgCharacteristicValueHandle": c.ValueHandle, "kCBMsgArgUUIDs": uuidSlice(ds), }) for _, xds := range rsp.descriptors() { xd := msg(xds.(xpc.Dict)) c.Descriptors = append(c.Descriptors, &ble.Descriptor{ UUID: ble.MustParse(xd.uuid()), Handle: uint16(xd.descriptorHandle()), }) } return c.Descriptors, nil }
// DiscoverCharacteristics finds all the characteristics within a service. [Vol 3, Part G, 4.6.1] // If filter is specified, only filtered characteristics are returned. func (cln *Client) DiscoverCharacteristics(cs []ble.UUID, s *ble.Service) ([]*ble.Characteristic, error) { rsp := cln.conn.sendReq(62, xpc.Dict{ "kCBMsgArgDeviceUUID": cln.id, "kCBMsgArgServiceStartHandle": s.Handle, "kCBMsgArgServiceEndHandle": s.EndHandle, "kCBMsgArgUUIDs": uuidSlice(cs), }) if rsp.err() != nil { return nil, rsp.err() } for _, xcs := range rsp.characteristics() { xc := msg(xcs.(xpc.Dict)) s.Characteristics = append(s.Characteristics, &ble.Characteristic{ UUID: ble.MustParse(xc.uuid()), Property: ble.Property(xc.characteristicProperties()), Handle: uint16(xc.characteristicHandle()), ValueHandle: uint16(xc.characteristicValueHandle()), }) } return s.Characteristics, nil }
// DiscoverServices finds all the primary services on a server. [Vol 3, Part G, 4.4.1] // If filter is specified, only filtered services are returned. func (cln *Client) DiscoverServices(ss []ble.UUID) ([]*ble.Service, error) { rsp := cln.conn.sendReq(45, xpc.Dict{ "kCBMsgArgDeviceUUID": cln.id, "kCBMsgArgUUIDs": uuidSlice(ss), }) if rsp.err() != nil { return nil, rsp.err() } svcs := []*ble.Service{} for _, xss := range rsp.services() { xs := msg(xss.(xpc.Dict)) svcs = append(svcs, &ble.Service{ UUID: ble.MustParse(xs.uuid()), Handle: uint16(xs.serviceStartHandle()), EndHandle: uint16(xs.serviceEndHandle()), }) } if cln.profile == nil { cln.profile = &ble.Profile{Services: svcs} } return svcs, nil }
func explore(cln ble.Client, p *ble.Profile) error { for _, s := range p.Services { fmt.Printf(" Service: %s %s, Handle (0x%02X)\n", s.UUID, ble.Name(s.UUID), s.Handle) for _, c := range s.Characteristics { fmt.Printf(" Characteristic: %s %s, Property: 0x%02X (%s), Handle(0x%02X), VHandle(0x%02X)\n", c.UUID, ble.Name(c.UUID), c.Property, propString(c.Property), c.Handle, c.ValueHandle) if (c.Property & ble.CharRead) != 0 { b, err := cln.ReadCharacteristic(c) if err != nil { fmt.Printf("Failed to read characteristic: %s\n", err) continue } fmt.Printf(" Value %x | %q\n", b, b) } for _, d := range c.Descriptors { fmt.Printf(" Descriptor: %s %s, Handle(0x%02x)\n", d.UUID, ble.Name(d.UUID), d.Handle) b, err := cln.ReadDescriptor(d) if err != nil { fmt.Printf("Failed to read descriptor: %s\n", err) continue } fmt.Printf(" Value %x | %q\n", b, b) } if *sub != 0 { // Don't bother to subscribe the Service Changed characteristics. if c.UUID.Equal(ble.ServiceChangedUUID) { continue } // Don't touch the Apple-specific Service/Characteristic. // Service: D0611E78BBB44591A5F8487910AE4366 // Characteristic: 8667556C9A374C9184ED54EE27D90049, Property: 0x18 (WN), // Descriptor: 2902, Client Characteristic Configuration // Value 0000 | "\x00\x00" if c.UUID.Equal(ble.MustParse("8667556C9A374C9184ED54EE27D90049")) { continue } if (c.Property & ble.CharNotify) != 0 { fmt.Printf("\n-- Subscribe to notification for %s --\n", *sub) h := func(req []byte) { fmt.Printf("Notified: %q [ % X ]\n", string(req), req) } if err := cln.Subscribe(c, false, h); err != nil { log.Fatalf("subscribe failed: %s", err) } time.Sleep(*sub) if err := cln.Unsubscribe(c, false); err != nil { log.Fatalf("unsubscribe failed: %s", err) } fmt.Printf("-- Unsubscribe to notification --\n") } if (c.Property & ble.CharIndicate) != 0 { fmt.Printf("\n-- Subscribe to indication of %s --\n", *sub) h := func(req []byte) { fmt.Printf("Indicated: %q [ % X ]\n", string(req), req) } if err := cln.Subscribe(c, true, h); err != nil { log.Fatalf("subscribe failed: %s", err) } time.Sleep(*sub) if err := cln.Unsubscribe(c, true); err != nil { log.Fatalf("unsubscribe failed: %s", err) } fmt.Printf("-- Unsubscribe to indication --\n") } } } fmt.Printf("\n") } return nil }
package lib import "github.com/currantlabs/ble" // Private 128-bit UUIDs, which avoids the base of pre-defined 16/32-bits UUIDS // xxxxxxxx-0000-1000-8000-00805F9B34FB [Vol 3, Part B, 2.5.1]. var ( TestSvcUUID = ble.MustParse("00010000-0001-1000-8000-00805F9B34FB") CountCharUUID = ble.MustParse("00010000-0002-1000-8000-00805F9B34FB") EchoCharUUID = ble.MustParse("00020000-0002-1000-8000-00805F9B34FB") )