Add Fireboard Input Plugin (#6052)

This commit is contained in:
Lance O'Connor 2019-07-22 15:11:34 -07:00 committed by Daniel Nelson
parent 109d1e1e15
commit e098758d78
4 changed files with 294 additions and 0 deletions

View File

@ -42,6 +42,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/file"
_ "github.com/influxdata/telegraf/plugins/inputs/filecount"
_ "github.com/influxdata/telegraf/plugins/inputs/filestat"
_ "github.com/influxdata/telegraf/plugins/inputs/fireboard"
_ "github.com/influxdata/telegraf/plugins/inputs/fluentd"
_ "github.com/influxdata/telegraf/plugins/inputs/github"
_ "github.com/influxdata/telegraf/plugins/inputs/graylog"

View File

@ -0,0 +1,62 @@
# Fireboard Input Plugin
The fireboard plugin gathers the real time temperature data from fireboard
thermometers. In order to use this input plugin, you'll need to sign up
to use their REST API, you can find more information on their website
here [https://docs.fireboard.io/reference/restapi.html]
### Configuration
This section contains the default TOML to configure the plugin. You can
generate it using `telegraf --usage <plugin-name>`.
```toml
[[inputs.fireboard]]
## Specify auth token for your account
auth_token = "invalidAuthToken"
## You can override the fireboard server URL if necessary
# url = https://fireboard.io/api/v1/devices.json
## You can set a different http_timeout if you need to
# http_timeout = 4
```
#### auth_token
In lieu of requiring a username and password, this plugin requires the
authToken that you can generate using the Fireboard REST API as described
in their docs [https://docs.fireboard.io/reference/restapi.html#Authentication]
#### url
While there should be no reason to override the URL, the option is available
in case Fireboard changes their site, etc.
#### http_timeout
If you need to increase the HTTP timeout, you can do so here. You can set this
value in seconds. The default value is four (4) seconds.
### Metrics
The Fireboard REST API docs have good examples of the data that is available,
currently this input only returns the real time temperatures. Temperature
values are included if they are less than a minute old.
- fireboard
- tags:
- channel
- scale (Celcius; Farenheit)
- title (name of the Fireboard)
- uuid (UUID of the Fireboard)
- fields:
- temperature (float, unit)
### Example Output
This section shows example output in Line Protocol format. You can often use
`telegraf --input-filter <plugin-name> --test` or use the `file` output to get
this information.
```
fireboard,channel=2,host=patas-mbp,scale=Farenheit,title=telegraf-FireBoard,uuid=b55e766c-b308-49b5-93a4-df89fe31efd0 temperature=78.2 1561690040000000000
```

View File

@ -0,0 +1,157 @@
package fireboard
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs"
)
// Fireboard gathers statistics from the fireboard.io servers
type Fireboard struct {
AuthToken string `toml:"auth_token"`
URL string `toml:"url"`
HTTPTimeout internal.Duration `toml:"http_timeout"`
client *http.Client
}
// NewFireboard return a new instance of Fireboard with a default http client
func NewFireboard() *Fireboard {
tr := &http.Transport{ResponseHeaderTimeout: time.Duration(3 * time.Second)}
client := &http.Client{
Transport: tr,
Timeout: time.Duration(4 * time.Second),
}
return &Fireboard{client: client}
}
// RTT fireboardStats represents the data that is received from Fireboard
type RTT struct {
Temp float64 `json:"temp"`
Channel int64 `json:"channel"`
Degreetype int `json:"degreetype"`
Created string `json:"created"`
}
type fireboardStats struct {
Title string `json:"title"`
UUID string `json:"uuid"`
Latesttemps []RTT `json:"latest_temps"`
}
// A sample configuration to only gather stats from localhost, default port.
const sampleConfig = `
## Specify auth token for your account
auth_token = "invalidAuthToken"
## You can override the fireboard server URL if necessary
# url = https://fireboard.io/api/v1/devices.json
## You can set a different http_timeout if you need to
## You should set a string using an number and time indicator
## for example "12s" for 12 seconds.
# http_timeout = "4s"
`
// SampleConfig Returns a sample configuration for the plugin
func (r *Fireboard) SampleConfig() string {
return sampleConfig
}
// Description Returns a description of the plugin
func (r *Fireboard) Description() string {
return "Read real time temps from fireboard.io servers"
}
// Init the things
func (r *Fireboard) Init() error {
if len(r.AuthToken) == 0 {
return fmt.Errorf("You must specify an authToken")
}
if len(r.URL) == 0 {
r.URL = "https://fireboard.io/api/v1/devices.json"
}
// Have a default timeout of 4s
if r.HTTPTimeout.Duration == 0 {
r.HTTPTimeout.Duration = time.Second * 4
}
r.client.Timeout = r.HTTPTimeout.Duration
return nil
}
// Gather Reads stats from all configured servers.
func (r *Fireboard) Gather(acc telegraf.Accumulator) error {
// Perform the GET request to the fireboard servers
req, err := http.NewRequest("GET", r.URL, nil)
if err != nil {
return err
}
req.Header.Set("Authorization", "Token "+r.AuthToken)
resp, err := r.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// Successful responses will always return status code 200
if resp.StatusCode != http.StatusOK {
if resp.StatusCode == http.StatusForbidden {
return fmt.Errorf("fireboard server responded with %d [Forbidden], verify your authToken", resp.StatusCode)
}
return fmt.Errorf("fireboard responded with unexepcted status code %d", resp.StatusCode)
}
// Decode the response JSON into a new stats struct
var stats []fireboardStats
if err := json.NewDecoder(resp.Body).Decode(&stats); err != nil {
return fmt.Errorf("unable to decode fireboard response: %s", err)
}
// Range over all devices, gathering stats. Returns early in case of any error.
for _, s := range stats {
r.gatherTemps(s, acc)
}
return nil
}
// Return text description of degree type (scale)
func scale(n int) string {
switch n {
case 1:
return "Celcius"
case 2:
return "Fahrenheit"
default:
return ""
}
}
// Gathers stats from a single device, adding them to the accumulator
func (r *Fireboard) gatherTemps(s fireboardStats, acc telegraf.Accumulator) {
// Construct lookup for scale values
for _, t := range s.Latesttemps {
tags := map[string]string{
"title": s.Title,
"uuid": s.UUID,
"channel": strconv.FormatInt(t.Channel, 10),
"scale": scale(t.Degreetype),
}
fields := map[string]interface{}{
"temperature": t.Temp,
}
acc.AddFields("fireboard", fields, tags)
}
}
func init() {
inputs.Add("fireboard", func() telegraf.Input {
return NewFireboard()
})
}

View File

@ -0,0 +1,74 @@
package fireboard
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"testing"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/require"
)
func TestFireboard(t *testing.T) {
// Create a test server with the const response JSON
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, response)
}))
defer ts.Close()
// Parse the URL of the test server, used to verify the expected host
u, err := url.Parse(ts.URL)
require.NoError(t, err)
// Create a new fb instance with our given test server
fireboard := NewFireboard()
fireboard.AuthToken = "b4bb6e6a7b6231acb9f71b304edb2274693d8849"
fireboard.URL = u.String()
// Create a test accumulator
acc := &testutil.Accumulator{}
// Gather data from the test server
err = fireboard.Gather(acc)
require.NoError(t, err)
// Expect the correct values for all known keys
expectFields := map[string]interface{}{
"temperature": float64(79.9),
}
// Expect the correct values for all tags
expectTags := map[string]string{
"title": "telegraf-FireBoard",
"uuid": "b55e766c-b308-49b5-93a4-df89fe31efd0",
"channel": strconv.FormatInt(1, 10),
"scale": "Fahrenheit",
}
acc.AssertContainsTaggedFields(t, "fireboard", expectFields, expectTags)
}
var response = `
[{
"id": 99999,
"title": "telegraf-FireBoard",
"created": "2019-03-23T16:48:32.152010Z",
"uuid": "b55e766c-b308-49b5-93a4-df89fe31efd0",
"hardware_id": "XXXXXXXXX",
"latest_temps": [
{
"temp": 79.9,
"channel": 1,
"degreetype": 2,
"created": "2019-06-25T06:07:10Z"
}
],
"last_templog": "2019-06-25T06:06:40Z",
"model": "FBX11E",
"channel_count": 6,
"degreetype": 2
}]
`