forked from influxdata/telegraf
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add input plugin for KNX home automation bus (influxdata#7048)
- Loading branch information
Showing
9 changed files
with
432 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# KNX input plugin | ||
|
||
The KNX input plugin that listens for messages on the KNX home-automation bus. | ||
This plugin connects to the KNX bus via a KNX-IP interface. | ||
Information about supported KNX message datapoint types can be found at the | ||
underlying "knx-go" project site (https://github.com/vapourismo/knx-go). | ||
|
||
### Configuration | ||
|
||
This is a sample config for the plugin. | ||
|
||
```toml | ||
# Listener capable of handling KNX bus messages provided through a KNX-IP Interface. | ||
[[inputs.KNXListener]] | ||
## Type of KNX-IP interface. | ||
## Can be either "tunnel" or "router". | ||
# service_type = "tunnel" | ||
|
||
## Address of the KNX-IP interface. | ||
service_address = "localhost:3671" | ||
|
||
## Measurement definition(s) | ||
# [[inputs.KNXListener.measurement]] | ||
# ## Name of the measurement | ||
# name = "temperature" | ||
# ## Datapoint-Type (DPT) of the KNX messages | ||
# dpt = "9.001" | ||
# ## List of Group-Addresses (GAs) assigned to the measurement | ||
# addresses = ["5/5/1"] | ||
|
||
# [[inputs.KNXListener.measurement]] | ||
# name = "illumination" | ||
# dpt = "9.004" | ||
# addresses = ["5/5/3"] | ||
``` | ||
|
||
#### Measurement configurations | ||
|
||
Each measurement contains only one datapoint-type (DPT) and assigns a list of | ||
addresses to this measurement. You can, for example group all temperature sensor | ||
messages within a "temperature" measurement. However, you are free to split | ||
messages of one datapoint-type to multiple measurements. | ||
|
||
**NOTE: You should not assign a group-address (GA) to multiple measurements!** | ||
|
||
### Metrics | ||
|
||
Received KNX data is stored in the named measurement as configured above using | ||
the "value" field. Additional to the value, there are the following tags added | ||
to the datapoint: | ||
- "groupaddress": KNX group-address corresponding to the value | ||
- "unit": unit of the value | ||
- "source": KNX physical address sending the value | ||
|
||
To find out about the datatype of the datapoint please check your KNX project, | ||
the KNX-specification or the "knx-go" project for the corresponding DPT. | ||
|
||
### Example Output | ||
|
||
This section shows example output in Line Protocol format. | ||
|
||
``` | ||
illumination,groupaddress=5/5/4,host=Hugin,source=1.1.12,unit=lux value=17.889999389648438 1582132674999013274 | ||
temperature,groupaddress=5/5/1,host=Hugin,source=1.1.8,unit=°C value=17.799999237060547 1582132663427587361 | ||
windowopen,groupaddress=1/0/1,host=Hugin,source=1.1.3 value=true 1582132630425581320 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package knx_listener | ||
|
||
import ( | ||
"github.com/vapourismo/knx-go/knx" | ||
) | ||
|
||
type KNXDummyInterface struct { | ||
inbound chan knx.GroupEvent | ||
} | ||
|
||
func NewDummyInterface() (di KNXDummyInterface, err error) { | ||
di, err = KNXDummyInterface{}, nil | ||
di.inbound = make(chan knx.GroupEvent) | ||
|
||
return di, err | ||
} | ||
|
||
func (di *KNXDummyInterface) Send(event knx.GroupEvent) { | ||
di.inbound <- event | ||
} | ||
|
||
func (di *KNXDummyInterface) Inbound() <-chan knx.GroupEvent { | ||
return di.inbound | ||
} | ||
|
||
func (di *KNXDummyInterface) Close() { | ||
close(di.inbound) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
package knx_listener | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
"sync" | ||
|
||
"github.com/vapourismo/knx-go/knx" | ||
"github.com/vapourismo/knx-go/knx/dpt" | ||
|
||
"github.com/influxdata/telegraf" | ||
"github.com/influxdata/telegraf/plugins/inputs" | ||
) | ||
|
||
type KNXInterface interface { | ||
Inbound() <-chan knx.GroupEvent | ||
Close() | ||
} | ||
|
||
type addressTarget struct { | ||
measurement string | ||
datapoint dpt.DatapointValue | ||
} | ||
|
||
type Measurement struct { | ||
Name string | ||
Dpt string | ||
Addresses []string | ||
} | ||
|
||
type KNXListener struct { | ||
ServiceType string `toml:"service_type"` | ||
ServiceAddress string `toml:"service_address"` | ||
Measurements []Measurement `toml:"measurement"` | ||
Log telegraf.Logger `toml:"-"` | ||
|
||
client KNXInterface | ||
gaTargetMap map[string]addressTarget | ||
gaLogbook map[string]bool | ||
|
||
acc telegraf.Accumulator | ||
wg sync.WaitGroup | ||
} | ||
|
||
func (kl *KNXListener) Description() string { | ||
return "Listener capable of handling KNX bus messages provided through a KNX-IP Interface." | ||
} | ||
|
||
func (kl *KNXListener) SampleConfig() string { | ||
return ` | ||
## Type of KNX-IP interface. | ||
## Can be either "tunnel" or "router". | ||
# service_type = "tunnel" | ||
## Address of the KNX-IP interface. | ||
service_address = "localhost:3671" | ||
## Measurement definition(s) | ||
# [[inputs.KNXListener.measurement]] | ||
# ## Name of the measurement | ||
# name = "temperature" | ||
# ## Datapoint-Type (DPT) of the KNX messages | ||
# dpt = "9.001" | ||
# ## List of Group-Addresses (GAs) assigned to the measurement | ||
# addresses = ["5/5/1"] | ||
# [[inputs.KNXListener.measurement]] | ||
# name = "illumination" | ||
# dpt = "9.004" | ||
# addresses = ["5/5/3"] | ||
` | ||
} | ||
|
||
func (kl *KNXListener) Gather(_ telegraf.Accumulator) error { | ||
return nil | ||
} | ||
|
||
func (kl *KNXListener) Start(acc telegraf.Accumulator) error { | ||
// Store the accumulator for later use | ||
kl.acc = acc | ||
|
||
// Setup a logbook to track unknown GAs to avoid log-spamming | ||
kl.gaLogbook = make(map[string]bool) | ||
|
||
// Construct the mapping of Group-addresses (GAs) to DPTs and the name | ||
// of the measurement | ||
kl.gaTargetMap = make(map[string]addressTarget) | ||
for _, m := range kl.Measurements { | ||
kl.Log.Debugf("Group-address mapping for measurement %q:", m.Name) | ||
for _, ga := range m.Addresses { | ||
kl.Log.Debugf(" %s --> %s", ga, m.Dpt) | ||
if _, ok := kl.gaTargetMap[ga]; ok { | ||
return fmt.Errorf("duplicate specification of address %q", ga) | ||
} | ||
d, ok := dpt.Produce(m.Dpt) | ||
if !ok { | ||
return fmt.Errorf("cannot create datapoint-type %q for address %q", m.Dpt, ga) | ||
} | ||
kl.gaTargetMap[ga] = addressTarget{m.Name, d} | ||
} | ||
} | ||
|
||
// Connect to the KNX-IP interface | ||
kl.Log.Infof("Trying to connect to %q at %q", kl.ServiceType, kl.ServiceAddress) | ||
switch kl.ServiceType { | ||
case "tunnel": | ||
c, err := knx.NewGroupTunnel(kl.ServiceAddress, knx.DefaultTunnelConfig) | ||
if err != nil { | ||
return err | ||
} | ||
kl.client = &c | ||
case "router": | ||
c, err := knx.NewGroupRouter(kl.ServiceAddress, knx.DefaultRouterConfig) | ||
if err != nil { | ||
return err | ||
} | ||
kl.client = &c | ||
case "dummy": | ||
c, err := NewDummyInterface() | ||
if err != nil { | ||
return err | ||
} | ||
kl.client = &c | ||
default: | ||
return fmt.Errorf("invalid interface type: %s", kl.ServiceAddress) | ||
} | ||
kl.Log.Infof("Connected!") | ||
|
||
// Listen to the KNX bus | ||
kl.wg.Add(1) | ||
go func() { | ||
kl.wg.Done() | ||
kl.listen() | ||
}() | ||
|
||
return nil | ||
} | ||
|
||
func (kl *KNXListener) Stop() { | ||
if kl.client != nil { | ||
kl.client.Close() | ||
kl.wg.Wait() | ||
} | ||
} | ||
|
||
func (kl *KNXListener) listen() { | ||
for msg := range kl.client.Inbound() { | ||
// Match GA to DataPointType and measurement name | ||
ga := msg.Destination.String() | ||
target, ok := kl.gaTargetMap[ga] | ||
if !ok && !kl.gaLogbook[ga] { | ||
kl.Log.Infof("Ignoring message %+v for unknown GA %q", msg, ga) | ||
kl.gaLogbook[ga] = true | ||
continue | ||
} | ||
|
||
// Extract the value from the data-frame | ||
err := target.datapoint.Unpack(msg.Data) | ||
if err != nil { | ||
kl.Log.Errorf("Unpacking data failed: %v", err) | ||
continue | ||
} | ||
kl.Log.Debugf("Matched GA %q to measurement %q with value %v", ga, target.measurement, target.datapoint) | ||
|
||
// Convert the DatapointValue interface back to its basic type again | ||
// as otherwise telegraf will not push out the metrics and eat it | ||
// silently. | ||
var value interface{} | ||
vi := reflect.Indirect(reflect.ValueOf(target.datapoint)) | ||
switch vi.Kind() { | ||
case reflect.Bool: | ||
value = vi.Bool() | ||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||
value = vi.Int() | ||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | ||
value = vi.Uint() | ||
case reflect.Float32, reflect.Float64: | ||
value = vi.Float() | ||
default: | ||
kl.Log.Errorf("Type conversion %v failed for address %q", vi.Kind(), ga) | ||
continue | ||
} | ||
|
||
// Compose the actual data to be pushed out | ||
fields := map[string]interface{}{"value": value} | ||
tags := map[string]string{ | ||
"groupaddress": ga, | ||
"unit": target.datapoint.(dpt.DatapointMeta).Unit(), | ||
"source": msg.Source.String(), | ||
} | ||
kl.acc.AddFields(target.measurement, fields, tags) | ||
} | ||
} | ||
|
||
func init() { | ||
inputs.Add("KNXListener", func() telegraf.Input { return &KNXListener{ServiceType: "tunnel"} }) | ||
} |
Oops, something went wrong.