Skip to content

Commit

Permalink
windows: add characteristic read, write and notify operations
Browse files Browse the repository at this point in the history
  • Loading branch information
jagobagascon authored and deadprogram committed Sep 15, 2022
1 parent 1b30ec4 commit e843ce9
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ smoketest-linux:
smoketest-windows:
# Test on Windows.
GOOS=windows CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc go build -o /tmp/go-build-discard ./examples/scanner
GOOS=windows CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc go build -o /tmp/go-build-discard ./examples/discover
GOOS=windows CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc go build -o /tmp/go-build-discard ./examples/heartrate-monitor

smoketest-macos:
# Test on macos.
Expand Down
191 changes: 191 additions & 0 deletions gattc_windows.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
package bluetooth

import (
"errors"
"fmt"
"syscall"
"unsafe"

"github.com/go-ole/go-ole"
"github.com/saltosystems/winrt-go"
"github.com/saltosystems/winrt-go/windows/devices/bluetooth"
"github.com/saltosystems/winrt-go/windows/devices/bluetooth/genericattributeprofile"
"github.com/saltosystems/winrt-go/windows/foundation"
"github.com/saltosystems/winrt-go/windows/storage/streams"
)

var (
errNoWrite = errors.New("bluetooth: write not supported")
errNoWriteWithoutResponse = errors.New("bluetooth: write without response not supported")
errWriteFailed = errors.New("bluetooth: write failed")
errNoRead = errors.New("bluetooth: read not supported")
errNoNotify = errors.New("bluetooth: notify not supported")
errEnableNotificationsFailed = errors.New("bluetooth: enable notifications failed")
)

// DiscoverServices starts a service discovery procedure. Pass a list of service
Expand Down Expand Up @@ -225,3 +240,179 @@ func (c *DeviceCharacteristic) UUID() UUID {
func (c *DeviceCharacteristic) Properties() uint32 {
return uint32(c.properties)
}

// Write replaces the characteristic value with a new value. The
// call will return after all data has been written.
func (c DeviceCharacteristic) Write(p []byte) (n int, err error) {
if c.properties&genericattributeprofile.GattCharacteristicPropertiesWrite == 0 {
return 0, errNoWrite
}

return c.write(p, genericattributeprofile.GattWriteOptionWriteWithResponse)
}

// 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).
func (c DeviceCharacteristic) WriteWithoutResponse(p []byte) (n int, err error) {
if c.properties&genericattributeprofile.GattCharacteristicPropertiesWriteWithoutResponse == 0 {
return 0, errNoWriteWithoutResponse
}
return c.write(p, genericattributeprofile.GattWriteOptionWriteWithoutResponse)
}

func (c DeviceCharacteristic) write(p []byte, mode genericattributeprofile.GattWriteOption) (n int, err error) {
// Convert data to buffer
writer, err := streams.NewDataWriter()
if err != nil {
return 0, err
}
defer writer.Release()

// Add bytes to writer
if err := writer.WriteBytes(uint32(len(p)), p); err != nil {
return 0, err
}

value, err := writer.DetachBuffer()
if err != nil {
return 0, err
}

// IAsyncOperation<GattCommunicationStatus>
asyncOp, err := c.characteristic.WriteValueWithOptionAsync(value, mode)

if err := awaitAsyncOperation(asyncOp, genericattributeprofile.SignatureGattCommunicationStatus); err != nil {
return 0, err
}

res, err := asyncOp.GetResults()
if err != nil {
return 0, err
}

status := genericattributeprofile.GattCommunicationStatus(uintptr(res))

// Is the status success?
if status != genericattributeprofile.GattCommunicationStatusSuccess {
return 0, errWriteFailed
}

// Success
return len(p), nil
}

// Read reads the current characteristic value.
func (c *DeviceCharacteristic) Read(data []byte) (int, error) {
if c.properties&genericattributeprofile.GattCharacteristicPropertiesRead == 0 {
return 0, errNoRead
}

readOp, err := c.characteristic.ReadValueAsync()
if err != nil {
return 0, err
}

// IAsyncOperation<GattReadResult>
if err := awaitAsyncOperation(readOp, genericattributeprofile.SignatureGattReadResult); err != nil {
return 0, err
}

res, err := readOp.GetResults()
if err != nil {
return 0, err
}

result := (*genericattributeprofile.GattReadResult)(res)

buffer, err := result.GetValue()
if err != nil {
return 0, err
}

datareader, err := streams.FromBuffer(buffer)
if err != nil {
return 0, err
}

bufferlen, err := buffer.GetLength()
if err != nil {
return 0, err
}

readBuffer, err := datareader.ReadBytes(bufferlen)
if err != nil {
return 0, err
}

copy(data, readBuffer)
return len(readBuffer), nil
}

// EnableNotifications enables notifications in the Client Characteristic
// Configuration Descriptor (CCCD). This means that most peripherals will send a
// notification with a new value every time the value of the characteristic
// changes.
func (c DeviceCharacteristic) EnableNotifications(callback func(buf []byte)) error {
if c.properties&genericattributeprofile.GattCharacteristicPropertiesNotify == 0 {
return errNoNotify
}

// listen value changed event
// TypedEventHandler<GattCharacteristic,GattValueChangedEventArgs>
guid := winrt.ParameterizedInstanceGUID(foundation.GUIDTypedEventHandler, genericattributeprofile.SignatureGattCharacteristic, genericattributeprofile.SignatureGattValueChangedEventArgs)
valueChangedEventHandler := foundation.NewTypedEventHandler(ole.NewGUID(guid), func(instance *foundation.TypedEventHandler, sender, args unsafe.Pointer) {
valueChangedEvent := (*genericattributeprofile.GattValueChangedEventArgs)(args)

buf, err := valueChangedEvent.GetCharacteristicValue()
if err != nil {
return
}

reader, err := streams.FromBuffer(buf)
if err != nil {
return
}
defer reader.Release()

buflen, err := buf.GetLength()
if err != nil {
return
}

data, err := reader.ReadBytes(buflen)
if err != nil {
return
}

callback(data)
})
_, err := c.characteristic.AddValueChanged(valueChangedEventHandler)
if err != nil {
return err
}

writeOp, err := c.characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(genericattributeprofile.GattClientCharacteristicConfigurationDescriptorValueNotify)
if err != nil {
return err
}

// IAsyncOperation<GattCommunicationStatus>
if err := awaitAsyncOperation(writeOp, genericattributeprofile.SignatureGattCommunicationStatus); err != nil {
return err
}

res, err := writeOp.GetResults()
if err != nil {
return err
}

result := genericattributeprofile.GattCommunicationStatus(uintptr(res))

if result != genericattributeprofile.GattCommunicationStatusSuccess {
return errEnableNotificationsFailed
}

return nil
}

0 comments on commit e843ce9

Please sign in to comment.