From 11971e92742bc363e9fb419d3721a9da51a9aa85 Mon Sep 17 00:00:00 2001 From: cn Date: Wed, 12 Dec 2018 07:26:18 +0100 Subject: [PATCH] reorga --- .gitignore | 2 +- README.md | 48 +++++++ cmd/{mifloractl => munin-miflora}/main.go | 11 +- common/common.go | 16 +++ main.g__o | 164 ---------------------- reset.py | 7 - main.g_o => utils/legacy/scanner.go | 10 +- utils/reset.py | 12 ++ 8 files changed, 86 insertions(+), 184 deletions(-) create mode 100644 README.md rename cmd/{mifloractl => munin-miflora}/main.go (90%) delete mode 100644 main.g__o delete mode 100644 reset.py rename main.g_o => utils/legacy/scanner.go (94%) create mode 100755 utils/reset.py diff --git a/.gitignore b/.gitignore index a4ef343..6026acb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -mifloractl +cmd/munin-miflora/munin-miflora miflorad diff --git a/README.md b/README.md new file mode 100644 index 0000000..6557982 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# miflorad + +This project aims to produce tools written in Go for interfacing with Xiaomi Flora sensors for IoT use cases. + +## Misc + +If the Intel Wireless Bluetooth 8265 chip gets stuck ([source](https://bbs.archlinux.org/viewtopic.php?id=193813)): + +```bash +# pip install pyusb +sudo python utils/reset.py +``` + +The output of `gatttool` listing all characteristics of a Xiaomi Flora sensor (firmware version 2.7.0): + +``` +$ gatttool -b C4:7C:8D:xx:xx:xx --characteristics +handle = 0x0002, char properties = 0x02, char value handle = 0x0003, uuid = 00002a00-0000-1000-8000-00805f9b34fb +handle = 0x0004, char properties = 0x02, char value handle = 0x0005, uuid = 00002a01-0000-1000-8000-00805f9b34fb +handle = 0x0006, char properties = 0x0a, char value handle = 0x0007, uuid = 00002a02-0000-1000-8000-00805f9b34fb +handle = 0x0008, char properties = 0x02, char value handle = 0x0009, uuid = 00002a04-0000-1000-8000-00805f9b34fb +handle = 0x000d, char properties = 0x22, char value handle = 0x000e, uuid = 00002a05-0000-1000-8000-00805f9b34fb +handle = 0x0011, char properties = 0x1a, char value handle = 0x0012, uuid = 00000001-0000-1000-8000-00805f9b34fb +handle = 0x0014, char properties = 0x02, char value handle = 0x0015, uuid = 00000002-0000-1000-8000-00805f9b34fb +handle = 0x0016, char properties = 0x12, char value handle = 0x0017, uuid = 00000004-0000-1000-8000-00805f9b34fb +handle = 0x0018, char properties = 0x08, char value handle = 0x0019, uuid = 00000007-0000-1000-8000-00805f9b34fb +handle = 0x001a, char properties = 0x08, char value handle = 0x001b, uuid = 00000010-0000-1000-8000-00805f9b34fb +handle = 0x001c, char properties = 0x0a, char value handle = 0x001d, uuid = 00000013-0000-1000-8000-00805f9b34fb +handle = 0x001e, char properties = 0x02, char value handle = 0x001f, uuid = 00000014-0000-1000-8000-00805f9b34fb +handle = 0x0020, char properties = 0x10, char value handle = 0x0021, uuid = 00001001-0000-1000-8000-00805f9b34fb +handle = 0x0024, char properties = 0x0a, char value handle = 0x0025, uuid = 8082caa8-41a6-4021-91c6-56f9b954cc34 +handle = 0x0026, char properties = 0x0a, char value handle = 0x0027, uuid = 724249f0-5ec3-4b5f-8804-42345af08651 +handle = 0x0028, char properties = 0x02, char value handle = 0x0029, uuid = 6c53db25-47a1-45fe-a022-7c92fb334fd4 +handle = 0x002a, char properties = 0x0a, char value handle = 0x002b, uuid = 9d84b9a3-000c-49d8-9183-855b673fda31 +handle = 0x002c, char properties = 0x0e, char value handle = 0x002d, uuid = 457871e8-d516-4ca1-9116-57d0b17b9cb2 +handle = 0x002e, char properties = 0x12, char value handle = 0x002f, uuid = 5f78df94-798c-46f5-990a-b3eb6a065c88 +handle = 0x0032, char properties = 0x0a, char value handle = 0x0033, uuid = 00001a00-0000-1000-8000-00805f9b34fb +handle = 0x0034, char properties = 0x1a, char value handle = 0x0035, uuid = 00001a01-0000-1000-8000-00805f9b34fb +handle = 0x0037, char properties = 0x02, char value handle = 0x0038, uuid = 00001a02-0000-1000-8000-00805f9b34fb +handle = 0x003b, char properties = 0x02, char value handle = 0x003c, uuid = 00001a11-0000-1000-8000-00805f9b34fb +handle = 0x003d, char properties = 0x1a, char value handle = 0x003e, uuid = 00001a10-0000-1000-8000-00805f9b34fb +handle = 0x0040, char properties = 0x02, char value handle = 0x0041, uuid = 00001a12-0000-1000-8000-00805f9b34fb +``` + +## Related Work + +- [https://wiki.hackerspace.pl/projects:xiaomi-flora](https://wiki.hackerspace.pl/projects:xiaomi-flora) (very nice teardown) +- [https://github.com/open-homeautomation/miflora](https://github.com/open-homeautomation/miflora) (python library) diff --git a/cmd/mifloractl/main.go b/cmd/munin-miflora/main.go similarity index 90% rename from cmd/mifloractl/main.go rename to cmd/munin-miflora/main.go index 31003a3..4680e1d 100644 --- a/cmd/mifloractl/main.go +++ b/cmd/munin-miflora/main.go @@ -90,11 +90,11 @@ func onPeriphConnected(p gatt.Peripheral, err error) { return } - fmt.Fprintf(os.Stdout, "%s.miflora.%s.battery_level %d %d\n", prefix, id, metaData.BatteryLevel, time.Now().Unix()) - // fmt.Fprintf(os.Stdout, "Battery level: %d%%\n", metaData.BatteryLevel) - // fmt.Fprintf(os.Stdout, "%s.miflora.%s.firmware_version %s %d\n", prefix, id, metaData.FirmwareVersion, time.Now().Unix()) fmt.Fprintf(os.Stderr, "Firmware version: %s\n", metaData.FirmwareVersion) + fmt.Fprintf(os.Stdout, "%s.miflora.%s.battery_level %d %d\n", prefix, id, metaData.BatteryLevel, time.Now().Unix()) + fmt.Fprintf(os.Stdout, "%s.miflora.%s.firmware_version %s %d\n", prefix, id, metaData.NumericFirmwareVersion(), time.Now().Unix()) + // for the newer models a magic number must be written before we can read the current data if metaData.FirmwareVersion >= "2.6.6" { err2 := common.MifloraRequestModeChange(p) @@ -113,10 +113,6 @@ func onPeriphConnected(p gatt.Peripheral, err error) { fmt.Fprintf(os.Stdout, "%s.miflora.%s.brightness %d %d\n", prefix, id, sensorData.Brightness, time.Now().Unix()) fmt.Fprintf(os.Stdout, "%s.miflora.%s.moisture %d %d\n", prefix, id, sensorData.Moisture, time.Now().Unix()) fmt.Fprintf(os.Stdout, "%s.miflora.%s.conductivity %d %d\n", prefix, id, sensorData.Conductivity, time.Now().Unix()) - // fmt.Fprintf(os.Stdout, "Temperature: %.1f °C\n", sensorData.Temperature) - // fmt.Fprintf(os.Stdout, "Brightness: %d lux\n", sensorData.Brightness) - // fmt.Fprintf(os.Stdout, "Moisture: %d %%\n", sensorData.Moisture) - // fmt.Fprintf(os.Stdout, "Conductivity: %d µS/cm\n", sensorData.Conductivity) } func onPeriphDisconnected(p gatt.Peripheral, err error) { @@ -171,6 +167,7 @@ func main() { fmt.Fprintln(os.Stderr, "Connection timed out") // TODO: can hang due when device has terminated the connection on it's own already // device.CancelConnection(discoveryResult.p) + os.Exit(1) } // Note: calls CancelConnetcion() and thus suffers the same problem, kernel will cleanup after our process finishes diff --git a/common/common.go b/common/common.go index 867059a..7dcd0aa 100644 --- a/common/common.go +++ b/common/common.go @@ -3,6 +3,9 @@ package common import ( "encoding/binary" "errors" + "math" + "strconv" + "strings" "github.com/currantlabs/gatt" ) @@ -107,3 +110,16 @@ func MifloraRequstSensorData(p gatt.Peripheral) (SensorDataResponse, error) { Conductivity: binary.LittleEndian.Uint16(bytes[8:10]), }, nil } + +func (res VersionBatteryResponse) NumericFirmwareVersion() int { + // turns "2.3.4" into 20304 + version := 0 + parts := strings.Split(res.FirmwareVersion, ".") + for i, part := range parts { + partNumber, err := strconv.Atoi(part) + if err != nil { + version += int(math.Pow10((len(parts)-(i+1))*2)) * partNumber + } + } + return version +} diff --git a/main.g__o b/main.g__o deleted file mode 100644 index 0b63014..0000000 --- a/main.g__o +++ /dev/null @@ -1,164 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "os" - "strings" - "time" - - "github.com/currantlabs/gatt" - "github.com/currantlabs/gatt/examples/option" -) - -var done = make(chan struct{}) - -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() - } -} - -func onPeriphDiscovered(p gatt.Peripheral, a *gatt.Advertisement, rssi int) { - id := strings.ToUpper(flag.Args()[0]) - if strings.ToUpper(p.ID()) != id { - return - } - - // 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("") - - p.Device().Connect(p) -} - -func onPeriphConnected(p gatt.Peripheral, err error) { - 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) - } - - // Discovery services - ss, err := p.DiscoverServices(nil) - 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) - - // Discovery characteristics - cs, err := p.DiscoverCharacteristics(nil, s) - if err != nil { - fmt.Printf("Failed to discover characteristics, err: %s\n", err) - continue - } - - for _, c := range cs { - msg := " Characteristic " + c.UUID().String() - if len(c.Name()) > 0 { - msg += " (" + c.Name() + ")" - } - msg += "\n properties " + c.Properties().String() - fmt.Println(msg) - - // Read the characteristic, if possible. - if (c.Properties() & gatt.CharRead) != 0 { - 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) - } - - // 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) -} - -func onPeriphDisconnected(p gatt.Peripheral, err error) { - fmt.Println("Disconnected") - close(done) -} - -func main() { - 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 - } - - // Register handlers. - d.Handle( - gatt.PeripheralDiscovered(onPeriphDiscovered), - gatt.PeripheralConnected(onPeriphConnected), - gatt.PeripheralDisconnected(onPeriphDisconnected), - ) - - d.Init(onStateChanged) - <-done - fmt.Println("Done") -} diff --git a/reset.py b/reset.py deleted file mode 100644 index f52a2c3..0000000 --- a/reset.py +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/python - -# Note: pip install pyusb - -from usb.core import find as finddev -dev = finddev(idVendor=0x8087, idProduct=0x0a2b) -dev.reset() diff --git a/main.g_o b/utils/legacy/scanner.go similarity index 94% rename from main.g_o rename to utils/legacy/scanner.go index ac92863..0edca50 100644 --- a/main.g_o +++ b/utils/legacy/scanner.go @@ -1,12 +1,12 @@ //shows how to watch for new devices and list them -package main +package legacy import ( - "os" + "os" - log "github.com/sirupsen/logrus" - "github.com/muka/go-bluetooth/api" - "github.com/muka/go-bluetooth/emitter" + "github.com/muka/go-bluetooth/api" + "github.com/muka/go-bluetooth/emitter" + log "github.com/sirupsen/logrus" ) const adapterID = "hci0" diff --git a/utils/reset.py b/utils/reset.py new file mode 100755 index 0000000..3aa3710 --- /dev/null +++ b/utils/reset.py @@ -0,0 +1,12 @@ +#!/usr/bin/python + +# This script allows hard-resetting the Intel Wireless Bluetooth 8265 chip +# (ID 8087:0a2b) built into newer Thinkpads which tends to get stuck as other +# people noticed before: https://bbs.archlinux.org/viewtopic.php?id=193813 + +# Setup: pip install pyusb +# Usage: sudo python reset.py + +from usb.core import find as finddev +dev = finddev(idVendor=0x8087, idProduct=0x0a2b) +dev.reset()