package mijia import ( "encoding/binary" "fmt" "time" "github.com/go-ble/ble" influxdb2 "github.com/influxdata/influxdb-client-go/v2" influxdb2_api "github.com/influxdata/influxdb-client-go/v2/api" "go.uber.org/zap" ) // 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 ) const ( isFactoryNewFlag = (1 << iota) isConnectedFlag isCentralFlag isEncryptedFlag hasMacAddressFlag hasCapabilitiesFlag hasEventFlag hasCustomDataFlag hasSubtitleFlag hasBindingFlag ) // ParseXiaomiMijiaSensorData parses func ParseXiaomiMijiaSensorData(writeAPI *influxdb2_api.WriteAPI, logger *zap.Logger, timestamp time.Time, detector string, description string, advertisement ble.Advertisement, index int, sd ble.ServiceData) { 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&isFactoryNewFlag != 0 sensorData.isConnected = sensorData.frameControl&isConnectedFlag != 0 sensorData.isCentral = sensorData.frameControl&isCentralFlag != 0 sensorData.isEncrypted = sensorData.frameControl&isEncryptedFlag != 0 sensorData.hasMacAddress = sensorData.frameControl&hasMacAddressFlag != 0 sensorData.hasCapabilities = sensorData.frameControl&hasCapabilitiesFlag != 0 sensorData.hasEvent = sensorData.frameControl&hasEventFlag != 0 sensorData.hasCustomData = sensorData.frameControl&hasCustomDataFlag != 0 sensorData.hasSubtitle = sensorData.frameControl&hasSubtitleFlag != 0 sensorData.hasBinding = sensorData.frameControl&hasBindingFlag != 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 logger.Debug("mijia capabilities", zap.Bool("connectable", sensorData.capabilityData.connectable), zap.Bool("central", sensorData.capabilityData.central), zap.Bool("secure", sensorData.capabilityData.secure), zap.Uint8("io", sensorData.capabilityData.io)) } if sensorData.isEncrypted { logger.Warn("packet is encrypted") 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]) switch sensorData.eventData.eventType { case temperatureEvent: sensorData.eventData.temperature = float64(int16(binary.LittleEndian.Uint16(sd.Data[eventOffset+3:]))) / 10.0 point := influxdb2.NewPointWithMeasurement("temperature") point.SetTime(timestamp) if detector != "" { point.AddTag("detector", detector) } point.AddTag("address", advertisement.Addr().String()) point.AddTag("product_id", fmt.Sprintf("%x", sensorData.productID)) if description != "" { point.AddTag("description", description) } point.AddTag("unit", "°C") point.AddField("value", sensorData.eventData.temperature) (*writeAPI).WritePoint(point) logger.Debug("sending sensor reading", zap.String("source", advertisement.Addr().String()), zap.String("description", description), zap.String("measurement", "temperature"), zap.String("measurement_unit", "°C"), zap.Float64("value", sensorData.eventData.temperature)) case humidityEvent: sensorData.eventData.humidity = float64(binary.LittleEndian.Uint16(sd.Data[eventOffset+3:])) / 10.0 point := influxdb2.NewPointWithMeasurement("humidity") point.SetTime(timestamp) if detector != "" { point.AddTag("detector", detector) } point.AddTag("address", advertisement.Addr().String()) point.AddTag("product_id", fmt.Sprintf("%x", sensorData.productID)) if description != "" { point.AddTag("description", description) } point.AddTag("unit", "%") point.AddField("value", sensorData.eventData.humidity) (*writeAPI).WritePoint(point) logger.Debug("sending sensor reading", zap.String("source", advertisement.Addr().String()), zap.String("description", description), zap.String("measurement", "humidity"), zap.String("measurement_unit", "%"), zap.Float64("value", 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 := influxdb2.NewPointWithMeasurement("illuminance") point.SetTime(timestamp) if detector != "" { point.AddTag("detector", detector) } point.AddTag("address", advertisement.Addr().String()) point.AddTag("product_id", fmt.Sprintf("%x", sensorData.productID)) if description != "" { point.AddTag("description", description) } point.AddTag("unit", "lx") point.AddField("value", sensorData.eventData.illuminance) (*writeAPI).WritePoint(point) logger.Debug("sending sensor reading", zap.String("source", advertisement.Addr().String()), zap.String("description", description), zap.String("measurement", "illuminance"), zap.String("measurement_unit", "lx"), zap.Uint("value", sensorData.eventData.illuminance)) case moistureEvent: sensorData.eventData.moisture = int8(sd.Data[eventOffset+3]) point := influxdb2.NewPointWithMeasurement("moisture") point.SetTime(timestamp) if detector != "" { point.AddTag("detector", detector) } point.AddTag("address", advertisement.Addr().String()) point.AddTag("product_id", fmt.Sprintf("%x", sensorData.productID)) if description != "" { point.AddTag("description", description) } point.AddTag("unit", "%") point.AddField("value", sensorData.eventData.moisture) (*writeAPI).WritePoint(point) logger.Debug("sending sensor reading", zap.String("source", advertisement.Addr().String()), zap.String("description", description), zap.String("measurement", "moisture"), zap.String("measurement_unit", "%"), zap.Int8("value", sensorData.eventData.moisture)) case conductivityEvent: sensorData.eventData.conductivity = int16(binary.LittleEndian.Uint16(sd.Data[eventOffset+3:])) point := influxdb2.NewPointWithMeasurement("conductivity") point.SetTime(timestamp) if detector != "" { point.AddTag("detector", detector) } point.AddTag("address", advertisement.Addr().String()) point.AddTag("product_id", fmt.Sprintf("%x", sensorData.productID)) if description != "" { point.AddTag("description", description) } point.AddTag("unit", "µS/cm") point.AddField("value", sensorData.eventData.conductivity) (*writeAPI).WritePoint(point) logger.Debug("sending sensor reading", zap.String("source", advertisement.Addr().String()), zap.String("description", description), zap.String("measurement", "conducivity"), zap.String("measurement_unit", "µS/cm"), zap.Int16("value", sensorData.eventData.conductivity)) case batteryEvent: sensorData.eventData.battery = uint8(sd.Data[eventOffset+3]) point := influxdb2.NewPointWithMeasurement("battery") point.SetTime(timestamp) if detector != "" { point.AddTag("detector", detector) } point.AddTag("address", advertisement.Addr().String()) point.AddTag("product_id", fmt.Sprintf("%x", sensorData.productID)) if description != "" { point.AddTag("description", description) } point.AddTag("unit", "%") point.AddField("value", sensorData.eventData.battery) (*writeAPI).WritePoint(point) logger.Debug("sending sensor reading", zap.String("source", advertisement.Addr().String()), zap.String("description", description), zap.String("measurement", "battery"), zap.String("measurement_unit", "%"), zap.Uint8("value", sensorData.eventData.battery)) case temperatureAndHumidityEvent: sensorData.eventData.temperature = float64(int16(binary.LittleEndian.Uint16(sd.Data[eventOffset+3:]))) / 10.0 point := influxdb2.NewPointWithMeasurement("temperature") point.SetTime(timestamp) if detector != "" { point.AddTag("detector", detector) } point.AddTag("address", advertisement.Addr().String()) point.AddTag("product_id", fmt.Sprintf("%x", sensorData.productID)) if description != "" { point.AddTag("description", description) } point.AddTag("unit", "°C") point.AddField("value", sensorData.eventData.temperature) (*writeAPI).WritePoint(point) logger.Debug("sending sensor reading", zap.String("source", advertisement.Addr().String()), zap.String("description", description), zap.String("measurement", "temperature"), zap.String("measurement_unit", "°C"), zap.Float64("value", sensorData.eventData.temperature)) sensorData.eventData.humidity = float64(binary.LittleEndian.Uint16(sd.Data[eventOffset+5:])) / 10.0 point = influxdb2.NewPointWithMeasurement("humidity") point.SetTime(timestamp) if detector != "" { point.AddTag("detector", detector) } point.AddTag("address", advertisement.Addr().String()) point.AddTag("product_id", fmt.Sprintf("%x", sensorData.productID)) if description != "" { point.AddTag("description", description) } point.AddTag("unit", "%") point.AddField("value", sensorData.eventData.humidity) (*writeAPI).WritePoint(point) logger.Debug("sending sensor reading", zap.String("source", advertisement.Addr().String()), zap.String("description", description), zap.String("measurement", "humidity"), zap.String("measurement_unit", "%"), zap.Float64("value", sensorData.eventData.humidity)) default: logger.Debug("unknown event type", zap.String("source", advertisement.Addr().String()), zap.String("description", description), zap.Uint16("event_type", sensorData.eventData.eventType), zap.Binary("data", sd.Data), zap.Binary("event_data", sd.Data[eventOffset+3:])) } } }