Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
spacemanspiff2007 committed Mar 26, 2024
1 parent da12ddc commit ac78807
Show file tree
Hide file tree
Showing 13 changed files with 253 additions and 255 deletions.
8 changes: 5 additions & 3 deletions docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ Example output for the meter data:
If the meter does not report ``0100000009ff`` it's possible to configure another number (of even multiple ones)
for configuration matching (see :ref:`command line interface <CONFIG_GENERAL>`).
for configuration matching (see :ref:`general under config <CONFIG_GENERAL>`).

5. Edit device settings
======================================
Expand Down Expand Up @@ -371,7 +371,7 @@ Run the analyze command again to see how the reported values change.
mqtt:
topic: energy_today
operations:
- type: meter
- type: meter # A virtual meter
start now: true # Start immediately
reset times: # Reset at midnight
- 00:00
Expand All @@ -391,7 +391,9 @@ Run the analyze command again to see how the reported values change.
mqtt:
topic: power
operations:
- delta filter: 5%
- type: delta filter
min: 10
min %: 5
- refresh action: 01:00
Expand Down
50 changes: 31 additions & 19 deletions docs/operations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,24 @@ Example
type: change filter
Range filter
--------------------------------------

.. autopydantic_model:: RangeFilter
:inherited-members: BaseModel
:exclude-members: type, get_kwargs_limit

Example
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
..
YamlModel: RangeFilter
.. code-block:: yaml
type: range filter
min: 0
Delta Filter
--------------------------------------

Expand All @@ -44,14 +62,25 @@ Example
.. code-block:: yaml
delta filter: 5
type: delta filter
min: 5
min %: 10
..
YamlModel: DeltaFilter
.. code-block:: yaml
delta filter: "5 %"
type: delta filter
min: 5
..
YamlModel: DeltaFilter
.. code-block:: yaml
type: delta filter
min %: 10
Heartbeat Filter
Expand Down Expand Up @@ -143,23 +172,6 @@ Example
round: 2
Limit value
--------------------------------------

.. autopydantic_model:: LimitValue
:inherited-members: BaseModel
:exclude-members: type, get_kwargs_limit

Example
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
..
YamlModel: LimitValue
.. code-block:: yaml
type: limit value
min: 0
Workarounds
======================================
Expand Down
81 changes: 25 additions & 56 deletions src/sml2mqtt/config/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from annotated_types import Len
from easyconfig import BaseModel
from pydantic import Discriminator, Field, StrictBool, StrictFloat, StrictInt, Tag
from pydantic import Discriminator, Field, StrictBool, StrictFloat, StrictInt, Tag, model_validator

from sml2mqtt.const import DateTimeFinder, DurationType, TimeSeries

Expand All @@ -30,40 +30,35 @@ def get_kwargs_on_change(self) -> EmptyKwargs:
return {}


class DeltaFilterKwargs(TypedDict):
delta: int | float
is_percent: bool
class RangeFilter(BaseModel):
"""Filters or limits to values that are in a certain range
"""
type: Literal['range filter']
min_value: float | None = Field(None, alias='min', description='minimum value that will pass')
max_value: float | None = Field(None, alias='max', description='maximum value that will pass')
limit_values: bool = Field(
False, alias='limit', description='Instead of ignoring the values they will be limited to min/max'
)

@model_validator(mode='after')
def _check_set(self) -> RangeFilter:
if self.min_value is None and self.max_value is None:
msg = 'Neither min or max are set!'
raise ValueError(msg)
return self


class DeltaFilter(BaseModel):
"""A filter which lets the value only pass if the incoming value is different enough from value that was passed the
last time. The delta can be specified as an absolute value or as a percentage.
last time. The delta can an absolute value or as a percentage.
If multiple deltas are specified they are all checked.
"""

delta: StrictInt | StrictFloat | PercentStr = Field(
alias='delta filter',
description='Absolute value or a percentage'
)

@final
def get_kwargs_delta(self) -> DeltaFilterKwargs:

is_percent = False
if isinstance(self.delta, str):
delta = self.delta
is_percent = delta.endswith('%')
delta = delta.removesuffix('%')
try:
delta = int(delta)
except ValueError:
delta = float(delta)
else:
delta = self.delta
type: Literal['delta filter']

return {
'delta': delta,
'is_percent': is_percent
}
min_value: StrictInt | StrictFloat | None = Field(None, alias='min')
min_percent: StrictInt | StrictFloat | None = Field(None, alias='min %')


