diff --git a/plugins/inputs/all/neoom_beaam.go b/plugins/inputs/all/neoom_beaam.go new file mode 100644 index 000000000..6592391b4 --- /dev/null +++ b/plugins/inputs/all/neoom_beaam.go @@ -0,0 +1,5 @@ +//go:build !custom || inputs || inputs.neoom_beaam + +package all + +import _ "github.com/influxdata/telegraf/plugins/inputs/neoom_beaam" // register plugin diff --git a/plugins/inputs/neoom_beaam/README.md b/plugins/inputs/neoom_beaam/README.md new file mode 100644 index 000000000..058d087bb --- /dev/null +++ b/plugins/inputs/neoom_beaam/README.md @@ -0,0 +1,121 @@ +# Neoom Beaam Input Plugin + +This plugin reads metrics from the Neoom Beaam gateway using the +[Beaam API][BeaamAPI]. Usually an access token is required which can be created +in the Neoom web interface. Please follow the [developer instructions][DevPage] +to create the token! + +[BeaamAPI]: https://developer.neoom.com/reference/concepts-terms-1 +[DevPage]:https://neoom.com/developers + +## Global configuration options + +In addition to the plugin-specific configuration settings, plugins support +additional global and plugin configuration settings. These settings are used to +modify metrics, tags, and field or create aliases and configure ordering, etc. +See the [CONFIGURATION.md][CONFIGURATION.md] for more details. + +[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins + +## Secret-store support + +This plugin supports secrets from secret-stores for the `token` option. +See the [secret-store documentation][SECRETSTORE] for more details on how +to use them. + +[SECRETSTORE]: ../../../docs/CONFIGURATION.md#secret-store-secrets + +## Configuration + +```toml @sample.conf +# Read energy data from the local Neoom Beaam gateway +[[inputs.neoom_beaam]] + ## Address of the gateway + # address = "https://10.10.10.10" + + ## Bearer token for accessing the data + # token = "" + + ## Enforce refreshing the site configuration in each gather cycle + # refresh_configuration = false + + ## Optional TLS Config + ## Set to true/false to enforce TLS being enabled/disabled. If not set, + ## enable TLS only if any of the other options are specified. + # tls_enable = + ## Trusted root certificates for server + # tls_ca = "/path/to/cafile" + ## Used for TLS client certificate authentication + # tls_cert = "/path/to/certfile" + ## Used for TLS client certificate authentication + # tls_key = "/path/to/keyfile" + ## Password for the key file if it is encrypted + # tls_key_pwd = "" + ## Send the specified TLS server name via SNI + # tls_server_name = "kubernetes.example.com" + ## Minimal TLS version to accept by the client + # tls_min_version = "TLS12" + ## List of ciphers to accept, by default all secure ciphers will be accepted + ## See https://pkg.go.dev/crypto/tls#pkg-constants for supported values. + ## Use "all", "secure" and "insecure" to add all support ciphers, secure + ## suites or insecure suites respectively. + # tls_cipher_suites = ["secure"] + ## Renegotiation method, "never", "once" or "freely" + # tls_renegotiation_method = "never" + ## Use TLS but skip chain & host verification + # insecure_skip_verify = false +``` + +## Example Output + +For energy flow metrics: + +```text +neoom_beaam_energy_flow,datapoint=SELF_SUFFICIENCY,source=127.0.0.1,unit=% value=100 1723883145024999936 +neoom_beaam_energy_flow,datapoint=POWER_PRODUCTION,source=127.0.0.1,unit=W value=1184 1723883150001999872 +neoom_beaam_energy_flow,datapoint=POWER_GRID,source=127.0.0.1,unit=W value=15 1723883150001999872 +neoom_beaam_energy_flow,datapoint=POWER_STORAGE,source=127.0.0.1,unit=W value=3504 1723883150001999872 +neoom_beaam_energy_flow,datapoint=ENERGY_PRODUCED,source=127.0.0.1,unit=Wh value=639300 1723882905007000064 +neoom_beaam_energy_flow,datapoint=ENERGY_IMPORTED,source=127.0.0.1,unit=Wh value=22696.926152777138 1723883150001999872 +neoom_beaam_energy_flow,datapoint=ENERGY_EXPORTED,source=127.0.0.1,unit=Wh value=237421.33112499933 1723883135003000064 +neoom_beaam_energy_flow,datapoint=ENERGY_CHARGED,source=127.0.0.1,unit=Wh value=215200 1723882765001999872 +neoom_beaam_energy_flow,datapoint=ENERGY_DISCHARGED,source=127.0.0.1,unit=Wh value=144800 1723880490004999936 +neoom_beaam_energy_flow,datapoint=STATE_OF_CHARGE,source=127.0.0.1,unit=% value=59 1723883090001999872 +neoom_beaam_energy_flow,datapoint=POWER_CONSUMPTION_CALC,source=127.0.0.1,unit=W value=4703 1723883150001999872 +neoom_beaam_energy_flow,datapoint=ENERGY_CONSUMED_CALC,source=127.0.0.1,unit=Wh value=354175.59502777783 1723883150001999872 +``` + +for thing (aka device) metrics + +```text +neoom_beaam_thing,datapoint=CONNECTION,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=None value=true 1723890960060000000 +neoom_beaam_thing,datapoint=VOLTAGE_P1,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=V value=245 1723905135056000000 +neoom_beaam_thing,datapoint=VOLTAGE_P2,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=V value=242.3 1723905135056000000 +neoom_beaam_thing,datapoint=VOLTAGE_P3,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=V value=245.5 1723905135056000000 +neoom_beaam_thing,datapoint=FREQUENCY,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=Hz value=49.98 1723905135056000000 +neoom_beaam_thing,datapoint=ACTIVE_POWER,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=W value=-826 1723905135056000000 +neoom_beaam_thing,datapoint=OUTPUT_ENERGY,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=Wh value=241826.44736444377 1723905135075000064 +neoom_beaam_thing,datapoint=INPUT_ENERGY,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=Wh value=22988.396324999278 1723904090096000000 +neoom_beaam_thing,datapoint=POWER_P1,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=W value=-305 1723905135056000000 +neoom_beaam_thing,datapoint=POWER_P2,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=W value=-268 1723905135056000000 +neoom_beaam_thing,datapoint=POWER_P3,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=W value=-214 1723905135056000000 +``` + +## Metrics + +- neoom_beaam_energy_flow + - tags: + - source (address of gateway) + - datapoint (name of the datapoint) + - unit (unit of the measurement) + - fields: + - value (value of the datapoint) + +- neoom_beaam_thing + - tags: + - source (address of gateway) + - thing (name of the device) + - datapoint (name of the datapoint) + - unit (unit of the measurement) + - fields: + - value (value of the datapoint) diff --git a/plugins/inputs/neoom_beaam/neoom_beaam.go b/plugins/inputs/neoom_beaam/neoom_beaam.go new file mode 100644 index 000000000..44752c949 --- /dev/null +++ b/plugins/inputs/neoom_beaam/neoom_beaam.go @@ -0,0 +1,310 @@ +//go:generate ../../../tools/config_includer/generator +//go:generate ../../../tools/readme_config_includer/generator +package neoom_beaam + +import ( + "context" + _ "embed" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/config" + "github.com/influxdata/telegraf/internal" + chttp "github.com/influxdata/telegraf/plugins/common/http" + "github.com/influxdata/telegraf/plugins/inputs" +) + +//go:embed sample.conf +var sampleConfig string + +type NeoomBeaam struct { + Address string `toml:"address"` + Token config.Secret `toml:"token"` + RefreshConfig bool `toml:"refresh_configuration"` + Log telegraf.Logger `toml:"-"` + chttp.HTTPClientConfig + + source string + config site + client *http.Client +} + +func (*NeoomBeaam) SampleConfig() string { + return sampleConfig +} + +func (n *NeoomBeaam) Init() error { + if n.Address == "" { + n.Address = "https://10.10.10.10" + } + n.Address = strings.TrimRight(n.Address, "/") + + u, err := url.Parse(n.Address) + if err != nil { + return fmt.Errorf("parsing of address %q failed: %w", n.Address, err) + } + n.source = u.Hostname() + + return nil +} + +func (n *NeoomBeaam) Start(telegraf.Accumulator) error { + // Create the client + ctx := context.Background() + client, err := n.HTTPClientConfig.CreateClient(ctx, n.Log) + if err != nil { + return fmt.Errorf("creating client failed: %w", err) + } + n.client = client + + // Initialize configuration + return n.updateConfiguration() +} + +func (n *NeoomBeaam) Stop() { + if n.client != nil { + n.client.CloseIdleConnections() + } +} + +func (n *NeoomBeaam) Gather(acc telegraf.Accumulator) error { + // Refresh the config if requested + if n.RefreshConfig { + if err := n.updateConfiguration(); err != nil { + return err + } + } + + // Query the energy flow + if err := n.queryEnergyFlow(acc); err != nil { + acc.AddError(fmt.Errorf("querying site state failed: %w", err)) + } + + // Query all known things + for _, thing := range n.config.Things { + if err := n.queryThing(acc, thing); err != nil { + acc.AddError(fmt.Errorf("querying thing %q (%s) failed: %w", thing.Name, thing.id, err)) + } + } + + return nil +} + +func (n *NeoomBeaam) updateConfiguration() error { + endpoint := n.Address + "/api/v1/site/configuration" + request, err := http.NewRequest("GET", endpoint, nil) + if err != nil { + return fmt.Errorf("creating configuration request failed: %w", err) + } + + if !n.Token.Empty() { + token, err := n.Token.Get() + if err != nil { + return fmt.Errorf("getting token failed: %w", err) + } + bearer := "Bearer " + strings.TrimSpace(token.String()) + token.Destroy() + request.Header.Set("Authorization", bearer) + } + request.Header.Set("Accept", "application/json") + + // Update the configuration + response, err := n.client.Do(request) + if err != nil { + return &internal.StartupError{ + Err: fmt.Errorf("querying configuration failed: %w", err), + Retry: true, + } + } + defer response.Body.Close() + + body, err := io.ReadAll(response.Body) + if err != nil { + return fmt.Errorf("reading body failed: %w", err) + } + + if response.StatusCode != http.StatusOK { + return &internal.StartupError{ + Err: fmt.Errorf("configuration query returned %q: %s", response.Status, string(body)), + Retry: 400 <= response.StatusCode && response.StatusCode <= 499, + } + } + + if err := json.Unmarshal(body, &n.config); err != nil { + return fmt.Errorf("decoding configuration failed: %w", err) + } + + for id, thing := range n.config.Things { + thing.id = id + n.config.Things[id] = thing + } + + return nil +} + +func (n *NeoomBeaam) queryEnergyFlow(acc telegraf.Accumulator) error { + // Create the request + endpoint := n.Address + "/api/v1/site/state" + request, err := http.NewRequest("GET", endpoint, nil) + if err != nil { + return fmt.Errorf("creating request failed: %w", err) + } + + if !n.Token.Empty() { + token, err := n.Token.Get() + if err != nil { + return fmt.Errorf("getting token failed: %w", err) + } + bearer := "Bearer " + strings.TrimSpace(token.String()) + token.Destroy() + request.Header.Set("Authorization", bearer) + } + request.Header.Set("Accept", "application/json") + + // Execute query + response, err := n.client.Do(request) + if err != nil { + return fmt.Errorf("querying failed: %w", err) + } + defer response.Body.Close() + + // Handle response + body, err := io.ReadAll(response.Body) + if err != nil { + return fmt.Errorf("reading body failed: %w", err) + } + + if response.StatusCode != http.StatusOK { + return fmt.Errorf("query returned %q: %s", response.Status, string(body)) + } + + // Decode the data and create metric + var data siteState + if err := json.Unmarshal(body, &data); err != nil { + return fmt.Errorf("decoding failed: %w", err) + } + + for _, s := range data.EnergyFlow.States { + if s.Value == nil { + n.Log.Debugf("omitting data point %q (%s) due to 'null' value", s.Key, s.DataPointID) + continue + } + + dp, ok := n.config.EnergyFlow.DataPoints[s.DataPointID] + if !ok { + n.Log.Errorf("no data point definition for ID %q", s.DataPointID) + continue + } + + tags := map[string]string{ + "source": n.source, + "datapoint": s.Key, + "unit": dp.Unit, + } + fields := map[string]interface{}{ + "value": s.Value, + } + ts := time.Unix(0, int64(s.Timestamp*float64(time.Millisecond))) + acc.AddFields("neoom_beaam_energy_flow", fields, tags, ts) + } + + return nil +} + +func (n *NeoomBeaam) queryThing(acc telegraf.Accumulator, thing thingDefinition) error { + // Create the request + endpoint := n.Address + "/api/v1/things/" + thing.id + "/states" + request, err := http.NewRequest("GET", endpoint, nil) + if err != nil { + return fmt.Errorf("creating request failed: %w", err) + } + + if !n.Token.Empty() { + token, err := n.Token.Get() + if err != nil { + return fmt.Errorf("getting token failed: %w", err) + } + bearer := "Bearer " + strings.TrimSpace(token.String()) + token.Destroy() + request.Header.Set("Authorization", bearer) + } + request.Header.Set("Accept", "application/json") + + // Execute query + response, err := n.client.Do(request) + if err != nil { + return fmt.Errorf("querying failed: %w", err) + } + defer response.Body.Close() + + // Handle response + body, err := io.ReadAll(response.Body) + if err != nil { + return fmt.Errorf("reading body failed: %w", err) + } + + if response.StatusCode != http.StatusOK { + return fmt.Errorf("query returned %q: %s", response.Status, string(body)) + } + + // Decode the data and create metric + var data thingState + if err := json.Unmarshal(body, &data); err != nil { + return fmt.Errorf("decoding failed: %w", err) + } + + for _, s := range data.States { + if s.Value == nil { + n.Log.Debugf("omitting data point %q (%s) due to 'null' value", s.Key, s.DataPointID) + continue + } + + dp, ok := thing.DataPoints[s.DataPointID] + if !ok { + n.Log.Errorf("no data point definition for ID %q", s.DataPointID) + continue + } + + tags := map[string]string{ + "source": n.source, + "thing": thing.Name, + "datapoint": s.Key, + "unit": dp.Unit, + } + var fields map[string]interface{} + if elements, ok := s.Value.([]interface{}); ok { + fields = make(map[string]interface{}, len(elements)) + for i, v := range elements { + fields["value_"+strconv.Itoa(i)] = v + } + } else { + fields = map[string]interface{}{ + "value": s.Value, + } + } + + ts := time.Unix(0, int64(s.Timestamp*float64(time.Millisecond))) + acc.AddFields("neoom_beaam_thing", fields, tags, ts) + } + + return nil +} + +// Register the plugin +func init() { + inputs.Add("neoom_beaam", func() telegraf.Input { + return &NeoomBeaam{ + HTTPClientConfig: chttp.HTTPClientConfig{ + Timeout: config.Duration(5 * time.Second), + ResponseHeaderTimeout: config.Duration(5 * time.Second), + }, + } + }) +} diff --git a/plugins/inputs/neoom_beaam/neoom_beaam_test.go b/plugins/inputs/neoom_beaam/neoom_beaam_test.go new file mode 100644 index 000000000..1701fc678 --- /dev/null +++ b/plugins/inputs/neoom_beaam/neoom_beaam_test.go @@ -0,0 +1,115 @@ +package neoom_beaam + +import ( + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "sort" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/config" + "github.com/influxdata/telegraf/plugins/inputs" + "github.com/influxdata/telegraf/plugins/parsers/influx" + "github.com/influxdata/telegraf/testutil" +) + +func TestCases(t *testing.T) { + // Get all directories in testdata + folders, err := os.ReadDir("testcases") + require.NoError(t, err) + + // Register the plugin + inputs.Add("neoom_beaam", func() telegraf.Input { + return &NeoomBeaam{} + }) + + // Prepare the influx parser for expectations + parser := &influx.Parser{} + require.NoError(t, parser.Init()) + + for _, f := range folders { + // Only handle folders + if !f.IsDir() { + continue + } + testcasePath := filepath.Join("testcases", f.Name()) + configFilename := filepath.Join(testcasePath, "telegraf.conf") + inputFiles := filepath.Join(testcasePath, "*.json") + expectedFilename := filepath.Join(testcasePath, "expected.out") + expectedErrorFilename := filepath.Join(testcasePath, "expected.err") + + t.Run(f.Name(), func(t *testing.T) { + // Read the input data + matches, err := filepath.Glob(inputFiles) + require.NoError(t, err) + require.NotEmpty(t, matches) + sort.Strings(matches) + endpoints := make(map[string][]byte, len(matches)) + for _, fn := range matches { + buf, err := os.ReadFile(fn) + require.NoError(t, err) + key := strings.TrimSuffix(filepath.Base(fn), filepath.Ext(fn)) + if strings.HasPrefix(key, "thing_") { + endpoints["/api/v1/things/"+strings.TrimPrefix(key, "thing_")+"/states"] = buf + } else { + endpoints["/api/v1/site/"+key] = buf + } + } + + // Read the expected output if any + var expected []telegraf.Metric + if _, err := os.Stat(expectedFilename); err == nil { + var err error + expected, err = testutil.ParseMetricsFromFile(expectedFilename, parser) + require.NoError(t, err) + } + + // Read the expected output if any + var expectedErrors []string + if _, err := os.Stat(expectedErrorFilename); err == nil { + var err error + expectedErrors, err = testutil.ParseLinesFromFile(expectedErrorFilename) + require.NoError(t, err) + require.NotEmpty(t, expectedErrors) + } + + // Create a fake API server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if msg, ok := endpoints[r.URL.Path]; ok { + if _, err := w.Write(msg); err != nil { + w.WriteHeader(http.StatusInternalServerError) + } + } else { + w.WriteHeader(http.StatusNotFound) + } + })) + defer server.Close() + + // Load the configuration + cfg := config.NewConfig() + require.NoError(t, cfg.LoadConfig(configFilename)) + require.Len(t, cfg.Inputs, 1) + + // Setup and start the plugin + plugin := cfg.Inputs[0].Input.(*NeoomBeaam) + plugin.Address = server.URL + require.NoError(t, plugin.Init()) + require.NoError(t, plugin.Start(nil)) + defer plugin.Stop() + + // Gather the data + var acc testutil.Accumulator + require.NoError(t, plugin.Gather(&acc)) + require.Empty(t, acc.Errors) + + // Check the metric nevertheless as we might get some metrics despite errors. + actual := acc.GetTelegrafMetrics() + testutil.RequireMetricsEqual(t, expected, actual, testutil.SortMetrics()) + }) + } +} diff --git a/plugins/inputs/neoom_beaam/sample.conf b/plugins/inputs/neoom_beaam/sample.conf new file mode 100644 index 000000000..426e28e4b --- /dev/null +++ b/plugins/inputs/neoom_beaam/sample.conf @@ -0,0 +1,36 @@ +# Read energy data from the local Neoom Beaam gateway +[[inputs.neoom_beaam]] + ## Address of the gateway + # address = "https://10.10.10.10" + + ## Bearer token for accessing the data + # token = "" + + ## Enforce refreshing the site configuration in each gather cycle + # refresh_configuration = false + + ## Optional TLS Config + ## Set to true/false to enforce TLS being enabled/disabled. If not set, + ## enable TLS only if any of the other options are specified. + # tls_enable = + ## Trusted root certificates for server + # tls_ca = "/path/to/cafile" + ## Used for TLS client certificate authentication + # tls_cert = "/path/to/certfile" + ## Used for TLS client certificate authentication + # tls_key = "/path/to/keyfile" + ## Password for the key file if it is encrypted + # tls_key_pwd = "" + ## Send the specified TLS server name via SNI + # tls_server_name = "kubernetes.example.com" + ## Minimal TLS version to accept by the client + # tls_min_version = "TLS12" + ## List of ciphers to accept, by default all secure ciphers will be accepted + ## See https://pkg.go.dev/crypto/tls#pkg-constants for supported values. + ## Use "all", "secure" and "insecure" to add all support ciphers, secure + ## suites or insecure suites respectively. + # tls_cipher_suites = ["secure"] + ## Renegotiation method, "never", "once" or "freely" + # tls_renegotiation_method = "never" + ## Use TLS but skip chain & host verification + # insecure_skip_verify = false diff --git a/plugins/inputs/neoom_beaam/sample.conf.in b/plugins/inputs/neoom_beaam/sample.conf.in new file mode 100644 index 000000000..f90ac5e50 --- /dev/null +++ b/plugins/inputs/neoom_beaam/sample.conf.in @@ -0,0 +1,13 @@ +# Read energy data from the local Neoom Beaam gateway +[[inputs.neoom_beaam]] + ## Address of the gateway + # address = "https://10.10.10.10" + + ## Bearer token for accessing the data + # token = "" + + ## Enforce refreshing the site configuration in each gather cycle + # refresh_configuration = false + + ## Optional TLS Config +{{template "/plugins/common/tls/client.conf"}} diff --git a/plugins/inputs/neoom_beaam/testcases/small/configuration.json b/plugins/inputs/neoom_beaam/testcases/small/configuration.json new file mode 100644 index 000000000..f99cc300c --- /dev/null +++ b/plugins/inputs/neoom_beaam/testcases/small/configuration.json @@ -0,0 +1,333 @@ +{ + "siteId": "11111111-aaaa-1111-aaaa-222222222222", + "versionTimestamp": 1721051934, + "siteInfo": { + "gridConnections": {}, + "geoCoordinates": { + "latitude": 48.488256, + "longitude": 14.4954532 + } + }, + "things": { + "22222222-bbbb-2222-bbbb-222222222222": { + "type": "INVERTER", + "dataPoints": { + "f788ab8b-96d0-5efb-bda7-5214a92418f1": { + "key": "CONNECTION", + "dataType": "BOOLEAN", + "unitOfMeasure": "None" + }, + "3d2cf2d0-9c79-583b-b915-f980d0c64817": { + "key": "GRID_FEED_IN_LIMIT", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "fb898be0-82f0-5a04-a6f4-e303754cb00c": { + "key": "MAX_POWER_GENERATION_AC", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "c0aecdd7-e7f2-52ac-a972-35909c1ac938": { + "key": "STATE_CODE", + "dataType": "STRING", + "unitOfMeasure": "None" + }, + "a8aa1122-af0b-5239-9236-73a61d435687": { + "key": "ACTIVE_POWER", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "15353bf6-cff1-5182-b299-22cc20762b29": { + "key": "REACTIVE_POWER", + "dataType": "NUMBER", + "unitOfMeasure": "VAr" + }, + "ade3343d-333e-58e5-94d6-14d54d44620d": { + "key": "INPUTS_POWER", + "dataType": "NUMBER_ARRAY[]", + "unitOfMeasure": "W" + }, + "15de38ea-2c10-5f64-a991-3a3f011c6c23": { + "key": "PRODUCED_ENERGY", + "dataType": "NUMBER", + "unitOfMeasure": "Wh" + } + } + }, + "33333333-cccc-3333-cccc-333333333333": { + "type": "BATTERY", + "dataPoints": { + "7748ed36-8535-530c-bf4d-de0f300a8adb": { + "key": "CONNECTION", + "dataType": "BOOLEAN", + "unitOfMeasure": "None" + }, + "b4d3ceeb-71f9-5677-ba4f-ac368b8dbd3b": { + "key": "EPS_MODE_ACTIVE", + "dataType": "BOOLEAN", + "unitOfMeasure": "None" + }, + "f968a39d-b988-523c-a6d7-ccd1436bfb77": { + "key": "MAX_CURRENT_CHARGE", + "dataType": "NUMBER", + "unitOfMeasure": "A" + }, + "d51f9cea-4ed0-556d-ba7c-e8f82ceaf7d0": { + "key": "MAX_CURRENT_DISCHARGE", + "dataType": "NUMBER", + "unitOfMeasure": "A" + }, + "5e0188c0-0d26-5158-b837-536a1d8c715f": { + "key": "OPERATING_MODE", + "dataType": "STRING", + "unitOfMeasure": "None" + }, + "9df68d84-f679-528c-ad41-4e041d46b6f8": { + "key": "OPERATING_MODES", + "dataType": "STRING_ARRAY[]", + "unitOfMeasure": "None" + }, + "8eeb33e5-5c2e-59fa-9d91-c9d12dc833f4": { + "key": "MIN_SOC_SELF_CONSUMPTION_OPT", + "dataType": "NUMBER", + "unitOfMeasure": "%" + }, + "f2ea90d0-ca70-5b2d-a5fe-7a62b180e987": { + "key": "MIN_SOC", + "dataType": "NUMBER", + "unitOfMeasure": "%" + }, + "d7917007-a8cd-5d8e-8e92-f80237406aad": { + "key": "TARGET_POWER", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "94c7f11d-2489-5973-8eaf-57de93426d1b": { + "key": "VOLTAGE", + "dataType": "NUMBER", + "unitOfMeasure": "V" + }, + "932f9b7f-4e10-5506-a73a-e8679c3e699f": { + "key": "CURRENT", + "dataType": "NUMBER", + "unitOfMeasure": "A" + }, + "9c64cb00-b204-5f1a-8a58-a10d4343fb12": { + "key": "POWER", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "a8725a08-f4b5-5cd3-b54f-bd1a879754c9": { + "key": "CELL_TEMPERATURE", + "dataType": "NUMBER", + "unitOfMeasure": "°C" + }, + "61740954-99e3-5bd2-a530-c0aa59501d2b": { + "key": "STATE_OF_CHARGE", + "dataType": "NUMBER", + "unitOfMeasure": "%" + }, + "899099f9-6ebe-5040-93a6-de0db44f19fb": { + "key": "ERROR_CODES", + "dataType": "STRING_ARRAY[]", + "unitOfMeasure": "None" + }, + "6187ec40-ba60-5ec4-9e8a-a4daf5288124": { + "key": "CHARGED_ENERGY", + "dataType": "NUMBER", + "unitOfMeasure": "Wh" + }, + "1e1b3efa-f957-51b6-8d35-2de5d682f554": { + "key": "DISCHARGED_ENERGY", + "dataType": "NUMBER", + "unitOfMeasure": "Wh" + }, + "a82404ae-a4a5-5a08-86ba-c487dcad55ef": { + "key": "MAX_POWER_CHARGE", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "59821d14-13c8-5363-87c1-508095feb2b7": { + "key": "MAX_POWER_DISCHARGE", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "f57323b8-4874-58a6-91f9-a9f7c5568a67": { + "key": "DESIRED_SOC", + "dataType": "NUMBER", + "unitOfMeasure": "%" + } + } + }, + "44444444-dddd-4444-dddd-444444444444": { + "type": "PV", + "dataPoints": { + "244fa23e-cdbc-5d7d-a3e9-788af3ab43d2": { + "key": "CONNECTIONS", + "dataType": "BOOLEAN_ARRAY[]", + "unitOfMeasure": "None" + }, + "d064a7fc-4b9a-5cf4-a55f-eeda5289afac": { + "key": "POWER", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "44763dab-6224-5b2a-aadf-d5afd781cdd5": { + "key": "PRODUCED_ENERGY", + "dataType": "NUMBER", + "unitOfMeasure": "Wh" + }, + "892e0390-252d-5fb5-9857-04924f1770e1": { + "key": "VOLTAGES", + "dataType": "NUMBER_ARRAY[]", + "unitOfMeasure": "V" + }, + "a153ab79-3cf2-5f17-ab40-9af3c38d7e60": { + "key": "CURRENTS", + "dataType": "NUMBER_ARRAY[]", + "unitOfMeasure": "A" + } + } + }, + "55555555-eeee-5555-eeee-555555555555": { + "type": "ELECTRICITY_METER_AC", + "dataPoints": { + "094a8ac0-914f-5f0d-a376-d2e3abfdc75e": { + "key": "CONNECTION", + "dataType": "BOOLEAN", + "unitOfMeasure": "None" + }, + "22911bdc-0b2f-5008-9d42-c551d4fa53c7": { + "key": "VOLTAGE_P1", + "dataType": "NUMBER", + "unitOfMeasure": "V" + }, + "4e34e4eb-0859-5253-81ae-9d536e75c003": { + "key": "VOLTAGE_P2", + "dataType": "NUMBER", + "unitOfMeasure": "V" + }, + "a45c0fe9-42e8-5091-9007-ff73d55a149b": { + "key": "VOLTAGE_P3", + "dataType": "NUMBER", + "unitOfMeasure": "V" + }, + "755e91d4-f6c6-5570-a2bf-623b20055816": { + "key": "FREQUENCY", + "dataType": "NUMBER", + "unitOfMeasure": "Hz" + }, + "c7b812ba-e08b-5f4a-bcd4-97616c8130bc": { + "key": "ACTIVE_POWER", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "3f4105ab-bdf8-5d0b-88ba-9859dcf3f111": { + "key": "OUTPUT_ENERGY", + "dataType": "NUMBER", + "unitOfMeasure": "Wh" + }, + "6ddbb838-0557-5df0-bdeb-489f426248fb": { + "key": "INPUT_ENERGY", + "dataType": "NUMBER", + "unitOfMeasure": "Wh" + }, + "b89fda1c-f19a-5da0-bcbd-4277eb377b3a": { + "key": "POWER_P1", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "64fd2faa-13ca-5cc4-8302-3085f13775c8": { + "key": "POWER_P2", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "eca4dacc-1858-50f1-ac46-30cddcde75a2": { + "key": "POWER_P3", + "dataType": "NUMBER", + "unitOfMeasure": "W" + } + } + } + }, + "energyFlow": { + "dataPoints": { + "27291fe9-b44a-5530-92cf-1fad478f6f66": { + "key": "SELF_SUFFICIENCY", + "dataType": "NUMBER", + "unitOfMeasure": "%" + }, + "211f490a-af77-5c33-af3f-fd2e67e40b3f": { + "key": "POWER_PRODUCTION", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "58d84d20-ba7c-5218-9045-459e4a946080": { + "key": "POWER_CONSUMPTION", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "2548caa8-0288-559c-9e75-50b964f4bf01": { + "key": "POWER_GRID", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "b9399b12-e3bb-5515-9b1a-640bd51c1c1b": { + "key": "POWER_STORAGE", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "49764da7-5486-52bf-9867-66643445d7fb": { + "key": "ENERGY_PRODUCED", + "dataType": "NUMBER", + "unitOfMeasure": "Wh" + }, + "eb0f0b1f-73cc-5cb8-864d-baa11b8628c4": { + "key": "ENERGY_CONSUMED", + "dataType": "NUMBER", + "unitOfMeasure": "Wh" + }, + "3dd29b23-eb88-538a-8220-ccd7192d4fef": { + "key": "ENERGY_IMPORTED", + "dataType": "NUMBER", + "unitOfMeasure": "Wh" + }, + "804a496b-267b-5783-850d-b43ecc1b6f9c": { + "key": "ENERGY_EXPORTED", + "dataType": "NUMBER", + "unitOfMeasure": "Wh" + }, + "25aac409-c380-575f-8429-6a33de6bec3e": { + "key": "ENERGY_CHARGED", + "dataType": "NUMBER", + "unitOfMeasure": "Wh" + }, + "42aebe59-8b2c-58fa-a222-991e88451376": { + "key": "ENERGY_DISCHARGED", + "dataType": "NUMBER", + "unitOfMeasure": "Wh" + }, + "d40db932-df55-57c9-955b-2528a4df4d73": { + "key": "STATE_OF_CHARGE", + "dataType": "NUMBER", + "unitOfMeasure": "%" + }, + "6a7fe53b-65c1-576b-8159-bd79d1fd7651": { + "key": "POWER_CONSUMPTION_CALC", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "adf97bab-6a8f-543a-8dab-a7459cc977e7": { + "key": "POWER_GRID_REMAINING", + "dataType": "NUMBER", + "unitOfMeasure": "W" + }, + "df4668c6-6d3f-5837-a7c7-e7aaa408f068": { + "key": "ENERGY_CONSUMED_CALC", + "dataType": "NUMBER", + "unitOfMeasure": "Wh" + } + } + }, + "externalPlantControls": {} +} \ No newline at end of file diff --git a/plugins/inputs/neoom_beaam/testcases/small/expected.out b/plugins/inputs/neoom_beaam/testcases/small/expected.out new file mode 100644 index 000000000..15da5b377 --- /dev/null +++ b/plugins/inputs/neoom_beaam/testcases/small/expected.out @@ -0,0 +1,28 @@ +neoom_beaam_energy_flow,datapoint=SELF_SUFFICIENCY,source=127.0.0.1,unit=% value=100 1723883145024999936 +neoom_beaam_energy_flow,datapoint=POWER_PRODUCTION,source=127.0.0.1,unit=W value=1184 1723883150001999872 +neoom_beaam_energy_flow,datapoint=POWER_GRID,source=127.0.0.1,unit=W value=15 1723883150001999872 +neoom_beaam_energy_flow,datapoint=POWER_STORAGE,source=127.0.0.1,unit=W value=3504 1723883150001999872 +neoom_beaam_energy_flow,datapoint=ENERGY_PRODUCED,source=127.0.0.1,unit=Wh value=639300 1723882905007000064 +neoom_beaam_energy_flow,datapoint=ENERGY_IMPORTED,source=127.0.0.1,unit=Wh value=22696.926152777138 1723883150001999872 +neoom_beaam_energy_flow,datapoint=ENERGY_EXPORTED,source=127.0.0.1,unit=Wh value=237421.33112499933 1723883135003000064 +neoom_beaam_energy_flow,datapoint=ENERGY_CHARGED,source=127.0.0.1,unit=Wh value=215200 1723882765001999872 +neoom_beaam_energy_flow,datapoint=ENERGY_DISCHARGED,source=127.0.0.1,unit=Wh value=144800 1723880490004999936 +neoom_beaam_energy_flow,datapoint=STATE_OF_CHARGE,source=127.0.0.1,unit=% value=59 1723883090001999872 +neoom_beaam_energy_flow,datapoint=POWER_CONSUMPTION_CALC,source=127.0.0.1,unit=W value=4703 1723883150001999872 +neoom_beaam_energy_flow,datapoint=ENERGY_CONSUMED_CALC,source=127.0.0.1,unit=Wh value=354175.59502777783 1723883150001999872 +neoom_beaam_thing,datapoint=CONNECTION,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=None value=true 1723890960060000000 +neoom_beaam_thing,datapoint=VOLTAGE_P1,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=V value=245 1723905135056000000 +neoom_beaam_thing,datapoint=VOLTAGE_P2,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=V value=242.3 1723905135056000000 +neoom_beaam_thing,datapoint=VOLTAGE_P3,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=V value=245.5 1723905135056000000 +neoom_beaam_thing,datapoint=FREQUENCY,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=Hz value=49.98 1723905135056000000 +neoom_beaam_thing,datapoint=ACTIVE_POWER,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=W value=-826 1723905135056000000 +neoom_beaam_thing,datapoint=OUTPUT_ENERGY,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=Wh value=241826.44736444377 1723905135075000064 +neoom_beaam_thing,datapoint=INPUT_ENERGY,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=Wh value=22988.396324999278 1723904090096000000 +neoom_beaam_thing,datapoint=POWER_P1,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=W value=-305 1723905135056000000 +neoom_beaam_thing,datapoint=POWER_P2,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=W value=-268 1723905135056000000 +neoom_beaam_thing,datapoint=POWER_P3,source=127.0.0.1,thing=ELECTRICITY_METER_AC,unit=W value=-214 1723905135056000000 +neoom_beaam_thing,datapoint=CONNECTIONS,source=127.0.0.1,thing=PV,unit=None value_0=true,value_1=true 1723890960060000000 +neoom_beaam_thing,datapoint=POWER,source=127.0.0.1,thing=PV,unit=W value=245 1723890960060000000 +neoom_beaam_thing,datapoint=PRODUCED_ENERGY,source=127.0.0.1,thing=PV,unit=Wh value=335423.1124235639 1723890960060000000 +neoom_beaam_thing,datapoint=VOLTAGES,source=127.0.0.1,thing=PV,unit=V value_0=231.42,value_1=302.25 1723890960060000000 +neoom_beaam_thing,datapoint=CURRENTS,source=127.0.0.1,thing=PV,unit=A value_0=10.889292196,value_1=69.4789081886 1723890960060000000 diff --git a/plugins/inputs/neoom_beaam/testcases/small/state.json b/plugins/inputs/neoom_beaam/testcases/small/state.json new file mode 100644 index 000000000..2c701e58e --- /dev/null +++ b/plugins/inputs/neoom_beaam/testcases/small/state.json @@ -0,0 +1,96 @@ +{ + "energyFlow": { + "states": [ + { + "dataPointId": "27291fe9-b44a-5530-92cf-1fad478f6f66", + "key": "SELF_SUFFICIENCY", + "value": 100, + "ts": 1723883145025 + }, + { + "dataPointId": "211f490a-af77-5c33-af3f-fd2e67e40b3f", + "key": "POWER_PRODUCTION", + "value": 1184, + "ts": 1723883150002 + }, + { + "dataPointId": "58d84d20-ba7c-5218-9045-459e4a946080", + "key": "POWER_CONSUMPTION", + "value": null, + "ts": 1723877138187 + }, + { + "dataPointId": "2548caa8-0288-559c-9e75-50b964f4bf01", + "key": "POWER_GRID", + "value": 15, + "ts": 1723883150002 + }, + { + "dataPointId": "b9399b12-e3bb-5515-9b1a-640bd51c1c1b", + "key": "POWER_STORAGE", + "value": 3504, + "ts": 1723883150002 + }, + { + "dataPointId": "49764da7-5486-52bf-9867-66643445d7fb", + "key": "ENERGY_PRODUCED", + "value": 639300, + "ts": 1723882905007 + }, + { + "dataPointId": "eb0f0b1f-73cc-5cb8-864d-baa11b8628c4", + "key": "ENERGY_CONSUMED", + "value": null, + "ts": 1723877138187 + }, + { + "dataPointId": "3dd29b23-eb88-538a-8220-ccd7192d4fef", + "key": "ENERGY_IMPORTED", + "value": 22696.926152777138, + "ts": 1723883150002 + }, + { + "dataPointId": "804a496b-267b-5783-850d-b43ecc1b6f9c", + "key": "ENERGY_EXPORTED", + "value": 237421.33112499933, + "ts": 1723883135003 + }, + { + "dataPointId": "25aac409-c380-575f-8429-6a33de6bec3e", + "key": "ENERGY_CHARGED", + "value": 215200, + "ts": 1723882765002 + }, + { + "dataPointId": "42aebe59-8b2c-58fa-a222-991e88451376", + "key": "ENERGY_DISCHARGED", + "value": 144800, + "ts": 1723880490005 + }, + { + "dataPointId": "d40db932-df55-57c9-955b-2528a4df4d73", + "key": "STATE_OF_CHARGE", + "value": 59, + "ts": 1723883090002 + }, + { + "dataPointId": "6a7fe53b-65c1-576b-8159-bd79d1fd7651", + "key": "POWER_CONSUMPTION_CALC", + "value": 4703, + "ts": 1723883150002 + }, + { + "dataPointId": "adf97bab-6a8f-543a-8dab-a7459cc977e7", + "key": "POWER_GRID_REMAINING", + "value": null, + "ts": 1723877138187 + }, + { + "dataPointId": "df4668c6-6d3f-5837-a7c7-e7aaa408f068", + "key": "ENERGY_CONSUMED_CALC", + "value": 354175.59502777783, + "ts": 1723883150002 + } + ] + } +} \ No newline at end of file diff --git a/plugins/inputs/neoom_beaam/testcases/small/telegraf.conf b/plugins/inputs/neoom_beaam/testcases/small/telegraf.conf new file mode 100644 index 000000000..c140a29d7 --- /dev/null +++ b/plugins/inputs/neoom_beaam/testcases/small/telegraf.conf @@ -0,0 +1,2 @@ +[[inputs.neoom_beaam]] + address = "dummy" \ No newline at end of file diff --git a/plugins/inputs/neoom_beaam/testcases/small/thing_22222222-bbbb-2222-bbbb-222222222222.json b/plugins/inputs/neoom_beaam/testcases/small/thing_22222222-bbbb-2222-bbbb-222222222222.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/plugins/inputs/neoom_beaam/testcases/small/thing_22222222-bbbb-2222-bbbb-222222222222.json @@ -0,0 +1 @@ +{} diff --git a/plugins/inputs/neoom_beaam/testcases/small/thing_33333333-cccc-3333-cccc-333333333333.json b/plugins/inputs/neoom_beaam/testcases/small/thing_33333333-cccc-3333-cccc-333333333333.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/plugins/inputs/neoom_beaam/testcases/small/thing_33333333-cccc-3333-cccc-333333333333.json @@ -0,0 +1 @@ +{} diff --git a/plugins/inputs/neoom_beaam/testcases/small/thing_44444444-dddd-4444-dddd-444444444444.json b/plugins/inputs/neoom_beaam/testcases/small/thing_44444444-dddd-4444-dddd-444444444444.json new file mode 100644 index 000000000..d27301de3 --- /dev/null +++ b/plugins/inputs/neoom_beaam/testcases/small/thing_44444444-dddd-4444-dddd-444444444444.json @@ -0,0 +1,35 @@ +{ + "thingId": "44444444-dddd-4444-dddd-444444444444", + "states": [ + { + "dataPointId": "244fa23e-cdbc-5d7d-a3e9-788af3ab43d2", + "key": "CONNECTIONS", + "value": [true, true], + "ts": 1723890960060 + }, + { + "dataPointId": "d064a7fc-4b9a-5cf4-a55f-eeda5289afac", + "key": "POWER", + "value": 245, + "ts": 1723890960060 + }, + { + "dataPointId": "44763dab-6224-5b2a-aadf-d5afd781cdd5", + "key": "PRODUCED_ENERGY", + "value": 335423.11242356391, + "ts": 1723890960060 + }, + { + "dataPointId": "892e0390-252d-5fb5-9857-04924f1770e1", + "key": "VOLTAGES", + "value": [231.42, 302.25], + "ts": 1723890960060 + }, + { + "dataPointId": "a153ab79-3cf2-5f17-ab40-9af3c38d7e60", + "key": "CURRENTS", + "value": [10.889292196, 69.4789081886], + "ts": 1723890960060 + } + ] +} \ No newline at end of file diff --git a/plugins/inputs/neoom_beaam/testcases/small/thing_55555555-eeee-5555-eeee-555555555555.json b/plugins/inputs/neoom_beaam/testcases/small/thing_55555555-eeee-5555-eeee-555555555555.json new file mode 100644 index 000000000..ff6e5e8e1 --- /dev/null +++ b/plugins/inputs/neoom_beaam/testcases/small/thing_55555555-eeee-5555-eeee-555555555555.json @@ -0,0 +1,71 @@ +{ + "thingId": "55555555-eeee-5555-eeee-555555555555", + "states": [ + { + "dataPointId": "094a8ac0-914f-5f0d-a376-d2e3abfdc75e", + "key": "CONNECTION", + "value": true, + "ts": 1723890960060 + }, + { + "dataPointId": "22911bdc-0b2f-5008-9d42-c551d4fa53c7", + "key": "VOLTAGE_P1", + "value": 245, + "ts": 1723905135056 + }, + { + "dataPointId": "4e34e4eb-0859-5253-81ae-9d536e75c003", + "key": "VOLTAGE_P2", + "value": 242.3, + "ts": 1723905135056 + }, + { + "dataPointId": "a45c0fe9-42e8-5091-9007-ff73d55a149b", + "key": "VOLTAGE_P3", + "value": 245.5, + "ts": 1723905135056 + }, + { + "dataPointId": "755e91d4-f6c6-5570-a2bf-623b20055816", + "key": "FREQUENCY", + "value": 49.98, + "ts": 1723905135056 + }, + { + "dataPointId": "c7b812ba-e08b-5f4a-bcd4-97616c8130bc", + "key": "ACTIVE_POWER", + "value": -826, + "ts": 1723905135056 + }, + { + "dataPointId": "3f4105ab-bdf8-5d0b-88ba-9859dcf3f111", + "key": "OUTPUT_ENERGY", + "value": 241826.44736444377, + "ts": 1723905135075 + }, + { + "dataPointId": "6ddbb838-0557-5df0-bdeb-489f426248fb", + "key": "INPUT_ENERGY", + "value": 22988.396324999278, + "ts": 1723904090096 + }, + { + "dataPointId": "b89fda1c-f19a-5da0-bcbd-4277eb377b3a", + "key": "POWER_P1", + "value": -305, + "ts": 1723905135056 + }, + { + "dataPointId": "64fd2faa-13ca-5cc4-8302-3085f13775c8", + "key": "POWER_P2", + "value": -268, + "ts": 1723905135056 + }, + { + "dataPointId": "eca4dacc-1858-50f1-ac46-30cddcde75a2", + "key": "POWER_P3", + "value": -214, + "ts": 1723905135056 + } + ] +} \ No newline at end of file diff --git a/plugins/inputs/neoom_beaam/types.go b/plugins/inputs/neoom_beaam/types.go new file mode 100644 index 000000000..45626325c --- /dev/null +++ b/plugins/inputs/neoom_beaam/types.go @@ -0,0 +1,38 @@ +package neoom_beaam + +type site struct { + EnergyFlow struct { + DataPoints map[string]datapoint `json:"dataPoints"` + } `json:"energyFlow"` + Things map[string]thingDefinition `json:"things"` +} + +type siteState struct { + EnergyFlow struct { + States []state `json:"states"` + } `json:"energyFlow"` +} + +type thingDefinition struct { + id string + Name string `json:"type"` + DataPoints map[string]datapoint `json:"dataPoints"` +} + +type thingState struct { + ID string `json:"thingId"` + States []state `json:"states"` +} + +type datapoint struct { + Key string `json:"key"` + DataType string `json:"dataType"` + Unit string `json:"unitOfMeasure"` +} + +type state struct { + Key string `json:"key"` + Value interface{} `json:"value"` + DataPointID string `json:"dataPointId"` + Timestamp float64 `json:"ts"` +}