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

This commit is contained in:
Joshua Powers 2023-08-28 14:47:08 -06:00 committed by GitHub
parent 5fb290fb46
commit ca2295e1a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 689 additions and 272 deletions

View File

@ -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

View File

@ -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
@ -22,65 +23,27 @@ 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"`
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

View File

@ -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())
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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"`
}

View File

@ -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"

View File

@ -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
}
]

View File

@ -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
}
]

View File

@ -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
}
]

View File

@ -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
}
]