1
0
mirror of https://github.com/cmur2/miflorad.git synced 2026-04-02 16:18:34 +02:00
This commit is contained in:
cn
2018-12-12 07:26:18 +01:00
parent 1fff831652
commit 11971e9274
8 changed files with 86 additions and 184 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,2 @@
mifloractl
cmd/munin-miflora/munin-miflora
miflorad

48
README.md Normal file
View File

@@ -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)

View File

@@ -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

View File

@@ -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
}

164
main.g__o
View File

@@ -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")
}

View File

@@ -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()

View File

@@ -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"

12
utils/reset.py Executable file
View File

@@ -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()