Skip to content

Commit

Permalink
feat(processors.scale): Add scaling by factor and offset (influxdata#…
Browse files Browse the repository at this point in the history
  • Loading branch information
srebhan committed May 8, 2023
1 parent 5cb928c commit afab345
Show file tree
Hide file tree
Showing 4 changed files with 391 additions and 117 deletions.
35 changes: 21 additions & 14 deletions plugins/processors/scale/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,18 @@ the given output range according to this formula:
\text{output\_minimum}
```

Input fields are converted to floating point values.
If the conversion fails, those fields are ignored.
Alternatively, you can apply a factor and offset to the input according to
this formula

**Please note:** Neither the input nor the output values
are clipped to their respective ranges!
```math
\text{result}=\text{factor} \cdot \text{value} + \text{offset}
```

Input fields are converted to floating point values if possible. Otherwise,
fields that cannot be converted are ignored and keep their original value.

**Please note:** Neither the input nor the output values are clipped to their
respective ranges!

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

Expand All @@ -37,24 +44,24 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
## - input_maximum: Maximum expected input value
## - output_minimum: Minimum desired output value
## - output_maximum: Maximum desired output value
## alternatively you can specify a scaling with factor and offset
## - factor: factor to scale the input value with
## - offset: additive offset for value after scaling
## - fields: a list of field names (or filters) to apply this scaling to
## Example: Define a scaling

## Example: Scaling with minimum and maximum values
# [processors.scale.scaling]
# input_minimum = 0
# input_maximum = 1
# output_minimum = 0
# output_maximum = 100
# fields = ["temperature1", "temperature2"]

## Multiple scalings can be defined simultaneously
## Example: A second scaling.

## Example: Scaling with factor and offset
# [processors.scale.scaling]
# input_minimum = 0
# input_maximum = 50
# output_minimum = 50
# output_maximum = 100
# fields = ["humidity*"]
# factor = 10.0
# offset = -5.0
# fields = ["voltage*"]
```

## Example
Expand Down
20 changes: 10 additions & 10 deletions plugins/processors/scale/sample.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@
## - input_maximum: Maximum expected input value
## - output_minimum: Minimum desired output value
## - output_maximum: Maximum desired output value
## alternatively you can specify a scaling with factor and offset
## - factor: factor to scale the input value with
## - offset: additive offset for value after scaling
## - fields: a list of field names (or filters) to apply this scaling to
## Example: Define a scaling

## Example: Scaling with minimum and maximum values
# [processors.scale.scaling]
# input_minimum = 0
# input_maximum = 1
# output_minimum = 0
# output_maximum = 100
# fields = ["temperature1", "temperature2"]

## Multiple scalings can be defined simultaneously
## Example: A second scaling.

## Example: Scaling with factor and offset
# [processors.scale.scaling]
# input_minimum = 0
# input_maximum = 50
# output_minimum = 50
# output_maximum = 100
# fields = ["humidity*"]
# factor = 10.0
# offset = -5.0
# fields = ["voltage*"]
56 changes: 41 additions & 15 deletions plugins/processors/scale/scale.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,55 @@ func (*Scale) SampleConfig() string {
}

type Scaling struct {
InMin float64 `toml:"input_minimum"`
InMax float64 `toml:"input_maximum"`
OutMin float64 `toml:"output_minimum"`
OutMax float64 `toml:"output_maximum"`
InMin *float64 `toml:"input_minimum"`
InMax *float64 `toml:"input_maximum"`
OutMin *float64 `toml:"output_minimum"`
OutMax *float64 `toml:"output_maximum"`
Factor *float64 `toml:"factor"`
Offset *float64 `toml:"offset"`
Fields []string `toml:"fields"`

factor float64
fieldFilter filter.Filter
scale float64
shiftIn float64
shiftOut float64
}

type Scale struct {
Scalings []Scaling `toml:"scaling"`
Log telegraf.Logger `toml:"-"`
}

func (s *Scaling) init() error {
if s.InMax == s.InMin {
return fmt.Errorf("input minimum and maximum are equal for fields %s", strings.Join(s.Fields, ","))
}
func (s *Scaling) Init() error {
s.scale, s.shiftOut, s.shiftIn = float64(1.0), float64(0.0), float64(0.0)
allMinMaxSet := s.OutMax != nil && s.OutMin != nil && s.InMax != nil && s.InMin != nil
anyMinMaxSet := s.OutMax != nil || s.OutMin != nil || s.InMax != nil || s.InMin != nil
factorSet := s.Factor != nil || s.Offset != nil
if anyMinMaxSet && factorSet {
return fmt.Errorf("cannot use factor/offset and minimum/maximum at the same time for fields %s", strings.Join(s.Fields, ","))
} else if anyMinMaxSet && !allMinMaxSet {
return fmt.Errorf("all minimum and maximum values need to be set for fields %s", strings.Join(s.Fields, ","))
} else if !anyMinMaxSet && !factorSet {
return fmt.Errorf("no scaling defined for fields %s", strings.Join(s.Fields, ","))
} else if allMinMaxSet {
if *s.InMax == *s.InMin {
return fmt.Errorf("input minimum and maximum are equal for fields %s", strings.Join(s.Fields, ","))
}

if *s.OutMax == *s.OutMin {
return fmt.Errorf("output minimum and maximum are equal for fields %s", strings.Join(s.Fields, ","))
}

if s.OutMax == s.OutMin {
return fmt.Errorf("output minimum and maximum are equal for fields %s", strings.Join(s.Fields, ","))
s.scale = (*s.OutMax - *s.OutMin) / (*s.InMax - *s.InMin)
s.shiftOut = *s.OutMin
s.shiftIn = *s.InMin
} else {
if s.Factor != nil {
s.scale = *s.Factor
}
if s.Offset != nil {
s.shiftOut = *s.Offset
}
}

scalingFilter, err := filter.Compile(s.Fields)
Expand All @@ -51,18 +78,17 @@ func (s *Scaling) init() error {
}
s.fieldFilter = scalingFilter

s.factor = (s.OutMax - s.OutMin) / (s.InMax - s.InMin)
return nil
}

// scale a float according to the input and output range
func (s *Scaling) process(value float64) float64 {
return (value-s.InMin)*s.factor + s.OutMin
return s.scale*(value-s.shiftIn) + s.shiftOut
}

func (s *Scale) Init() error {
if s.Scalings == nil {
return errors.New("no valid scalings defined")
return errors.New("no valid scaling defined")
}

allFields := make(map[string]bool)
Expand All @@ -77,7 +103,7 @@ func (s *Scale) Init() error {
}
}

if err := s.Scalings[i].init(); err != nil {
if err := s.Scalings[i].Init(); err != nil {
return fmt.Errorf("scaling %d: %w", i+1, err)
}
}
Expand Down
Loading

0 comments on commit afab345

Please sign in to comment.