| package gatt |
| |
| import ( |
| "errors" |
| "log" |
| |
| "github.com/paypal/gatt/xpc" |
| ) |
| |
| type peripheral struct { |
| // NameChanged is called whenever the peripheral GAP Device name has changed. |
| NameChanged func(Peripheral) |
| |
| // ServicesModified is called when one or more service of a peripheral have changed. |
| // A list of invalid service is provided in the parameter. |
| ServicesModified func(Peripheral, []*Service) |
| |
| d *device |
| svcs []*Service |
| |
| sub *subscriber |
| |
| id xpc.UUID |
| name string |
| |
| reqc chan message |
| rspc chan message |
| quitc chan struct{} |
| } |
| |
| func NewPeripheral(u UUID) Peripheral { return &peripheral{id: xpc.UUID(u.b)} } |
| |
| func (p *peripheral) Device() Device { return p.d } |
| func (p *peripheral) ID() string { return p.id.String() } |
| func (p *peripheral) Name() string { return p.name } |
| func (p *peripheral) Services() []*Service { return p.svcs } |
| |
| func (p *peripheral) DiscoverServices(ss []UUID) ([]*Service, error) { |
| rsp := p.sendReq(44, xpc.Dict{ |
| "kCBMsgArgDeviceUUID": p.id, |
| "kCBMsgArgUUIDs": uuidSlice(ss), |
| }) |
| if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { |
| return nil, attEcode(res) |
| } |
| svcs := []*Service{} |
| for _, xss := range rsp["kCBMsgArgServices"].(xpc.Array) { |
| xs := xss.(xpc.Dict) |
| u := MustParseUUID(xs.MustGetHexBytes("kCBMsgArgUUID")) |
| h := uint16(xs.MustGetInt("kCBMsgArgServiceStartHandle")) |
| endh := uint16(xs.MustGetInt("kCBMsgArgServiceEndHandle")) |
| svcs = append(svcs, &Service{uuid: u, h: h, endh: endh}) |
| } |
| p.svcs = svcs |
| return svcs, nil |
| } |
| |
| func (p *peripheral) DiscoverIncludedServices(ss []UUID, s *Service) ([]*Service, error) { |
| rsp := p.sendReq(60, xpc.Dict{ |
| "kCBMsgArgDeviceUUID": p.id, |
| "kCBMsgArgServiceStartHandle": s.h, |
| "kCBMsgArgServiceEndHandle": s.endh, |
| "kCBMsgArgUUIDs": uuidSlice(ss), |
| }) |
| if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { |
| return nil, attEcode(res) |
| } |
| // TODO |
| return nil, notImplemented |
| } |
| |
| func (p *peripheral) DiscoverCharacteristics(cs []UUID, s *Service) ([]*Characteristic, error) { |
| rsp := p.sendReq(61, xpc.Dict{ |
| "kCBMsgArgDeviceUUID": p.id, |
| "kCBMsgArgServiceStartHandle": s.h, |
| "kCBMsgArgServiceEndHandle": s.endh, |
| "kCBMsgArgUUIDs": uuidSlice(cs), |
| }) |
| if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { |
| return nil, attEcode(res) |
| } |
| for _, xcs := range rsp.MustGetArray("kCBMsgArgCharacteristics") { |
| xc := xcs.(xpc.Dict) |
| u := MustParseUUID(xc.MustGetHexBytes("kCBMsgArgUUID")) |
| ch := uint16(xc.MustGetInt("kCBMsgArgCharacteristicHandle")) |
| vh := uint16(xc.MustGetInt("kCBMsgArgCharacteristicValueHandle")) |
| props := Property(xc.MustGetInt("kCBMsgArgCharacteristicProperties")) |
| c := &Characteristic{uuid: u, svc: s, props: props, h: ch, vh: vh} |
| s.chars = append(s.chars, c) |
| } |
| return s.chars, nil |
| } |
| |
| func (p *peripheral) DiscoverDescriptors(ds []UUID, c *Characteristic) ([]*Descriptor, error) { |
| rsp := p.sendReq(69, xpc.Dict{ |
| "kCBMsgArgDeviceUUID": p.id, |
| "kCBMsgArgCharacteristicHandle": c.h, |
| "kCBMsgArgCharacteristicValueHandle": c.vh, |
| "kCBMsgArgUUIDs": uuidSlice(ds), |
| }) |
| for _, xds := range rsp.MustGetArray("kCBMsgArgDescriptors") { |
| xd := xds.(xpc.Dict) |
| u := MustParseUUID(xd.MustGetHexBytes("kCBMsgArgUUID")) |
| h := uint16(xd.MustGetInt("kCBMsgArgDescriptorHandle")) |
| d := &Descriptor{uuid: u, char: c, h: h} |
| c.descs = append(c.descs, d) |
| } |
| return c.descs, nil |
| } |
| |
| func (p *peripheral) ReadCharacteristic(c *Characteristic) ([]byte, error) { |
| rsp := p.sendReq(64, xpc.Dict{ |
| "kCBMsgArgDeviceUUID": p.id, |
| "kCBMsgArgCharacteristicHandle": c.h, |
| "kCBMsgArgCharacteristicValueHandle": c.vh, |
| }) |
| if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { |
| return nil, attEcode(res) |
| } |
| b := rsp.MustGetBytes("kCBMsgArgData") |
| return b, nil |
| } |
| |
| func (p *peripheral) ReadCharacteristicBlob(c *Characteristic) ([]byte, error) { |
| return nil, errors.New("Not implemented") |
| } |
| |
| func (p *peripheral) WriteCharacteristic(c *Characteristic, b []byte, noRsp bool) error { |
| args := xpc.Dict{ |
| "kCBMsgArgDeviceUUID": p.id, |
| "kCBMsgArgCharacteristicHandle": c.h, |
| "kCBMsgArgCharacteristicValueHandle": c.vh, |
| "kCBMsgArgData": b, |
| "kCBMsgArgType": map[bool]int{false: 0, true: 1}[noRsp], |
| } |
| if noRsp { |
| p.sendCmd(65, args) |
| return nil |
| } |
| rsp := p.sendReq(65, args) |
| if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { |
| return attEcode(res) |
| } |
| return nil |
| } |
| |
| func (p *peripheral) ReadDescriptor(d *Descriptor) ([]byte, error) { |
| rsp := p.sendReq(76, xpc.Dict{ |
| "kCBMsgArgDeviceUUID": p.id, |
| "kCBMsgArgDescriptorHandle": d.h, |
| }) |
| if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { |
| return nil, attEcode(res) |
| } |
| b := rsp.MustGetBytes("kCBMsgArgData") |
| return b, nil |
| } |
| |
| func (p *peripheral) WriteDescriptor(d *Descriptor, b []byte) error { |
| rsp := p.sendReq(77, xpc.Dict{ |
| "kCBMsgArgDeviceUUID": p.id, |
| "kCBMsgArgDescriptorHandle": d.h, |
| "kCBMsgArgData": b, |
| }) |
| if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { |
| return attEcode(res) |
| } |
| return nil |
| } |
| |
| func (p *peripheral) SetNotifyValue(c *Characteristic, f func(*Characteristic, []byte, error)) error { |
| set := 1 |
| if f == nil { |
| set = 0 |
| } |
| // To avoid race condition, registeration is handled before requesting the server. |
| if f != nil { |
| // Note: when notified, core bluetooth reports characteristic handle, not value's handle. |
| p.sub.subscribe(c.h, func(b []byte, err error) { f(c, b, err) }) |
| } |
| rsp := p.sendReq(67, xpc.Dict{ |
| "kCBMsgArgDeviceUUID": p.id, |
| "kCBMsgArgCharacteristicHandle": c.h, |
| "kCBMsgArgCharacteristicValueHandle": c.vh, |
| "kCBMsgArgState": set, |
| }) |
| if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { |
| return attEcode(res) |
| } |
| // To avoid race condition, unregisteration is handled after server responses. |
| if f == nil { |
| p.sub.unsubscribe(c.h) |
| } |
| return nil |
| } |
| |
| func (p *peripheral) SetIndicateValue(c *Characteristic, |
| f func(*Characteristic, []byte, error)) error { |
| // TODO: Implement set indications logic for darwin (https://github.com/paypal/gatt/issues/32) |
| return nil |
| } |
| |
| func (p *peripheral) ReadRSSI() int { |
| rsp := p.sendReq(43, xpc.Dict{"kCBMsgArgDeviceUUID": p.id}) |
| return rsp.MustGetInt("kCBMsgArgData") |
| } |
| |
| func (p *peripheral) SetMTU(mtu uint16) error { |
| return errors.New("Not implemented") |
| } |
| |
| func uuidSlice(uu []UUID) [][]byte { |
| us := [][]byte{} |
| for _, u := range uu { |
| us = append(us, reverse(u.b)) |
| } |
| return us |
| } |
| |
| type message struct { |
| id int |
| args xpc.Dict |
| rspc chan xpc.Dict |
| } |
| |
| func (p *peripheral) sendCmd(id int, args xpc.Dict) { |
| p.reqc <- message{id: id, args: args} |
| } |
| |
| func (p *peripheral) sendReq(id int, args xpc.Dict) xpc.Dict { |
| m := message{id: id, args: args, rspc: make(chan xpc.Dict)} |
| p.reqc <- m |
| return <-m.rspc |
| } |
| |
| func (p *peripheral) loop() { |
| rspc := make(chan message) |
| |
| go func() { |
| for { |
| select { |
| case req := <-p.reqc: |
| p.d.sendCBMsg(req.id, req.args) |
| if req.rspc == nil { |
| break |
| } |
| m := <-rspc |
| req.rspc <- m.args |
| case <-p.quitc: |
| return |
| } |
| } |
| }() |
| |
| for { |
| select { |
| case rsp := <-p.rspc: |
| // Notification |
| if rsp.id == 70 && rsp.args.GetInt("kCBMsgArgIsNotification", 0) != 0 { |
| // While we're notified with the value's handle, blued reports the characteristic handle. |
| ch := uint16(rsp.args.MustGetInt("kCBMsgArgCharacteristicHandle")) |
| b := rsp.args.MustGetBytes("kCBMsgArgData") |
| f := p.sub.fn(ch) |
| if f == nil { |
| log.Printf("notified by unsubscribed handle") |
| // FIXME: should terminate the connection? |
| } else { |
| go f(b, nil) |
| } |
| break |
| } |
| rspc <- rsp |
| case <-p.quitc: |
| return |
| } |
| } |
| } |