class HeartbeatFilter(BaseModel):
Expand Down Expand Up @@ -100,32 +95,6 @@ class Round(BaseModel):
digits: int = Field(ge=0, le=6, alias='round', description='Round to the specified digits')


class LimitValue(BaseModel):
"""Limits the value to always be in a certain range
"""
type: Literal['limit value']
min: float | None = Field(None, description='minimum value')
max: float | None = Field(None, description='maximum value')
ignore: bool = Field(
False, alias='ignore out of range', description='Instead of limiting the value it will be ignored'
)

@final
def get_kwargs_limit(self) -> LimitValueKwargs:
return {
'min': self.min,
'max': self.max,
'ignore': self.ignore
}


class LimitValueKwargs(TypedDict):
min: float | None
max: float | None
ignore: bool


# -------------------------------------------------------------------------------------------------
# Workarounds
# -------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -276,9 +245,9 @@ class MeanOfInterval(HasIntervalFields):
# -------------------------------------------------------------------------------------------------

OperationsModels = (
OnChangeFilter, DeltaFilter, HeartbeatFilter,
OnChangeFilter, DeltaFilter, HeartbeatFilter, RangeFilter,
RefreshAction,
Factor, Offset, Round, LimitValue,
Factor, Offset, Round,
NegativeOnEnergyMeterWorkaround,
Or, Sequence,
VirtualMeter, MaxValue, MinValue,
Expand Down
4 changes: 2 additions & 2 deletions src/sml2mqtt/sml_device/setup_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
from sml2mqtt.sml_value import SmlValue
from sml2mqtt.sml_value.operations import (
FactorOperation,
LimitValueOperation,
OffsetOperation,
OnChangeFilterOperation,
OrOperation,
RangeFilterOperation,
RefreshActionOperation,
RoundOperation,
SequenceOperation,
Expand Down Expand Up @@ -59,7 +59,7 @@ def _create_default_transformations(log: logging.Logger, sml_value: SmlValue, fr
def _create_default_filters(log: logging.Logger, sml_value: SmlValue, general_cfg: GeneralSettings):
if op := has_operation_type(
sml_value,
FactorOperation, OffsetOperation, RoundOperation, LimitValueOperation, SkipZeroMeterOperation,
FactorOperation, OffsetOperation, RoundOperation, RangeFilterOperation, SkipZeroMeterOperation,
is_of=False
):
log.debug(f'Found {op.__class__.__name__:s} - skip creating default filters')
Expand Down
26 changes: 26 additions & 0 deletions src/sml2mqtt/sml_value/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,29 @@ def add_operation(self, operation: ValueOperationBase):
def insert_operation(self, operation: ValueOperationBase):
self.operations = (operation, *self.operations)
return self


class ValueOperationWithStartupBase(ValueOperationBase):
_PROCESS_VALUE_BACKUP_ATTR: Final = '_process_value_original'

def on_first_value(self, value: float, info: SmlValueInfo):
raise NotImplementedError()

def enable_on_first_value(self):
name: Final = self._PROCESS_VALUE_BACKUP_ATTR
if hasattr(self, name):
raise ValueError()

setattr(self, name, self.process_value)
self.process_value = self._process_value_first

def _process_value_first(self, value: float | None, info: SmlValueInfo) -> float | None:
if value is None:
return None

# restore original function
name: Final = self._PROCESS_VALUE_BACKUP_ATTR
self.process_value = getattr(self, name)
delattr(self, name)

return self.on_first_value(value, info)
7 changes: 3 additions & 4 deletions src/sml2mqtt/sml_value/operations/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
from .actions import RefreshActionOperation
from .date_time import DateTimeFinder, MaxValueOperation, MinValueOperation, VirtualMeterOperation
from .filter import (
AbsDeltaFilterOperation,
DeltaFilterBase,
DeltaFilterOperation,
HeartbeatFilterOperation,
OnChangeFilterOperation,
PercDeltaFilterOperation,
RangeFilterOperation,
SkipZeroMeterOperation,
)
from .math import FactorOperation, LimitValueOperation, OffsetOperation, RoundOperation
from .math import FactorOperation, OffsetOperation, RoundOperation
from .operations import OrOperation, SequenceOperation
from .time_series import MaxOfIntervalOperation, MeanOfIntervalOperation, MinOfIntervalOperation
from .workarounds import NegativeOnEnergyMeterWorkaroundOperation
Loading

0 comments on commit ac78807

Please sign in to comment.