Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(inputs.fibaro): Support HC3 device types #13754

Merged
merged 5 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions plugins/inputs/fibaro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ The Fibaro plugin makes HTTP calls to the Fibaro controller API to gather values
of hooked devices. Those values could be true (1) or false (0) for switches,
percentage for dimmers, temperature, etc.

By default, this plugin supports HC2 devices. To support HC3 devices, please
use the device type config option.

## Global configuration options <!-- @/docs/includes/plugin_config.md -->

In addition to the plugin-specific configuration settings, plugins support
Expand All @@ -28,6 +31,11 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.

## Amount of time allowed to complete the HTTP request
# timeout = "5s"

## Fibaro Device Type
## By default, this plugin will attempt to read using the HC2 API. For HC3
## devices, set this to "HC3"
# device_type = "HC2"
```

## Metrics
Expand Down
159 changes: 34 additions & 125 deletions plugins/inputs/fibaro/fibaro.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ package fibaro

import (
_ "embed"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"time"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/plugins/inputs"
"github.com/influxdata/telegraf/plugins/inputs/fibaro/hc2"
"github.com/influxdata/telegraf/plugins/inputs/fibaro/hc3"
)

//go:embed sample.conf
Expand All @@ -21,66 +22,28 @@ const defaultTimeout = 5 * time.Second

// Fibaro contains connection information
type Fibaro struct {
URL string `toml:"url"`

// HTTP Basic Auth Credentials
Username string `toml:"username"`
Password string `toml:"password"`

Timeout config.Duration `toml:"timeout"`
URL string `toml:"url"`
Username string `toml:"username"`
Password string `toml:"password"`
Timeout config.Duration `toml:"timeout"`
DeviceType string `toml:"device_type"`

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 {
BatteryLevel *string `json:"batteryLevel"`
Dead string `json:"dead"`
Energy *string `json:"energy"`
Power *string `json:"power"`
Value interface{} `json:"value"`
Value2 *string `json:"value2"`
} `json:"properties"`
}

// getJSON connects, authenticates and reads JSON payload returned by Fibaro box
func (f *Fibaro) getJSON(path string, dataStruct interface{}) error {
func (f *Fibaro) getJSON(path string) ([]byte, error) {
var requestURL = f.URL + path

req, err := http.NewRequest("GET", requestURL, nil)
if err != nil {
return err
return nil, err
}

req.SetBasicAuth(f.Username, f.Password)
resp, err := f.client.Do(req)
if err != nil {
return err
return nil, err
}
defer resp.Body.Close()

Expand All @@ -91,13 +54,24 @@ func (f *Fibaro) getJSON(path string, dataStruct interface{}) error {
http.StatusText(resp.StatusCode),
http.StatusOK,
http.StatusText(http.StatusOK))
return err
return nil, err
}

dec := json.NewDecoder(resp.Body)
err = dec.Decode(&dataStruct)
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return err
return nil, fmt.Errorf("unable to read response body: %w", err)
}

return bodyBytes, nil
}

func (f *Fibaro) Init() error {
switch f.DeviceType {
case "":
f.DeviceType = "HC2"
case "HC2", "HC3":
default:
return fmt.Errorf("invalid option for device type")
}

return nil
Expand All @@ -118,89 +92,24 @@ func (f *Fibaro) Gather(acc telegraf.Accumulator) error {
}
}

var tmpSections []Sections
err := f.getJSON("/api/sections", &tmpSections)
sections, err := f.getJSON("/api/sections")
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)
rooms, err := f.getJSON("/api/rooms")
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)
devices, err := f.getJSON("/api/devices")
if err != nil {
return err
}

for _, device := range devices {
// skip device in some cases
if device.RoomID == 0 ||
!device.Enabled ||
device.Properties.Dead == "true" ||
device.Type == "com.fibaro.zwaveDevice" {
continue
}

tags := map[string]string{
"deviceId": strconv.FormatUint(uint64(device.ID), 10),
"section": sections[rooms[device.RoomID].SectionID],
"room": rooms[device.RoomID].Name,
"name": device.Name,
"type": device.Type,
}
fields := make(map[string]interface{})

if device.Properties.BatteryLevel != nil {
if fValue, err := strconv.ParseFloat(*device.Properties.BatteryLevel, 64); err == nil {
fields["batteryLevel"] = fValue
}
}

if device.Properties.Energy != nil {
if fValue, err := strconv.ParseFloat(*device.Properties.Energy, 64); err == nil {
fields["energy"] = fValue
}
}

if device.Properties.Power != nil {
if fValue, err := strconv.ParseFloat(*device.Properties.Power, 64); err == nil {
fields["power"] = fValue
}
}

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, 64); err == nil {
fields["value2"] = fValue
}
}

acc.AddFields("fibaro", fields, tags)
switch f.DeviceType {
case "HC2":
return hc2.Parse(acc, sections, rooms, devices)
case "HC3":
return hc3.Parse(acc, sections, rooms, devices)
}

return nil
Expand Down
Loading