feat(inputs.fibaro): Support HC3 device types (#13754)
This commit is contained in:
parent
5fb290fb46
commit
ca2295e1a4
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -4,131 +4,16 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const sectionsJSON = `
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Section 1",
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Section 2",
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Section 3",
|
||||
"sortOrder": 3
|
||||
}
|
||||
]`
|
||||
|
||||
const roomsJSON = `
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Room 1",
|
||||
"sectionID": 1,
|
||||
"icon": "room_1",
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Room 2",
|
||||
"sectionID": 2,
|
||||
"icon": "room_2",
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Room 3",
|
||||
"sectionID": 3,
|
||||
"icon": "room_3",
|
||||
"sortOrder": 3
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Room 4",
|
||||
"sectionID": 3,
|
||||
"icon": "room_4",
|
||||
"sortOrder": 4
|
||||
}
|
||||
]`
|
||||
|
||||
const devicesJSON = `
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Device 1",
|
||||
"roomID": 1,
|
||||
"type": "com.fibaro.binarySwitch",
|
||||
"enabled": true,
|
||||
"properties": {
|
||||
"dead": "false",
|
||||
"value": "false"
|
||||
},
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Device 2",
|
||||
"roomID": 2,
|
||||
"type": "com.fibaro.binarySwitch",
|
||||
"enabled": true,
|
||||
"properties": {
|
||||
"dead": "false",
|
||||
"value": "true"
|
||||
},
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Device 3",
|
||||
"roomID": 3,
|
||||
"type": "com.fibaro.multilevelSwitch",
|
||||
"enabled": true,
|
||||
"properties": {
|
||||
"dead": "false",
|
||||
"value": "67"
|
||||
},
|
||||
"sortOrder": 3
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Device 4",
|
||||
"roomID": 4,
|
||||
"type": "com.fibaro.temperatureSensor",
|
||||
"enabled": true,
|
||||
"properties": {
|
||||
"batteryLevel": "100",
|
||||
"dead": "false",
|
||||
"value": "22.80"
|
||||
},
|
||||
"sortOrder": 4
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Device 5",
|
||||
"roomID": 4,
|
||||
"type": "com.fibaro.FGRM222",
|
||||
"enabled": true,
|
||||
"properties": {
|
||||
"energy": "4.33",
|
||||
"power": "0.7",
|
||||
"dead": "false",
|
||||
"value": "50",
|
||||
"value2": "75"
|
||||
},
|
||||
"sortOrder": 5
|
||||
}
|
||||
]`
|
||||
|
||||
// TestUnauthorized validates that 401 (wrong credentials) is managed properly
|
||||
func TestUnauthorized(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -142,6 +27,7 @@ func TestUnauthorized(t *testing.T) {
|
|||
Password: "pass",
|
||||
client: &http.Client{},
|
||||
}
|
||||
require.NoError(t, a.Init())
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := acc.GatherError(a.Gather)
|
||||
|
|
@ -154,11 +40,17 @@ func TestJSONSuccess(t *testing.T) {
|
|||
payload := ""
|
||||
switch r.URL.Path {
|
||||
case "/api/sections":
|
||||
payload = sectionsJSON
|
||||
content, err := os.ReadFile(path.Join("testdata", "sections.json"))
|
||||
require.NoError(t, err)
|
||||
payload = string(content)
|
||||
case "/api/rooms":
|
||||
payload = roomsJSON
|
||||
content, err := os.ReadFile(path.Join("testdata", "rooms.json"))
|
||||
require.NoError(t, err)
|
||||
payload = string(content)
|
||||
case "/api/devices":
|
||||
payload = devicesJSON
|
||||
content, err := os.ReadFile(path.Join("testdata", "device_hc2.json"))
|
||||
require.NoError(t, err)
|
||||
payload = string(content)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := fmt.Fprintln(w, payload)
|
||||
|
|
@ -172,36 +64,212 @@ func TestJSONSuccess(t *testing.T) {
|
|||
Password: "pass",
|
||||
client: &http.Client{},
|
||||
}
|
||||
require.NoError(t, a.Init())
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := acc.GatherError(a.Gather)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Gather should add 5 metrics
|
||||
require.Equal(t, uint64(5), acc.NMetrics())
|
||||
|
||||
// Ensure fields / values are correct - Device 1
|
||||
tags := map[string]string{"deviceId": "1", "section": "Section 1", "room": "Room 1", "name": "Device 1", "type": "com.fibaro.binarySwitch"}
|
||||
fields := map[string]interface{}{"value": float64(0)}
|
||||
acc.AssertContainsTaggedFields(t, "fibaro", fields, tags)
|
||||
expected := []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"fibaro",
|
||||
map[string]string{
|
||||
"deviceId": "1",
|
||||
"section": "Section 1",
|
||||
"room": "Room 1",
|
||||
"name": "Device 1",
|
||||
"type": "com.fibaro.binarySwitch",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"value": float64(0),
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
testutil.MustMetric(
|
||||
"fibaro",
|
||||
map[string]string{
|
||||
"deviceId": "2",
|
||||
"section": "Section 2",
|
||||
"room": "Room 2",
|
||||
"name": "Device 2",
|
||||
"type": "com.fibaro.binarySwitch",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"value": float64(1),
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
testutil.MustMetric(
|
||||
"fibaro",
|
||||
map[string]string{
|
||||
"deviceId": "3",
|
||||
"section": "Section 3",
|
||||
"room": "Room 3",
|
||||
"name": "Device 3",
|
||||
"type": "com.fibaro.multilevelSwitch",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"value": float64(67),
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
testutil.MustMetric(
|
||||
"fibaro",
|
||||
map[string]string{
|
||||
"deviceId": "4",
|
||||
"section": "Section 3",
|
||||
"room": "Room 4",
|
||||
"name": "Device 4",
|
||||
"type": "com.fibaro.temperatureSensor",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"batteryLevel": float64(100),
|
||||
"value": float64(22.8),
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
testutil.MustMetric(
|
||||
"fibaro",
|
||||
map[string]string{
|
||||
"deviceId": "5",
|
||||
"section": "Section 3",
|
||||
"room": "Room 4",
|
||||
"name": "Device 5",
|
||||
"type": "com.fibaro.FGRM222",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"energy": float64(4.33),
|
||||
"power": float64(0.7),
|
||||
"value": float64(50),
|
||||
"value2": float64(75),
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
}
|
||||
|
||||
// Ensure fields / values are correct - Device 2
|
||||
tags = map[string]string{"deviceId": "2", "section": "Section 2", "room": "Room 2", "name": "Device 2", "type": "com.fibaro.binarySwitch"}
|
||||
fields = map[string]interface{}{"value": float64(1)}
|
||||
acc.AssertContainsTaggedFields(t, "fibaro", fields, tags)
|
||||
|
||||
// Ensure fields / values are correct - Device 3
|
||||
tags = map[string]string{"deviceId": "3", "section": "Section 3", "room": "Room 3", "name": "Device 3", "type": "com.fibaro.multilevelSwitch"}
|
||||
fields = map[string]interface{}{"value": float64(67)}
|
||||
acc.AssertContainsTaggedFields(t, "fibaro", fields, tags)
|
||||
|
||||
// Ensure fields / values are correct - Device 4
|
||||
tags = map[string]string{"deviceId": "4", "section": "Section 3", "room": "Room 4", "name": "Device 4", "type": "com.fibaro.temperatureSensor"}
|
||||
fields = map[string]interface{}{"batteryLevel": float64(100), "value": float64(22.8)}
|
||||
acc.AssertContainsTaggedFields(t, "fibaro", fields, tags)
|
||||
|
||||
// Ensure fields / values are correct - Device 5
|
||||
tags = map[string]string{"deviceId": "5", "section": "Section 3", "room": "Room 4", "name": "Device 5", "type": "com.fibaro.FGRM222"}
|
||||
fields = map[string]interface{}{"energy": float64(4.33), "power": float64(0.7), "value": float64(50), "value2": float64(75)}
|
||||
acc.AssertContainsTaggedFields(t, "fibaro", fields, tags)
|
||||
testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
|
||||
}
|
||||
|
||||
func TestHC3JSON(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
payload := ""
|
||||
switch r.URL.Path {
|
||||
case "/api/sections":
|
||||
content, err := os.ReadFile(path.Join("testdata", "sections.json"))
|
||||
require.NoError(t, err)
|
||||
payload = string(content)
|
||||
case "/api/rooms":
|
||||
content, err := os.ReadFile(path.Join("testdata", "rooms.json"))
|
||||
require.NoError(t, err)
|
||||
payload = string(content)
|
||||
case "/api/devices":
|
||||
content, err := os.ReadFile(path.Join("testdata", "device_hc3.json"))
|
||||
require.NoError(t, err)
|
||||
payload = string(content)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := fmt.Fprintln(w, payload)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
a := Fibaro{
|
||||
URL: ts.URL,
|
||||
Username: "user",
|
||||
Password: "pass",
|
||||
DeviceType: "HC3",
|
||||
client: &http.Client{},
|
||||
}
|
||||
require.NoError(t, a.Init())
|
||||
|
||||
var acc testutil.Accumulator
|
||||
err := acc.GatherError(a.Gather)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(5), acc.NMetrics())
|
||||
|
||||
expected := []telegraf.Metric{
|
||||
testutil.MustMetric(
|
||||
"fibaro",
|
||||
map[string]string{
|
||||
"deviceId": "1",
|
||||
"section": "Section 1",
|
||||
"room": "Room 1",
|
||||
"name": "Device 1",
|
||||
"type": "com.fibaro.binarySwitch",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"value": float64(0),
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
testutil.MustMetric(
|
||||
"fibaro",
|
||||
map[string]string{
|
||||
"deviceId": "2",
|
||||
"section": "Section 2",
|
||||
"room": "Room 2",
|
||||
"name": "Device 2",
|
||||
"type": "com.fibaro.binarySwitch",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"value": float64(1),
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
testutil.MustMetric(
|
||||
"fibaro",
|
||||
map[string]string{
|
||||
"deviceId": "3",
|
||||
"section": "Section 3",
|
||||
"room": "Room 3",
|
||||
"name": "Device 3",
|
||||
"type": "com.fibaro.multilevelSwitch",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"value": float64(67),
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
testutil.MustMetric(
|
||||
"fibaro",
|
||||
map[string]string{
|
||||
"deviceId": "4",
|
||||
"section": "Section 3",
|
||||
"room": "Room 4",
|
||||
"name": "Device 4",
|
||||
"type": "com.fibaro.temperatureSensor",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"batteryLevel": float64(100),
|
||||
"value": float64(22.8),
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
testutil.MustMetric(
|
||||
"fibaro",
|
||||
map[string]string{
|
||||
"deviceId": "5",
|
||||
"section": "Section 3",
|
||||
"room": "Room 4",
|
||||
"name": "Device 5",
|
||||
"type": "com.fibaro.FGRM222",
|
||||
},
|
||||
map[string]interface{}{
|
||||
"energy": float64(4.33),
|
||||
"power": float64(0.7),
|
||||
"value": float64(34),
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
),
|
||||
}
|
||||
|
||||
testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
|
||||
}
|
||||
|
||||
func TestInvalidDeviceType(t *testing.T) {
|
||||
a := Fibaro{
|
||||
DeviceType: "foobar",
|
||||
}
|
||||
require.Error(t, a.Init())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
package hc2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
)
|
||||
|
||||
func Parse(acc telegraf.Accumulator, sectionBytes []byte, roomBytes []byte, deviecsBytes []byte) error {
|
||||
var tmpSections []Sections
|
||||
if err := json.Unmarshal(sectionBytes, &tmpSections); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sections := map[uint16]string{}
|
||||
for _, v := range tmpSections {
|
||||
sections[v.ID] = v.Name
|
||||
}
|
||||
|
||||
var tmpRooms []Rooms
|
||||
if err := json.Unmarshal(roomBytes, &tmpRooms); 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
|
||||
if err := json.Unmarshal(deviecsBytes, &devices); 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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package hc2
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package hc3
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
)
|
||||
|
||||
func Parse(acc telegraf.Accumulator, sectionBytes []byte, roomBytes []byte, deviecsBytes []byte) error {
|
||||
var tmpSections []Sections
|
||||
if err := json.Unmarshal(sectionBytes, &tmpSections); err != nil {
|
||||
return err
|
||||
}
|
||||
sections := make(map[uint16]string, len(tmpSections))
|
||||
for _, v := range tmpSections {
|
||||
sections[v.ID] = v.Name
|
||||
}
|
||||
|
||||
var tmpRooms []Rooms
|
||||
if err := json.Unmarshal(roomBytes, &tmpRooms); err != nil {
|
||||
return err
|
||||
}
|
||||
rooms := make(map[uint16]linkRoomsSections, len(tmpRooms))
|
||||
for _, v := range tmpRooms {
|
||||
rooms[v.ID] = linkRoomsSections{Name: v.Name, SectionID: v.SectionID}
|
||||
}
|
||||
|
||||
var devices []Devices
|
||||
if err := json.Unmarshal(deviecsBytes, &devices); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, device := range devices {
|
||||
// skip device in some cases
|
||||
if device.RoomID == 0 ||
|
||||
!device.Enabled ||
|
||||
device.Properties.Dead ||
|
||||
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 {
|
||||
fields["batteryLevel"] = *device.Properties.BatteryLevel
|
||||
}
|
||||
|
||||
if device.Properties.Energy != nil {
|
||||
fields["energy"] = *device.Properties.Energy
|
||||
}
|
||||
|
||||
if device.Properties.Power != nil {
|
||||
fields["power"] = *device.Properties.Power
|
||||
}
|
||||
|
||||
// Value can be a JSON bool, string, or numeric value
|
||||
if device.Properties.Value != nil {
|
||||
v, err := internal.ToFloat64(device.Properties.Value)
|
||||
if err != nil {
|
||||
acc.AddError(fmt.Errorf("unable to convert value: %w", err))
|
||||
} else {
|
||||
fields["value"] = v
|
||||
}
|
||||
}
|
||||
|
||||
acc.AddFields("fibaro", fields, tags)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package hc3
|
||||
|
||||
// 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 *float64 `json:"batteryLevel"`
|
||||
Dead bool `json:"dead"`
|
||||
Energy *float64 `json:"energy"`
|
||||
Power *float64 `json:"power"`
|
||||
Value interface{} `json:"value"`
|
||||
Value2 *string `json:"value2"`
|
||||
} `json:"properties"`
|
||||
}
|
||||
|
|
@ -10,3 +10,8 @@
|
|||
|
||||
## 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"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Device 1",
|
||||
"roomID": 1,
|
||||
"type": "com.fibaro.binarySwitch",
|
||||
"enabled": true,
|
||||
"properties": {
|
||||
"dead": "false",
|
||||
"value": "false"
|
||||
},
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Device 2",
|
||||
"roomID": 2,
|
||||
"type": "com.fibaro.binarySwitch",
|
||||
"enabled": true,
|
||||
"properties": {
|
||||
"dead": "false",
|
||||
"value": "true"
|
||||
},
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Device 3",
|
||||
"roomID": 3,
|
||||
"type": "com.fibaro.multilevelSwitch",
|
||||
"enabled": true,
|
||||
"properties": {
|
||||
"dead": "false",
|
||||
"value": "67"
|
||||
},
|
||||
"sortOrder": 3
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Device 4",
|
||||
"roomID": 4,
|
||||
"type": "com.fibaro.temperatureSensor",
|
||||
"enabled": true,
|
||||
"properties": {
|
||||
"batteryLevel": "100",
|
||||
"dead": "false",
|
||||
"value": "22.80"
|
||||
},
|
||||
"sortOrder": 4
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Device 5",
|
||||
"roomID": 4,
|
||||
"type": "com.fibaro.FGRM222",
|
||||
"enabled": true,
|
||||
"properties": {
|
||||
"energy": "4.33",
|
||||
"power": "0.7",
|
||||
"dead": "false",
|
||||
"value": "50",
|
||||
"value2": "75"
|
||||
},
|
||||
"sortOrder": 5
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Device 1",
|
||||
"roomID": 1,
|
||||
"type": "com.fibaro.binarySwitch",
|
||||
"enabled": true,
|
||||
"properties": {
|
||||
"dead": false,
|
||||
"value": false
|
||||
},
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Device 2",
|
||||
"roomID": 2,
|
||||
"type": "com.fibaro.binarySwitch",
|
||||
"enabled": true,
|
||||
"properties": {
|
||||
"dead": false,
|
||||
"value": true
|
||||
},
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Device 3",
|
||||
"roomID": 3,
|
||||
"type": "com.fibaro.multilevelSwitch",
|
||||
"enabled": true,
|
||||
"properties": {
|
||||
"dead": false,
|
||||
"value": "67"
|
||||
},
|
||||
"sortOrder": 3
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Device 4",
|
||||
"roomID": 4,
|
||||
"type": "com.fibaro.temperatureSensor",
|
||||
"enabled": true,
|
||||
"properties": {
|
||||
"batteryLevel": 100,
|
||||
"dead": false,
|
||||
"value": 22.80
|
||||
},
|
||||
"sortOrder": 4
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Device 5",
|
||||
"roomID": 4,
|
||||
"type": "com.fibaro.FGRM222",
|
||||
"enabled": true,
|
||||
"properties": {
|
||||
"energy": 4.33,
|
||||
"power": 0.7,
|
||||
"dead": false,
|
||||
"value": 34
|
||||
},
|
||||
"sortOrder": 5
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Room 1",
|
||||
"sectionID": 1,
|
||||
"icon": "room_1",
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Room 2",
|
||||
"sectionID": 2,
|
||||
"icon": "room_2",
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Room 3",
|
||||
"sectionID": 3,
|
||||
"icon": "room_3",
|
||||
"sortOrder": 3
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Room 4",
|
||||
"sectionID": 3,
|
||||
"icon": "room_4",
|
||||
"sortOrder": 4
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Section 1",
|
||||
"sortOrder": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Section 2",
|
||||
"sortOrder": 2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Section 3",
|
||||
"sortOrder": 3
|
||||
}
|
||||
]
|
||||
Loading…
Reference in New Issue