From fe363b98fbd9066a312614c47b5b3d896cf683de Mon Sep 17 00:00:00 2001 From: Alexis Couvreur Date: Fri, 3 Oct 2025 22:15:09 -0400 Subject: [PATCH 1/5] feat(gattc): add CanSendWriteWithoutResponse for darwin --- gattc_darwin.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/gattc_darwin.go b/gattc_darwin.go index 2d737dad..ba16e3e3 100644 --- a/gattc_darwin.go +++ b/gattc_darwin.go @@ -199,8 +199,9 @@ func (c DeviceCharacteristic) Write(p []byte) (n int, err error) { // WriteWithoutResponse replaces the characteristic value with a new value. The // call will return before all data has been written. A limited number of such -// writes can be in flight at any given time. This call is also known as a -// "write command" (as opposed to a write request). +// writes can be in flight at any given time. +// You can use CanSendWriteWithoutResponse to check if you can send more writes. +// This call is also known as a "write command" (as opposed to a write request). func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) { c.service.device.prph.WriteCharacteristic(p, c.characteristic, false) @@ -227,6 +228,11 @@ func (c DeviceCharacteristic) GetMTU() (uint16, error) { return uint16(c.service.device.prph.MaximumWriteValueLength(false)), nil } +// CanSendWriteWithoutResponse returns the MTU for the characteristic. +func (c DeviceCharacteristic) CanSendWriteWithoutResponse() bool { + return c.service.device.prph.CanSendWriteWithoutResponse() +} + // Read reads the current characteristic value. func (c *deviceCharacteristic) Read(data []byte) (n int, err error) { c.readChan = make(chan error) From 6b21611f009a4f1ecaae21c0757a1797fb2fd0aa Mon Sep 17 00:00:00 2001 From: Alexis Couvreur Date: Fri, 3 Oct 2025 22:26:13 -0400 Subject: [PATCH 2/5] fix doc --- gattc_darwin.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gattc_darwin.go b/gattc_darwin.go index ba16e3e3..c2b01e78 100644 --- a/gattc_darwin.go +++ b/gattc_darwin.go @@ -228,7 +228,11 @@ func (c DeviceCharacteristic) GetMTU() (uint16, error) { return uint16(c.service.device.prph.MaximumWriteValueLength(false)), nil } -// CanSendWriteWithoutResponse returns the MTU for the characteristic. +// CanSendWriteWithoutResponse returns whether a WriteWithoutResponse can be sent +// at this time. If this returns false, you must wait for some time before +// sending another WriteWithoutResponse. This is typically because the internal +// buffer is full. You can use this to implement your own flow control when +// sending many WriteWithoutResponse calls in a row. func (c DeviceCharacteristic) CanSendWriteWithoutResponse() bool { return c.service.device.prph.CanSendWriteWithoutResponse() } From 319250fd4177611d7f44d38f4dca7a0854d2cfdd Mon Sep 17 00:00:00 2001 From: Alexis Couvreur Date: Tue, 21 Oct 2025 19:42:15 -0400 Subject: [PATCH 3/5] WriteWithoutResponse checks if it can send write without response requests --- gattc.go | 5 +++++ gattc_darwin.go | 27 ++++++++++++++++++--------- 2 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 gattc.go diff --git a/gattc.go b/gattc.go new file mode 100644 index 00000000..640a8fb3 --- /dev/null +++ b/gattc.go @@ -0,0 +1,5 @@ +package bluetooth + +import "errors" + +var ErrCannotSendWriteWithoutResponse = errors.New("cannot send write without response") diff --git a/gattc_darwin.go b/gattc_darwin.go index c2b01e78..ee86683f 100644 --- a/gattc_darwin.go +++ b/gattc_darwin.go @@ -2,6 +2,7 @@ package bluetooth import ( "errors" + "sync" "time" "github.com/tinygo-org/cbgo" @@ -145,9 +146,10 @@ func (s DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteri func (s DeviceService) makeCharacteristic(uuid UUID, dchar cbgo.Characteristic) DeviceCharacteristic { char := DeviceCharacteristic{ deviceCharacteristic: &deviceCharacteristic{ - uuidWrapper: uuid, - service: s, - characteristic: dchar, + uuidWrapper: uuid, + service: s, + characteristic: dchar, + writeWithoutResponseMx: sync.Mutex{}, }, } s.characteristics = append(s.characteristics, char) @@ -165,10 +167,11 @@ type deviceCharacteristic struct { service DeviceService - characteristic cbgo.Characteristic - callback func(buf []byte) - readChan chan error - writeChan chan error + characteristic cbgo.Characteristic + callback func(buf []byte) + readChan chan error + writeChan chan error + writeWithoutResponseMx sync.Mutex } // UUID returns the UUID for this DeviceCharacteristic. @@ -200,9 +203,15 @@ func (c DeviceCharacteristic) Write(p []byte) (n int, err error) { // WriteWithoutResponse replaces the characteristic value with a new value. The // call will return before all data has been written. A limited number of such // writes can be in flight at any given time. -// You can use CanSendWriteWithoutResponse to check if you can send more writes. -// This call is also known as a "write command" (as opposed to a write request). +// If the client is not ready to send write without response requests at this time, ErrCannotSendWriteWithoutResponse is returned. func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) { + c.writeWithoutResponseMx.Lock() + defer c.writeWithoutResponseMx.Unlock() + + if !c.service.device.prph.CanSendWriteWithoutResponse() { + return 0, ErrCannotSendWriteWithoutResponse + } + c.service.device.prph.WriteCharacteristic(p, c.characteristic, false) return len(p), nil From 0e87cf58e16da4e42cba4874ecf8aa68b65af699 Mon Sep 17 00:00:00 2001 From: Alexis Couvreur Date: Thu, 23 Oct 2025 08:48:21 -0400 Subject: [PATCH 4/5] remove mutex --- gattc_darwin.go | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/gattc_darwin.go b/gattc_darwin.go index ee86683f..62781263 100644 --- a/gattc_darwin.go +++ b/gattc_darwin.go @@ -2,7 +2,6 @@ package bluetooth import ( "errors" - "sync" "time" "github.com/tinygo-org/cbgo" @@ -146,10 +145,9 @@ func (s DeviceService) DiscoverCharacteristics(uuids []UUID) ([]DeviceCharacteri func (s DeviceService) makeCharacteristic(uuid UUID, dchar cbgo.Characteristic) DeviceCharacteristic { char := DeviceCharacteristic{ deviceCharacteristic: &deviceCharacteristic{ - uuidWrapper: uuid, - service: s, - characteristic: dchar, - writeWithoutResponseMx: sync.Mutex{}, + uuidWrapper: uuid, + service: s, + characteristic: dchar, }, } s.characteristics = append(s.characteristics, char) @@ -167,11 +165,10 @@ type deviceCharacteristic struct { service DeviceService - characteristic cbgo.Characteristic - callback func(buf []byte) - readChan chan error - writeChan chan error - writeWithoutResponseMx sync.Mutex + characteristic cbgo.Characteristic + callback func(buf []byte) + readChan chan error + writeChan chan error } // UUID returns the UUID for this DeviceCharacteristic. @@ -205,9 +202,6 @@ func (c DeviceCharacteristic) Write(p []byte) (n int, err error) { // writes can be in flight at any given time. // If the client is not ready to send write without response requests at this time, ErrCannotSendWriteWithoutResponse is returned. func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) { - c.writeWithoutResponseMx.Lock() - defer c.writeWithoutResponseMx.Unlock() - if !c.service.device.prph.CanSendWriteWithoutResponse() { return 0, ErrCannotSendWriteWithoutResponse } From 281cb3fd32a94931eeae05f82ce2af17cd69c966 Mon Sep 17 00:00:00 2001 From: Alexis Couvreur Date: Thu, 23 Oct 2025 16:21:49 -0400 Subject: [PATCH 5/5] remove unused named return params --- gattc_darwin.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gattc_darwin.go b/gattc_darwin.go index 62781263..983fb25e 100644 --- a/gattc_darwin.go +++ b/gattc_darwin.go @@ -201,7 +201,7 @@ func (c DeviceCharacteristic) Write(p []byte) (n int, err error) { // call will return before all data has been written. A limited number of such // writes can be in flight at any given time. // If the client is not ready to send write without response requests at this time, ErrCannotSendWriteWithoutResponse is returned. -func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) { +func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (int, error) { if !c.service.device.prph.CanSendWriteWithoutResponse() { return 0, ErrCannotSendWriteWithoutResponse } @@ -234,8 +234,7 @@ func (c DeviceCharacteristic) GetMTU() (uint16, error) { // CanSendWriteWithoutResponse returns whether a WriteWithoutResponse can be sent // at this time. If this returns false, you must wait for some time before // sending another WriteWithoutResponse. This is typically because the internal -// buffer is full. You can use this to implement your own flow control when -// sending many WriteWithoutResponse calls in a row. +// buffer is full. func (c DeviceCharacteristic) CanSendWriteWithoutResponse() bool { return c.service.device.prph.CanSendWriteWithoutResponse() }