package mijia import ( "encoding/binary" "fmt" "os" "sync" "time" "git.ocjtech.us/jeff/bluelisten/lib/bluetooth" "github.com/currantlabs/ble" influxdb2 "github.com/influxdata/influxdb-client-go" influxdb2_api "github.com/influxdata/influxdb-client-go/api" ) var debug = false // XiaomiMijiaHTV1UUID is the Bluetooth UUID for Xiaomi Mijia BLE sensor data var XiaomiMijiaHTV1UUID ble.UUID = []byte{0x95, 0xfe} const ( temperatureEvent = 0x1004 humidityEvent = 0x1006 illuminanceEvent = 0x1007 moistureEvent = 0x1008 conductivityEvent = 0x1009 batteryEvent = 0x100a temperatureAndHumidityEvent = 0x100d ) // ParseXiaomiMijiaSensorData parses func ParseXiaomiMijiaSensorData(mux *sync.Mutex, writeAPI *influxdb2_api.WriteAPI, advertisement ble.Advertisement, index int, sd ble.ServiceData) { timestamp := time.Now() var sensorData struct { frameControl uint16 isFactoryNew bool isConnected bool isCentral bool isEncrypted bool hasMacAddress bool hasCapabilities bool hasEvent bool hasCustomData bool hasSubtitle bool hasBinding bool version uint16 productID uint16 frameCounter uint8 macAddress []byte capabilities uint8 capabilityData struct { connectable bool central bool secure bool io uint8 } eventData struct { eventType uint16 eventLength uint8 temperature float64 humidity float64 battery uint8 illuminance uint conductivity int16 moisture int8 } } sensorData.frameControl = binary.LittleEndian.Uint16(sd.Data[0:]) sensorData.isFactoryNew = sensorData.frameControl&(1<<0) != 0 sensorData.isConnected = sensorData.frameControl&(1<<1) != 0 sensorData.isCentral = sensorData.frameControl&(1<<2) != 0 sensorData.isEncrypted = sensorData.frameControl&(1<<3) != 0 sensorData.hasMacAddress = sensorData.frameControl&(1<<4) != 0 sensorData.hasCapabilities = sensorData.frameControl&(1<<5) != 0 sensorData.hasEvent = sensorData.frameControl&(1<<6) != 0 sensorData.hasCustomData = sensorData.frameControl&(1<<7) != 0 sensorData.hasSubtitle = sensorData.frameControl&(1<<8) != 0 sensorData.hasBinding = sensorData.frameControl&(1<<9) != 0 sensorData.version = (sensorData.frameControl >> 12) & 0x0f sensorData.productID = binary.LittleEndian.Uint16(sd.Data[2:]) sensorData.frameCounter = uint8(sd.Data[4]) if sensorData.hasMacAddress { sensorData.macAddress = sd.Data[5 : 5+6] for i, j := 0, len(sensorData.macAddress)-1; i < j; i, j = i+1, j-1 { sensorData.macAddress[i], sensorData.macAddress[j] = sensorData.macAddress[j], sensorData.macAddress[i] } } if sensorData.hasCapabilities { var capabilityOffset int = 11 if !sensorData.hasMacAddress { capabilityOffset = 5 } sensorData.capabilities = uint8(sd.Data[capabilityOffset]) sensorData.capabilityData.connectable = sensorData.capabilities&(1<<0) != 0 sensorData.capabilityData.central = sensorData.capabilities&(1<<1) != 0 sensorData.capabilityData.secure = sensorData.capabilities&(1<<2) != 0 sensorData.capabilityData.io = sensorData.capabilities & ((1 << 3) | (1 << 4)) // != 0 } if sensorData.isEncrypted { if debug { fmt.Printf("Need to decrypt!\n") } return } if sensorData.hasEvent { var eventOffset int = 5 if sensorData.hasMacAddress { eventOffset = 11 } if sensorData.hasCapabilities { eventOffset++ } sensorData.eventData.eventType = binary.LittleEndian.Uint16(sd.Data[eventOffset:]) sensorData.eventData.eventLength = uint8(sd.Data[eventOffset+2]) point := influxdb2.NewPointWithMeasurement("sensor") point.SetTime(timestamp) detector, _ := os.Hostname() if detector != "" { point.AddTag("detector", detector) } point.AddTag("address", advertisement.Address().String()) point.AddTag("product_id", fmt.Sprintf("%x", sensorData.productID)) description := bluetooth.GetDescription(advertisement.Address()) if description != "" { point.AddTag("description", description) } point.AddField("rssi", advertisement.RSSI()) switch sensorData.eventData.eventType { case temperatureEvent: sensorData.eventData.temperature = float64(int16(binary.LittleEndian.Uint16(sd.Data[eventOffset+3:]))) / 10.0 point.AddTag("temperature_unit", "°C") point.AddField("temperature", sensorData.eventData.temperature) case humidityEvent: sensorData.eventData.humidity = float64(binary.LittleEndian.Uint16(sd.Data[eventOffset+3:])) / 10.0 point.AddTag("humidity_unit", "%") point.AddField("humidity", sensorData.eventData.humidity) case illuminanceEvent: sensorData.eventData.illuminance = uint(sd.Data[eventOffset+3]) + uint(sd.Data[eventOffset+4])<<8 + uint(sd.Data[eventOffset+5])<<16 point.AddTag("illuminance_unit", "lx") point.AddField("illuminance", sensorData.eventData.illuminance) case moistureEvent: sensorData.eventData.moisture = int8(sd.Data[eventOffset+3]) point.AddTag("moisture_unit", "%") point.AddField("moisture", sensorData.eventData.moisture) case conductivityEvent: sensorData.eventData.conductivity = int16(binary.LittleEndian.Uint16(sd.Data[eventOffset+3:])) point.AddTag("conductivity_unit", "µS/cm") point.AddField("conductivity", sensorData.eventData.conductivity) case batteryEvent: sensorData.eventData.battery = uint8(sd.Data[eventOffset+3]) if debug { fmt.Printf("battery: %d\n", sensorData.eventData.battery) } point.AddTag("battery_unit", "%") point.AddField("battery", sensorData.eventData.battery) case temperatureAndHumidityEvent: sensorData.eventData.temperature = float64(int16(binary.LittleEndian.Uint16(sd.Data[eventOffset+3:]))) / 10.0 sensorData.eventData.humidity = float64(binary.LittleEndian.Uint16(sd.Data[eventOffset+5:])) / 10.0 point.AddTag("temperature_unit", "°C") point.AddField("temperature", sensorData.eventData.temperature) point.AddTag("humidity_unit", "%") point.AddField("humidity", sensorData.eventData.humidity) default: if debug { fmt.Printf("Unknown event type: %d\n", sensorData.eventData.eventType) } } (*writeAPI).WritePoint(point) } if debug { mux.Lock() fmt.Printf("*******************************************\n") fmt.Printf("*******************************************\n") mux.Unlock() } }