diff --git a/main.go b/main.go index 3a2566f..41a8927 100644 --- a/main.go +++ b/main.go @@ -1,192 +1,180 @@ package main import ( - "flag" - "fmt" - "log" - "os" - "strings" - "time" + "encoding/binary" + "flag" + "fmt" + "log" + "os" + "strings" + "time" - "github.com/currantlabs/gatt" - "github.com/currantlabs/gatt/examples/option" + "github.com/currantlabs/gatt" + "github.com/currantlabs/gatt/examples/option" ) -var done = make(chan struct{}) +const discoveryTimeout = 4 * time.Second +const connectionTimeout = 4 * time.Second -func onStateChanged(d gatt.Device, s gatt.State) { - fmt.Println("State:", s) - switch s { - case gatt.StatePoweredOn: - fmt.Println("Scanning...") - d.Scan([]gatt.UUID{}, false) - return - default: - d.StopScanning() - } +var mifloraModeChangeData = []byte{0xa0, 0x1f} + +var mifloraServiceUUID = gatt.MustParseUUID("00001204-0000-1000-8000-00805f9b34fb") +var mifloraCharModeChangeUUID = gatt.MustParseUUID("00001a00-0000-1000-8000-00805f9b34fb") +var mifloraCharReadSensorDataUUID = gatt.MustParseUUID("00001a01-0000-1000-8000-00805f9b34fb") +var mifloraCharVersionBatteryUUID = gatt.MustParseUUID("00001a02-0000-1000-8000-00805f9b34fb") + +var discoveryDone = make(chan gatt.Peripheral) +var connectionDone = make(chan struct{}) + +func onStateChanged(device gatt.Device, state gatt.State) { + fmt.Println("State:", state) + switch state { + case gatt.StatePoweredOn: + fmt.Println("Scanning...") + device.Scan([]gatt.UUID{}, false) + return + default: + device.StopScanning() + } } func onPeriphDiscovered(p gatt.Peripheral, a *gatt.Advertisement, rssi int) { - id := strings.ToUpper(flag.Args()[0]) - if strings.ToUpper(p.ID()) != id { - return - } + id := strings.ToUpper(flag.Args()[0]) + fmt.Println(p.ID()) + if strings.ToUpper(p.ID()) != id { + return + } - // Stop scanning once we've got the peripheral we're looking for. - p.Device().StopScanning() + // Stop scanning once we've got the peripheral we're looking for. + p.Device().StopScanning() - fmt.Printf("\nPeripheral ID:%s, NAME:(%s)\n", p.ID(), p.Name()) - fmt.Println(" Local Name =", a.LocalName) - fmt.Println(" TX Power Level =", a.TxPowerLevel) - fmt.Println(" Manufacturer Data =", a.ManufacturerData) - fmt.Println(" Service Data =", a.ServiceData) - fmt.Println("") + discoveryDone <- p +} - p.Device().Connect(p) +func findServiceByUUID(services []*gatt.Service, u gatt.UUID) *gatt.Service { + for _, service := range services { + if service.UUID().Equal(u) { + return service + } + } + return nil +} + +func findCharacteristicByUUID(characteristics []*gatt.Characteristic, u gatt.UUID) *gatt.Characteristic { + for _, characteristic := range characteristics { + if characteristic.UUID().Equal(u) { + return characteristic + } + } + return nil } func onPeriphConnected(p gatt.Peripheral, err error) { - fmt.Println("Connected") - defer p.Device().CancelConnection(p) + fmt.Println("Connected") + defer p.Device().CancelConnection(p) - if err := p.SetMTU(500); err != nil { - fmt.Printf("Failed to set MTU, err: %s\n", err) - } + if err := p.SetMTU(500); err != nil { + fmt.Printf("Failed to set MTU, err: %s\n", err) + } - // Discovery services - ss, err := p.DiscoverServices(nil) - if err != nil { - fmt.Printf("Failed to discover services, err: %s\n", err) - return - } + // Discovery services + services, err := p.DiscoverServices([]gatt.UUID{mifloraServiceUUID}) + if err != nil { + fmt.Printf("Failed to discover services, err: %s\n", err) + return + } - for _, s := range ss { - msg := "Service: " + s.UUID().String() - if len(s.Name()) > 0 { - msg += " (" + s.Name() + ")" - } - fmt.Println(msg) + if len(services) == 1 { + mifloraService := findServiceByUUID(services, mifloraServiceUUID) - if s.UUID().String() != "0000120400001000800000805f9b34fb" { - fmt.Printf("Skipping uninteresting service\n") - continue - } + chars, err := p.DiscoverCharacteristics(nil, mifloraService) + if err != nil { + fmt.Printf("Failed to discover characteristics, err: %s\n", err) + return + } - // Discovery characteristics - cs, err := p.DiscoverCharacteristics(nil, s) - if err != nil { - fmt.Printf("Failed to discover characteristics, err: %s\n", err) - continue - } + mifloraVersionBatteryChar := findCharacteristicByUUID(chars, mifloraCharVersionBatteryUUID) + bytes, err := p.ReadCharacteristic(mifloraVersionBatteryChar) + if err != nil { + fmt.Printf("Failed to read characteristic, err: %s\n", err) + return + } + fmt.Printf("Battery level: %d%%\n", uint8(bytes[0])) + fmt.Printf("Firmware version: %s\n", string(bytes[2:])) - for _, c := range cs { - msg := " Characteristic " + c.UUID().String() - if len(c.Name()) > 0 { - msg += " (" + c.Name() + ")" - } - fmt.Println(msg) + // for the newer models a magic number must be written before we can read the current data + if string(bytes[2:]) >= "2.6.6" { + mifloraModeChangeChar := findCharacteristicByUUID(chars, mifloraCharModeChangeUUID) + err2 := p.WriteCharacteristic(mifloraModeChangeChar, mifloraModeChangeData, false) + if err2 != nil { + fmt.Printf("Failed to write characteristic, err: %s\n", err2) + return + } + } - if c.UUID().String() == "00001a0000001000800000805f9b34fb" { - err := p.WriteCharacteristic(c, []byte{0xa0, 0x1f}, false) - if err != nil { - fmt.Printf("Failed to write characteristic, err: %s\n", err) - continue - } - } - - if c.UUID().String() == "00001a0200001000800000805f9b34fb" { - b, err := p.ReadCharacteristic(c) - if err != nil { - fmt.Printf("Failed to read characteristic, err: %s\n", err) - continue - } - // fmt.Printf(" value %x | %q\n", b, b) - fmt.Printf(" battery level: %d%%\n", b[0]) - fmt.Printf(" firmware version: %s\n", string(b[2:])) - } - - if c.UUID().String() == "00001a0100001000800000805f9b34fb" { - b, err := p.ReadCharacteristic(c) - if err != nil { - fmt.Printf("Failed to read characteristic, err: %s\n", err) - continue - } - fmt.Printf(" temparature: %f °C\n", float64((int32(b[1]) << 8) + int32(b[0])) / 10.0) - fmt.Printf(" brightness: %d lux\n", (int32(b[6]) << 24) + (int32(b[5]) << 16) + (int32(b[4]) << 8) + int32(b[3])) - fmt.Printf(" moisture: %d %%\n", int32(b[7])) - fmt.Printf(" conductivity: %d µS/cm\n", (int32(b[9]) << 8) + int32(b[8])) - } - - fmt.Printf("Skipping uninteresting char\n") - continue - - // // Discovery descriptors - // ds, err := p.DiscoverDescriptors(nil, c) - // if err != nil { - // fmt.Printf("Failed to discover descriptors, err: %s\n", err) - // continue - // } - // - // for _, d := range ds { - // msg := " Descriptor " + d.UUID().String() - // if len(d.Name()) > 0 { - // msg += " (" + d.Name() + ")" - // } - // fmt.Println(msg) - // - // // Read descriptor (could fail, if it's not readable) - // b, err := p.ReadDescriptor(d) - // if err != nil { - // fmt.Printf("Failed to read descriptor, err: %s\n", err) - // continue - // } - // fmt.Printf(" value %x | %q\n", b, b) - // } - // - // // Subscribe the characteristic, if possible. - // if (c.Properties() & (gatt.CharNotify | gatt.CharIndicate)) != 0 { - // f := func(c *gatt.Characteristic, b []byte, err error) { - // fmt.Printf("notified: % X | %q\n", b, b) - // } - // if err := p.SetNotifyValue(c, f); err != nil { - // fmt.Printf("Failed to subscribe characteristic, err: %s\n", err) - // continue - // } - // } - - } - fmt.Println() - } - - fmt.Printf("Waiting for 5 seconds to get some notifiations, if any.\n") - time.Sleep(5 * time.Second) + mifloraSensorDataChar := findCharacteristicByUUID(chars, mifloraCharReadSensorDataUUID) + bytes2, err3 := p.ReadCharacteristic(mifloraSensorDataChar) + if err3 != nil { + fmt.Printf("Failed to read characteristic, err: %s\n", err3) + return + } + fmt.Printf("Temparature: %f °C\n", float64(binary.LittleEndian.Uint16(bytes2[0:2]))/10.0) + fmt.Printf("Brightness: %d lux\n", binary.LittleEndian.Uint32(bytes2[3:7])) + fmt.Printf("Moisture: %d %%\n", uint8(bytes2[7])) + fmt.Printf("Conductivity: %d µS/cm\n", binary.LittleEndian.Uint16(bytes2[8:10])) + } } func onPeriphDisconnected(p gatt.Peripheral, err error) { - fmt.Println("Disconnected") - close(done) + fmt.Println("Disconnected") + close(connectionDone) } func main() { - flag.Parse() - if len(flag.Args()) != 1 { - log.Fatalf("usage: %s [options] peripheral-id\n", os.Args[0]) - } + flag.Parse() + if len(flag.Args()) != 1 { + log.Fatalf("usage: %s [options] peripheral-id\n", os.Args[0]) + } - d, err := gatt.NewDevice(option.DefaultClientOptions...) - if err != nil { - log.Fatalf("Failed to open device, err: %s\n", err) - return - } + device, err := gatt.NewDevice(option.DefaultClientOptions...) + if err != nil { + log.Fatalf("Failed to open device, err: %s\n", err) + return + } - // Register handlers. - d.Handle( - gatt.PeripheralDiscovered(onPeriphDiscovered), - gatt.PeripheralConnected(onPeriphConnected), - gatt.PeripheralDisconnected(onPeriphDisconnected), - ) + // Register discovery handler + device.Handle(gatt.PeripheralDiscovered(onPeriphDiscovered)) - d.Init(onStateChanged) - <-done - fmt.Println("Done") + device.Init(onStateChanged) + + var peripheral gatt.Peripheral + + select { + case peripheral = <-discoveryDone: + fmt.Println("Discovery done") + case <-time.After(discoveryTimeout): + // fmt.Println("Discovery timed out") + log.Fatalf("Discovery timed out\n") + device.StopScanning() + device.Stop() + } + + fmt.Printf("Discovered peripheral ID:%s, NAME:(%s)\n", peripheral.ID(), peripheral.Name()) + + // Register connection handlers + device.Handle( + gatt.PeripheralConnected(onPeriphConnected), + gatt.PeripheralDisconnected(onPeriphDisconnected), + ) + + device.Connect(peripheral) + + select { + case <-connectionDone: + fmt.Println("Connection done") + case <-time.After(connectionTimeout): + fmt.Println("Connection timed out") + } + + device.Stop() }