329 lines
11 KiB
Go
329 lines
11 KiB
Go
package mijia
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/go-ble/ble"
|
|
influxdb2 "github.com/influxdata/influxdb-client-go"
|
|
influxdb2_api "github.com/influxdata/influxdb-client-go/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:]))
|
|
}
|
|
}
|
|
}
|