110 lines
2.9 KiB
Go
110 lines
2.9 KiB
Go
package mqtt
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/influxdata/telegraf"
|
|
"github.com/influxdata/telegraf/internal"
|
|
)
|
|
|
|
var idRe = regexp.MustCompile(`([^a-z0-9]+)`)
|
|
|
|
func (m *MQTT) collectHomieDeviceMessages(topic string, metric telegraf.Metric) ([]message, string, error) {
|
|
var messages []message
|
|
|
|
// Check if the device-id is already registered
|
|
if _, found := m.homieSeen[topic]; !found {
|
|
deviceName, err := homieGenerate(m.homieDeviceNameGenerator, metric)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("generating device name failed: %w", err)
|
|
}
|
|
messages = append(messages,
|
|
message{topic + "/$homie", []byte("4.0")},
|
|
message{topic + "/$name", []byte(deviceName)},
|
|
message{topic + "/$state", []byte("ready")},
|
|
)
|
|
m.homieSeen[topic] = make(map[string]bool)
|
|
}
|
|
|
|
// Generate the node-ID from the metric and fixup invalid characters
|
|
nodeName, err := homieGenerate(m.homieNodeIDGenerator, metric)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("generating device ID failed: %w", err)
|
|
}
|
|
nodeID := normalizeID(nodeName)
|
|
|
|
if !m.homieSeen[topic][nodeID] {
|
|
m.homieSeen[topic][nodeID] = true
|
|
nodeIDs := make([]string, 0, len(m.homieSeen[topic]))
|
|
for id := range m.homieSeen[topic] {
|
|
nodeIDs = append(nodeIDs, id)
|
|
}
|
|
sort.Strings(nodeIDs)
|
|
messages = append(messages,
|
|
message{topic + "/$nodes", []byte(strings.Join(nodeIDs, ","))},
|
|
message{topic + "/" + nodeID + "/$name", []byte(nodeName)},
|
|
)
|
|
}
|
|
|
|
properties := make([]string, 0, len(metric.TagList())+len(metric.FieldList()))
|
|
for _, tag := range metric.TagList() {
|
|
properties = append(properties, normalizeID(tag.Key))
|
|
}
|
|
for _, field := range metric.FieldList() {
|
|
properties = append(properties, normalizeID(field.Key))
|
|
}
|
|
sort.Strings(properties)
|
|
|
|
messages = append(messages, message{
|
|
topic + "/" + nodeID + "/$properties",
|
|
[]byte(strings.Join(properties, ",")),
|
|
})
|
|
|
|
return messages, nodeID, nil
|
|
}
|
|
|
|
func normalizeID(raw string) string {
|
|
// IDs in Home can only contain lowercase letters and hyphens
|
|
// see https://homieiot.github.io/specification/#topic-ids
|
|
id := strings.ToLower(raw)
|
|
id = idRe.ReplaceAllString(id, "-")
|
|
return strings.Trim(id, "-")
|
|
}
|
|
|
|
func convertType(value interface{}) (val, dtype string, err error) {
|
|
v, err := internal.ToString(value)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
switch value.(type) {
|
|
case int8, int16, int32, int64, uint8, uint16, uint32, uint64:
|
|
return v, "integer", nil
|
|
case float32, float64:
|
|
return v, "float", nil
|
|
case []byte, string, fmt.Stringer:
|
|
return v, "string", nil
|
|
case bool:
|
|
return v, "boolean", nil
|
|
}
|
|
return "", "", fmt.Errorf("unknown type %T", value)
|
|
}
|
|
|
|
func homieGenerate(t *template.Template, m telegraf.Metric) (string, error) {
|
|
var b strings.Builder
|
|
if err := t.Execute(&b, m.(telegraf.TemplateMetric)); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
result := b.String()
|
|
if strings.Contains(result, "/") {
|
|
return "", errors.New("cannot contain /")
|
|
}
|
|
|
|
return result, nil
|
|
}
|