2018-04-11 01:04:59 +00:00
|
|
|
package fibaro
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
2019-05-07 21:15:30 +00:00
|
|
|
"time"
|
2018-04-11 01:04:59 +00:00
|
|
|
|
|
|
|
"github.com/influxdata/telegraf"
|
|
|
|
"github.com/influxdata/telegraf/internal"
|
|
|
|
"github.com/influxdata/telegraf/plugins/inputs"
|
|
|
|
)
|
|
|
|
|
2019-05-07 21:15:30 +00:00
|
|
|
const defaultTimeout = 5 * time.Second
|
|
|
|
|
2018-04-11 01:04:59 +00:00
|
|
|
const sampleConfig = `
|
|
|
|
## Required Fibaro controller address/hostname.
|
|
|
|
## Note: at the time of writing this plugin, Fibaro only implemented http - no https available
|
|
|
|
url = "http://<controller>:80"
|
|
|
|
|
|
|
|
## Required credentials to access the API (http://<controller/api/<component>)
|
|
|
|
username = "<username>"
|
|
|
|
password = "<password>"
|
|
|
|
|
|
|
|
## Amount of time allowed to complete the HTTP request
|
|
|
|
# timeout = "5s"
|
|
|
|
`
|
|
|
|
|
|
|
|
const description = "Read devices value(s) from a Fibaro controller"
|
|
|
|
|
|
|
|
// Fibaro contains connection information
|
|
|
|
type Fibaro struct {
|
2019-05-07 21:15:30 +00:00
|
|
|
URL string `toml:"url"`
|
2018-04-11 01:04:59 +00:00
|
|
|
|
|
|
|
// HTTP Basic Auth Credentials
|
2019-05-07 21:15:30 +00:00
|
|
|
Username string `toml:"username"`
|
|
|
|
Password string `toml:"password"`
|
2018-04-11 01:04:59 +00:00
|
|
|
|
2019-05-07 21:15:30 +00:00
|
|
|
Timeout internal.Duration `toml:"timeout"`
|
2018-04-11 01:04:59 +00:00
|
|
|
|
|
|
|
client *http.Client
|
|
|
|
}
|
|
|
|
|
|
|
|
// LinkRoomsSections links rooms to sections
|
|
|
|
type LinkRoomsSections struct {
|
|
|
|
Name string
|
|
|
|
SectionID uint16
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sections contains sections informations
|
|
|
|
type Sections struct {
|
|
|
|
ID uint16 `json:"id"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rooms contains rooms informations
|
|
|
|
type Rooms struct {
|
|
|
|
ID uint16 `json:"id"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
SectionID uint16 `json:"sectionID"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Devices contains devices informations
|
|
|
|
type Devices struct {
|
|
|
|
ID uint16 `json:"id"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
RoomID uint16 `json:"roomID"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
Enabled bool `json:"enabled"`
|
|
|
|
Properties struct {
|
2020-04-20 14:58:50 +00:00
|
|
|
BatteryLevel interface{} `json:"batteryLevel"`
|
|
|
|
Dead interface{} `json:"dead"`
|
|
|
|
Energy interface{} `json:"energy"`
|
|
|
|
Power interface{} `json:"power"`
|
|
|
|
Value interface{} `json:"value"`
|
|
|
|
Value2 interface{} `json:"value2"`
|
2018-04-11 01:04:59 +00:00
|
|
|
} `json:"properties"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Description returns a string explaining the purpose of this plugin
|
|
|
|
func (f *Fibaro) Description() string { return description }
|
|
|
|
|
|
|
|
// SampleConfig returns text explaining how plugin should be configured
|
|
|
|
func (f *Fibaro) SampleConfig() string { return sampleConfig }
|
|
|
|
|
|
|
|
// getJSON connects, authenticates and reads JSON payload returned by Fibaro box
|
|
|
|
func (f *Fibaro) getJSON(path string, dataStruct interface{}) error {
|
|
|
|
var requestURL = f.URL + path
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", requestURL, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.SetBasicAuth(f.Username, f.Password)
|
|
|
|
resp, err := f.client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-02-20 02:46:08 +00:00
|
|
|
defer resp.Body.Close()
|
2018-04-11 01:04:59 +00:00
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
err = fmt.Errorf("Response from url \"%s\" has status code %d (%s), expected %d (%s)",
|
|
|
|
requestURL,
|
|
|
|
resp.StatusCode,
|
|
|
|
http.StatusText(resp.StatusCode),
|
|
|
|
http.StatusOK,
|
|
|
|
http.StatusText(http.StatusOK))
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
dec := json.NewDecoder(resp.Body)
|
|
|
|
err = dec.Decode(&dataStruct)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Gather fetches all required information to output metrics
|
|
|
|
func (f *Fibaro) Gather(acc telegraf.Accumulator) error {
|
|
|
|
|
|
|
|
if f.client == nil {
|
|
|
|
f.client = &http.Client{
|
|
|
|
Transport: &http.Transport{
|
|
|
|
Proxy: http.ProxyFromEnvironment,
|
|
|
|
},
|
|
|
|
Timeout: f.Timeout.Duration,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var tmpSections []Sections
|
|
|
|
err := f.getJSON("/api/sections", &tmpSections)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
sections := map[uint16]string{}
|
|
|
|
for _, v := range tmpSections {
|
|
|
|
sections[v.ID] = v.Name
|
|
|
|
}
|
|
|
|
|
|
|
|
var tmpRooms []Rooms
|
|
|
|
err = f.getJSON("/api/rooms", &tmpRooms)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
rooms := map[uint16]LinkRoomsSections{}
|
|
|
|
for _, v := range tmpRooms {
|
|
|
|
rooms[v.ID] = LinkRoomsSections{Name: v.Name, SectionID: v.SectionID}
|
|
|
|
}
|
|
|
|
|
|
|
|
var devices []Devices
|
|
|
|
err = f.getJSON("/api/devices", &devices)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, device := range devices {
|
|
|
|
// skip device in some cases
|
|
|
|
if device.RoomID == 0 ||
|
|
|
|
device.Enabled == false ||
|
|
|
|
device.Properties.Dead == "true" ||
|
|
|
|
device.Type == "com.fibaro.zwaveDevice" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
tags := map[string]string{
|
2018-07-02 18:57:05 +00:00
|
|
|
"deviceId": strconv.FormatUint(uint64(device.ID), 10),
|
|
|
|
"section": sections[rooms[device.RoomID].SectionID],
|
|
|
|
"room": rooms[device.RoomID].Name,
|
|
|
|
"name": device.Name,
|
|
|
|
"type": device.Type,
|
2018-04-11 01:04:59 +00:00
|
|
|
}
|
|
|
|
fields := make(map[string]interface{})
|
|
|
|
|
2020-04-20 14:58:50 +00:00
|
|
|
if device.Properties.BatteryLevel != nil {
|
|
|
|
if fValue, err := strconv.ParseFloat(device.Properties.BatteryLevel.(string), 64); err == nil {
|
|
|
|
fields["batteryLevel"] = fValue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-02 18:57:05 +00:00
|
|
|
if device.Properties.Energy != nil {
|
|
|
|
if fValue, err := strconv.ParseFloat(device.Properties.Energy.(string), 64); err == nil {
|
|
|
|
fields["energy"] = fValue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if device.Properties.Power != nil {
|
|
|
|
if fValue, err := strconv.ParseFloat(device.Properties.Power.(string), 64); err == nil {
|
|
|
|
fields["power"] = fValue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-11 01:04:59 +00:00
|
|
|
if device.Properties.Value != nil {
|
|
|
|
value := device.Properties.Value
|
|
|
|
switch value {
|
|
|
|
case "true":
|
|
|
|
value = "1"
|
|
|
|
case "false":
|
|
|
|
value = "0"
|
|
|
|
}
|
|
|
|
|
|
|
|
if fValue, err := strconv.ParseFloat(value.(string), 64); err == nil {
|
|
|
|
fields["value"] = fValue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if device.Properties.Value2 != nil {
|
|
|
|
if fValue, err := strconv.ParseFloat(device.Properties.Value2.(string), 64); err == nil {
|
|
|
|
fields["value2"] = fValue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
acc.AddFields("fibaro", fields, tags)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
inputs.Add("fibaro", func() telegraf.Input {
|
2019-05-07 21:15:30 +00:00
|
|
|
return &Fibaro{
|
|
|
|
Timeout: internal.Duration{Duration: defaultTimeout},
|
|
|
|
}
|
2018-04-11 01:04:59 +00:00
|
|
|
})
|
|
|
|
}
|