diff --git a/cmd/munin-miflora-gatt/main.go b/cmd/munin-miflora-gatt/main.go index 9e39f88..600f88c 100644 --- a/cmd/munin-miflora-gatt/main.go +++ b/cmd/munin-miflora-gatt/main.go @@ -4,7 +4,6 @@ import ( "flag" "fmt" "os" - "regexp" "strings" "time" @@ -26,13 +25,18 @@ type DiscoveryResult struct { rssi int } -var discoveryDone = make(chan DiscoveryResult) -var connectionDone = make(chan struct{}) +var ( + discoveryDone = make(chan DiscoveryResult) + connectionDone = make(chan struct{}) +) + +var timeConnectStart time.Time func onStateChanged(device gatt.Device, state gatt.State) { fmt.Fprintln(os.Stderr, "State:", state) switch state { case gatt.StatePoweredOn: + timeConnectStart = time.Now() fmt.Fprintln(os.Stderr, "Scanning...") device.Scan([]gatt.UUID{}, false) return @@ -56,6 +60,14 @@ func onPeriphDiscovered(p gatt.Peripheral, a *gatt.Advertisement, rssi int) { func onPeriphConnected(p gatt.Peripheral, err error) { fmt.Fprintln(os.Stderr, "Connected") + prefix := flag.Args()[0] + id := common.MifloraGetAlphaNumericID(flag.Args()[1]) + + timeConnectTook := time.Since(timeConnectStart).Seconds() + fmt.Fprintf(os.Stdout, "%s.miflora.%s.connect_time %.2f %d\n", prefix, id, timeConnectTook, time.Now().Unix()) + + timeReadoutStart := time.Now() + // Note: can hang due when device has terminated the connection on it's own already // defer p.Device().CancelConnection(p) @@ -68,6 +80,7 @@ func onPeriphConnected(p gatt.Peripheral, err error) { _, err := p.DiscoverServices([]gatt.UUID{gatt.MustParseUUID(common.MifloraServiceUUID)}) if err != nil { fmt.Fprintf(os.Stderr, "Failed to discover services, err: %s\n", err) + fmt.Fprintf(os.Stdout, "%s.miflora.%s.failed 1 %d\n", prefix, id, time.Now().Unix()) return } } @@ -75,21 +88,15 @@ func onPeriphConnected(p gatt.Peripheral, err error) { _, err := p.DiscoverCharacteristics(nil, service) if err != nil { fmt.Fprintf(os.Stderr, "Failed to discover characteristics, err: %s\n", err) + fmt.Fprintf(os.Stdout, "%s.miflora.%s.failed 1 %d\n", prefix, id, time.Now().Unix()) return } } - prefix := flag.Args()[0] - - regexNonAlphaNumeric, err4 := regexp.Compile("[^a-z0-9]+") - if err4 != nil { - fmt.Fprintf(os.Stderr, "Failed to compile regex, err: %s\n", err4) - } - id := regexNonAlphaNumeric.ReplaceAllString(strings.ToLower(flag.Args()[1]), "") - metaData, err := impl.MifloraRequestVersionBattery(p) if err != nil { fmt.Fprintf(os.Stderr, "Failed to request version battery, err: %s\n", err) + fmt.Fprintf(os.Stdout, "%s.miflora.%s.failed 1 %d\n", prefix, id, time.Now().Unix()) return } @@ -98,11 +105,11 @@ func onPeriphConnected(p gatt.Peripheral, err error) { 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 %d %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" { + if metaData.RequiresModeChangeBeforeRead() { err2 := impl.MifloraRequestModeChange(p) if err2 != nil { fmt.Fprintf(os.Stderr, "Failed to request mode change, err: %s\n", err2) + fmt.Fprintf(os.Stdout, "%s.miflora.%s.failed 1 %d\n", prefix, id, time.Now().Unix()) return } } @@ -110,6 +117,7 @@ func onPeriphConnected(p gatt.Peripheral, err error) { sensorData, err3 := impl.MifloraRequstSensorData(p) if err3 != nil { fmt.Fprintf(os.Stderr, "Failed to request sensor data, err: %s\n", err3) + fmt.Fprintf(os.Stdout, "%s.miflora.%s.failed 1 %d\n", prefix, id, time.Now().Unix()) return } fmt.Fprintf(os.Stdout, "%s.miflora.%s.temperature %.1f %d\n", prefix, id, sensorData.Temperature, time.Now().Unix()) @@ -117,6 +125,9 @@ func onPeriphConnected(p gatt.Peripheral, err error) { 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()) + timeReadoutTook := time.Since(timeReadoutStart).Seconds() + fmt.Fprintf(os.Stdout, "%s.miflora.%s.readout_time %.2f %d\n", prefix, id, timeReadoutTook, time.Now().Unix()) + // TODO: report that we are done without closing connection, since it could hang close(connectionDone) } @@ -133,9 +144,13 @@ func main() { os.Exit(1) } + prefix := flag.Args()[0] + id := common.MifloraGetAlphaNumericID(flag.Args()[1]) + device, err := gatt.NewDevice(option.DefaultClientOptions...) if err != nil { fmt.Fprintf(os.Stderr, "Failed to open device, err: %s\n", err) + fmt.Fprintf(os.Stdout, "%s.miflora.%s.failed 1 %d\n", prefix, id, time.Now().Unix()) os.Exit(1) } @@ -151,6 +166,7 @@ func main() { fmt.Fprintln(os.Stderr, "Discovery done") case <-time.After(discoveryTimeout): fmt.Fprintln(os.Stderr, "Discovery timed out") + fmt.Fprintf(os.Stdout, "%s.miflora.%s.failed 1 %d\n", prefix, id, time.Now().Unix()) device.StopScanning() device.Stop() os.Exit(1) @@ -158,6 +174,8 @@ func main() { fmt.Fprintf(os.Stderr, "Discovered peripheral ID:%s, NAME:(%s), RSSI:%d\n", discoveryResult.p.ID(), discoveryResult.p.Name(), discoveryResult.rssi) + fmt.Fprintf(os.Stdout, "%s.miflora.%s.rssi %d %d\n", prefix, id, discoveryResult.rssi, time.Now().Unix()) + // Register connection handlers device.Handle( gatt.PeripheralConnected(onPeriphConnected), diff --git a/cmd/munin-miflora/main.go b/cmd/munin-miflora/main.go index 1a7dc2c..b060e64 100644 --- a/cmd/munin-miflora/main.go +++ b/cmd/munin-miflora/main.go @@ -5,30 +5,26 @@ import ( "flag" "fmt" "os" - "regexp" "strings" "time" + "miflorad/common" impl "miflorad/common/ble" "github.com/go-ble/ble" "github.com/go-ble/ble/examples/lib/dev" ) -const discoveryTimeout = 4 * time.Second +const discoveryTimeout = 10 * time.Second func readData(client ble.Client, profile *ble.Profile) { prefix := flag.Args()[0] - - regexNonAlphaNumeric, err4 := regexp.Compile("[^a-z0-9]+") - if err4 != nil { - fmt.Fprintf(os.Stderr, "Failed to compile regex, err: %s\n", err4) - } - id := regexNonAlphaNumeric.ReplaceAllString(strings.ToLower(flag.Args()[1]), "") + id := common.MifloraGetAlphaNumericID(flag.Args()[1]) metaData, err := impl.RequestVersionBattery(client, profile) if err != nil { fmt.Fprintf(os.Stderr, "Failed to request version battery, err: %s\n", err) + fmt.Fprintf(os.Stdout, "%s.miflora.%s.failed 1 %d\n", prefix, id, time.Now().Unix()) return } @@ -37,11 +33,11 @@ func readData(client ble.Client, profile *ble.Profile) { 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 %d %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" { + if metaData.RequiresModeChangeBeforeRead() { err2 := impl.RequestModeChange(client, profile) if err2 != nil { fmt.Fprintf(os.Stderr, "Failed to request mode change, err: %s\n", err2) + fmt.Fprintf(os.Stdout, "%s.miflora.%s.failed 1 %d\n", prefix, id, time.Now().Unix()) return } } @@ -49,6 +45,7 @@ func readData(client ble.Client, profile *ble.Profile) { sensorData, err3 := impl.RequestSensorData(client, profile) if err3 != nil { fmt.Fprintf(os.Stderr, "Failed to request sensor data, err: %s\n", err3) + fmt.Fprintf(os.Stdout, "%s.miflora.%s.failed 1 %d\n", prefix, id, time.Now().Unix()) return } fmt.Fprintf(os.Stdout, "%s.miflora.%s.temperature %.1f %d\n", prefix, id, sensorData.Temperature, time.Now().Unix()) @@ -64,27 +61,47 @@ func main() { os.Exit(1) } + prefix := flag.Args()[0] + id := common.MifloraGetAlphaNumericID(flag.Args()[1]) + { device, err := dev.NewDevice("default") if err != nil { fmt.Fprintf(os.Stderr, "Failed to open device, err: %s\n", err) + fmt.Fprintf(os.Stdout, "%s.miflora.%s.failed 1 %d\n", prefix, id, time.Now().Unix()) os.Exit(1) } ble.SetDefaultDevice(device) } + // only way to get back the found advertisement, must be buffered! + foundAdvertisementChannel := make(chan ble.Advertisement, 1) + filter := func(adv ble.Advertisement) bool { - return strings.ToUpper(adv.Addr().String()) == strings.ToUpper(flag.Args()[1]) + if strings.ToUpper(adv.Addr().String()) == strings.ToUpper(flag.Args()[1]) { + foundAdvertisementChannel <- adv + return true + } + return false } + timeConnectStart := time.Now() + fmt.Fprintln(os.Stderr, "Scanning...") ctx := ble.WithSigHandler(context.WithTimeout(context.Background(), discoveryTimeout)) client, err := ble.Connect(ctx, filter) if err != nil { fmt.Fprintf(os.Stderr, "Failed to connect to %s, err: %s\n", flag.Args()[1], err) + fmt.Fprintf(os.Stdout, "%s.miflora.%s.failed 1 %d\n", prefix, id, time.Now().Unix()) os.Exit(1) } + timeConnectTook := time.Since(timeConnectStart).Seconds() + fmt.Fprintf(os.Stdout, "%s.miflora.%s.connect_time %.2f %d\n", prefix, id, timeConnectTook, time.Now().Unix()) + + foundAdvertisement := <-foundAdvertisementChannel + fmt.Fprintf(os.Stdout, "%s.miflora.%s.rssi %d %d\n", prefix, id, foundAdvertisement.RSSI(), time.Now().Unix()) + // Source: https://github.com/go-ble/ble/blob/master/examples/basic/explorer/main.go#L53 // Make sure we had the chance to print out the message. done := make(chan struct{}) @@ -99,14 +116,20 @@ func main() { fmt.Fprintln(os.Stderr, "Connected") + timeReadoutStart := time.Now() + profile, err := client.DiscoverProfile(true) if err != nil { fmt.Fprintf(os.Stderr, "Failed to discover profile, err: %s\n", err) + fmt.Fprintf(os.Stdout, "%s.miflora.%s.failed 1 %d\n", prefix, id, time.Now().Unix()) os.Exit(1) } readData(client, profile) + timeReadoutTook := time.Since(timeReadoutStart).Seconds() + fmt.Fprintf(os.Stdout, "%s.miflora.%s.readout_time %.2f %d\n", prefix, id, timeReadoutTook, time.Now().Unix()) + fmt.Fprintln(os.Stderr, "Connection done") client.CancelConnection() diff --git a/common/datatypes.go b/common/datatypes.go index 737b1d8..dfc536d 100644 --- a/common/datatypes.go +++ b/common/datatypes.go @@ -6,11 +6,13 @@ import ( "strings" ) +// captures response when reading meta data of miflora device type VersionBatteryResponse struct { BatteryLevel uint8 // in percent 0-100 FirmwareVersion string // as "x.y.z" } +// captures response when reading sensor data of miflora device type SensorDataResponse struct { Temperature float64 // in degree C Brightness uint32 // in lux @@ -18,8 +20,8 @@ type SensorDataResponse struct { Conductivity uint16 // in µS/cm } +// turns firmware version "2.3.4" into 20304 func (res VersionBatteryResponse) NumericFirmwareVersion() int { - // turns "2.3.4" into 20304 version := 0 parts := strings.Split(res.FirmwareVersion, ".") for i, part := range parts { @@ -31,3 +33,8 @@ func (res VersionBatteryResponse) NumericFirmwareVersion() int { } return version } + +// for the newer models a magic number must be written before we can read the current data +func (res VersionBatteryResponse) RequiresModeChangeBeforeRead() bool { + return res.FirmwareVersion >= "2.6.6" +} diff --git a/common/misc.go b/common/misc.go new file mode 100644 index 0000000..9996988 --- /dev/null +++ b/common/misc.go @@ -0,0 +1,14 @@ +package common + +import ( + "regexp" + "strings" +) + +const peripheralAddressAllowedChars = "[^a-z0-9]+" + +var peripheralAddressAllowedCharsPattern = regexp.MustCompile(peripheralAddressAllowedChars) + +func MifloraGetAlphaNumericID(peripheralAddress string) string { + return peripheralAddressAllowedCharsPattern.ReplaceAllString(strings.ToLower(peripheralAddress), "") +}