cd3c0c4835
I left in a debugging value. Oops. Let's fix that quickly.
171 lines
4.9 KiB
Go
171 lines
4.9 KiB
Go
//go:build !baremetal
|
|
|
|
package bluetooth
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"sync/atomic"
|
|
|
|
"github.com/godbus/dbus/v5"
|
|
"github.com/godbus/dbus/v5/prop"
|
|
)
|
|
|
|
// Unique ID per service (to generate a unique object path).
|
|
var serviceID uint64
|
|
|
|
// Characteristic is a single characteristic in a service. It has an UUID and a
|
|
// value.
|
|
type Characteristic struct {
|
|
char *bluezChar
|
|
permissions CharacteristicPermissions
|
|
}
|
|
|
|
// A small ObjectManager for a single service.
|
|
type objectManager struct {
|
|
objects map[dbus.ObjectPath]map[string]map[string]*prop.Prop
|
|
}
|
|
|
|
// This method implements org.freedesktop.DBus.ObjectManager.
|
|
func (om *objectManager) GetManagedObjects() (map[dbus.ObjectPath]map[string]map[string]dbus.Variant, *dbus.Error) {
|
|
// Convert from a map with *prop.Prop keys, to a map with dbus.Variant keys.
|
|
objects := map[dbus.ObjectPath]map[string]map[string]dbus.Variant{}
|
|
for path, object := range om.objects {
|
|
obj := make(map[string]map[string]dbus.Variant)
|
|
objects[path] = obj
|
|
for iface, props := range object {
|
|
ifaceObj := make(map[string]dbus.Variant)
|
|
obj[iface] = ifaceObj
|
|
for k, v := range props {
|
|
ifaceObj[k] = dbus.MakeVariant(v.Value)
|
|
}
|
|
}
|
|
}
|
|
return objects, nil
|
|
}
|
|
|
|
// Object that implements org.bluez.GattCharacteristic1 to be exported over
|
|
// DBus. Here is the documentation:
|
|
// https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/org.bluez.GattCharacteristic.rst
|
|
type bluezChar struct {
|
|
props *prop.Properties
|
|
writeEvent func(client Connection, offset int, value []byte)
|
|
}
|
|
|
|
func (c *bluezChar) ReadValue(options map[string]dbus.Variant) ([]byte, *dbus.Error) {
|
|
// TODO: should we use the offset value? The BlueZ documentation doesn't
|
|
// clearly specify this. The go-bluetooth library doesn't, but I believe it
|
|
// should be respected.
|
|
value := c.props.GetMust("org.bluez.GattCharacteristic1", "Value").([]byte)
|
|
return value, nil
|
|
}
|
|
|
|
func (c *bluezChar) WriteValue(value []byte, options map[string]dbus.Variant) *dbus.Error {
|
|
if c.writeEvent != nil {
|
|
// BlueZ doesn't seem to tell who did the write, so pass 0 always as the
|
|
// connection ID.
|
|
client := Connection(0)
|
|
offset, _ := options["offset"].Value().(uint16)
|
|
c.writeEvent(client, int(offset), value)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AddService creates a new service with the characteristics listed in the
|
|
// Service struct.
|
|
func (a *Adapter) AddService(s *Service) error {
|
|
// Create a unique DBus path for this service.
|
|
id := atomic.AddUint64(&serviceID, 1)
|
|
path := dbus.ObjectPath(fmt.Sprintf("/org/tinygo/bluetooth/service%d", id))
|
|
|
|
// All objects that will be part of the ObjectManager.
|
|
objects := map[dbus.ObjectPath]map[string]map[string]*prop.Prop{}
|
|
|
|
// Define the service to be exported over DBus.
|
|
serviceSpec := map[string]map[string]*prop.Prop{
|
|
"org.bluez.GattService1": {
|
|
"UUID": {Value: s.UUID.String()},
|
|
"Primary": {Value: true},
|
|
},
|
|
}
|
|
objects[path] = serviceSpec
|
|
|
|
for i, char := range s.Characteristics {
|
|
// Calculate Flags field.
|
|
bluezCharFlags := []string{
|
|
"broadcast", // bit 0
|
|
"read", // bit 1
|
|
"write-without-response", // bit 2
|
|
"write", // bit 3
|
|
"notify", // bit 4
|
|
"indicate", // bit 5
|
|
}
|
|
var flags []string
|
|
for i := 0; i < len(bluezCharFlags); i++ {
|
|
if (char.Flags>>i)&1 != 0 {
|
|
flags = append(flags, bluezCharFlags[i])
|
|
}
|
|
}
|
|
|
|
// Export the properties of this characteristic.
|
|
charPath := path + dbus.ObjectPath("/char"+strconv.Itoa(i))
|
|
propsSpec := map[string]map[string]*prop.Prop{
|
|
"org.bluez.GattCharacteristic1": {
|
|
"UUID": {Value: char.UUID.String()},
|
|
"Service": {Value: path},
|
|
"Flags": {Value: flags},
|
|
"Value": {Value: char.Value, Writable: true, Emit: prop.EmitTrue},
|
|
},
|
|
}
|
|
objects[charPath] = propsSpec
|
|
props, err := prop.Export(a.bus, charPath, propsSpec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Export the methods of this characteristic.
|
|
obj := &bluezChar{
|
|
props: props,
|
|
writeEvent: char.WriteEvent,
|
|
}
|
|
err = a.bus.Export(obj, charPath, "org.bluez.GattCharacteristic1")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Keep the object around for Characteristic.Write.
|
|
if char.Handle != nil {
|
|
char.Handle.permissions = char.Flags
|
|
char.Handle.char = obj
|
|
}
|
|
}
|
|
|
|
// Export all objects that are part of our service.
|
|
om := &objectManager{
|
|
objects: objects,
|
|
}
|
|
err := a.bus.Export(om, path, "org.freedesktop.DBus.ObjectManager")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Register our service.
|
|
return a.adapter.Call("org.bluez.GattManager1.RegisterApplication", 0, path, map[string]dbus.Variant(nil)).Err
|
|
}
|
|
|
|
// Write replaces the characteristic value with a new value.
|
|
func (c *Characteristic) Write(p []byte) (n int, err error) {
|
|
if len(p) == 0 {
|
|
return 0, nil // nothing to do
|
|
}
|
|
|
|
if c.char.writeEvent != nil {
|
|
c.char.writeEvent(0, 0, p)
|
|
}
|
|
gattError := c.char.props.Set("org.bluez.GattCharacteristic1", "Value", dbus.MakeVariant(p))
|
|
if gattError != nil {
|
|
return 0, gattError
|
|
}
|
|
return len(p), nil
|
|
}
|