Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial submittion of a reusable hysteresis function. #222

Closed
wants to merge 9 commits into from
101 changes: 101 additions & 0 deletions Community/Hysteresis/automation/lib/python/community/hysteresis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
"""
Author: Rich Koshak
The Hysteresis module provides an implementation that will tell you whether a
value is below, above, or inbetween a target value plus or minus an offset.

Functions
========
5iver marked this conversation as resolved.
Show resolved Hide resolved
- hysteresis: Returns a value indicating if the value is above, below, or
within the hysteresis gap.

License
5iver marked this conversation as resolved.
Show resolved Hide resolved
========
Copyright (c) 2019 Richard Koshak
5iver marked this conversation as resolved.
Show resolved Hide resolved

This program and accomanying materials are made available under the terms of
the Eclipse Public License 2.0 which is available at
http://www.eclipse.org/legal/epl-2.0

"""
from org.eclipse.smarthome.core.library.types import QuantityType, DecimalType, PercentType
5iver marked this conversation as resolved.
Show resolved Hide resolved

@log_traceback
def hysteresis(target, value, low=0, high=0):
"""
Checks if the value is below, above or between the hysteresis gap defined
by the target-low <= value <= target+high. The function accepts Python
primitives, QuantityTypes, DecimalTypes, or PercentTypes or any combination
of the four types. When using QuantityTypes, the default units of the Units
of Measure is used.

Arguments:
- target: The target value that defines the setpoint for the comparison.
- value: The value to determine where it is in the hysteresis.
- low: Value subtracted from target to define the lower bounds of the
hysteresis gap. Defaults to 0.
- high: Value added to target to define the upper bounds of the
hystersis gap. Defaults to 0
Returns:
- 1 if value is >= target+high
- 0 if the value is between target-low and target+high or if low and
high are both zero and value == target
- (-1) if value is <= target-low
"""
def get_float(value):
"""Helper function to normalize all the argumets to primitive floats."""
if isinstance(value, (QuantityType, DecimalType, PercentType)):
value = value.floatValue()
return value

target = get_float(target)
value = get_float(value)
low = get_float(low)
high = get_float(high)
5iver marked this conversation as resolved.
Show resolved Hide resolved

if value == target or target - low < value < target + high: rval = 0
5iver marked this conversation as resolved.
Show resolved Hide resolved
elif value <= (target - low): rval = (-1)
5iver marked this conversation as resolved.
Show resolved Hide resolved
else: rval = 1
5iver marked this conversation as resolved.
Show resolved Hide resolved

return rval

# Hysteresis test
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take this out and put in into a comment in the PR or add to a new Tests directory off of the root.

Copy link
Member

@5iver 5iver Sep 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've moved these, but put the Tests directory under .github. Please move it up to the root (same level as Core and Community).

I'm rethinking this directory structure though. I think it would be better to put the tests in packages, like /Tests/automation/lib/python/tests/core/utils.py. The ones for hysteresis would go in there and we can add in others for the rest of utils. Then we can use a script to load the module and execute the tests. Still just drawing in the sand though!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense for Communty to put the tests with the modules? For example:

Modules: Community/Gatekeeper/automation/lib/python/community/gatekeeper/gatekeeper.py
Scripts:
Community/Gatekeeper/automation/jsr223/python/community/gatekeeper/gatekeeper_script.py
Tests: Community/Gatekeeper/automation/test/python/gatekeeper.py

That way the tests and the code would live in the same root folder which might make it easier for contributors to manage.

For tests on the Helper Libraries themselves, the second "tests" feels redundant. Is there a standard for unit tests for Python? Maybe we should follow that (and for the other languages as well.

Anyway, obviously VSCode let me down when I created the folder. It'll be fixed shortly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not aware of any standards for tests and I've seen it done many different ways. I think it would be best to keep the tests separate, since they don't really have a place in a normal installation. To do this, they'd need to go into a separate directory off of the root of the repo. Under that directory, they'd have the normal directory structure so that they can be copied over an existing installation, or patched in with a symlink.

There's a lot to sort out for testing and this is just a rough cut to get them stashed somewhere. It will be nice to have some tests in the repo to have some experience using them to help determine the best location and workflow for them. There were some other tests built up for core.metadata and core.date, and I have pages of regression tests that I use after major changes in OHC. We'll get there.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, well the commit below moved the tests to the folder you suggested above. This issue should be addressed.

from core.log import logging, LOG_PREFIX
log = logging.getLogger("{}.TEST.util".format(LOG_PREFIX))
try:
assert hysteresis(30, 30, 1, 1) == 0
assert hysteresis(30, 29, 1, 1) == -1
assert hysteresis(30, 31, 1, 1) == 1
assert hysteresis(30, 30) == 0
assert hysteresis(30, 31) == 1
assert hysteresis(30, 29) == -1
assert hysteresis(QuantityType(u"30 %"),
QuantityType(u"29 %"),
low=QuantityType(u"1 %")) == -1
assert hysteresis(QuantityType(u"30 %"), QuantityType(u"29 %"), 1, 1) == -1
assert hysteresis(QuantityType(u"30 %"),
QuantityType(u"31 %"),
high=QuantityType(u"1 %")) == 1
assert hysteresis(DecimalType(30),
DecimalType(29),
low=DecimalType(1)) == -1
assert hysteresis(DecimalType(30), DecimalType(29), 1, 1) == -1
assert hysteresis(DecimalType(30),
DecimalType(31),
high=DecimalType(1)) == 1
assert hysteresis(PercentType(30),
PercentType(29),
low=PercentType(1)) == -1
assert hysteresis(PercentType(30), PercentType(29), 1, 1) == -1
assert hysteresis(PercentType(30),
PercentType(31),
high=PercentType(1)) == 1
assert hysteresis(QuantityType(u"30 %"),
DecimalType(29),
PercentType(1), 1) == -1

except AssertionError:
import traceback
log.error("Exception: {}".format(traceback.format_exc()))
else:
log.info("hysteresis tests passed!")
9 changes: 9 additions & 0 deletions Sphinx/Python/Community/Hysteresis.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
*****
Hysteresis
*****
5iver marked this conversation as resolved.
Show resolved Hide resolved


Packages and Modules
====================

.. automodule:: community.hystersis