Skip to content

Commit

Permalink
2.12.4 - Add integer clamping for scaled values
Browse files Browse the repository at this point in the history
  • Loading branch information
vkottler committed Sep 27, 2023
1 parent eeb706a commit 33c8873
Show file tree
Hide file tree
Showing 10 changed files with 65 additions and 9 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:
- run: |
mk python-release owner=vkottler \
repo=runtimepy version=2.12.3
repo=runtimepy version=2.12.4
if: |
matrix.python-version == '3.11'
&& matrix.system == 'ubuntu-latest'
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
=====================================
generator=datazen
version=3.1.3
hash=f8a5c23e6166199774f2dc5e19dc76d0
hash=2361470f35dc837ce96ac147be7e44ee
=====================================
-->

# runtimepy ([2.12.3](https://pypi.org/project/runtimepy/))
# runtimepy ([2.12.4](https://pypi.org/project/runtimepy/))

[![python](https://img.shields.io/pypi/pyversions/runtimepy.svg)](https://pypi.org/project/runtimepy/)
![Build Status](https://github.com/vkottler/runtimepy/workflows/Python%20Package/badge.svg)
Expand Down
2 changes: 1 addition & 1 deletion local/variables/package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
major: 2
minor: 12
patch: 3
patch: 4
entry: runtimepy
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta:__legacy__"

[project]
name = "runtimepy"
version = "2.12.3"
version = "2.12.4"
description = "A framework for implementing Python services."
readme = "README.md"
requires-python = ">=3.11"
Expand Down
4 changes: 2 additions & 2 deletions runtimepy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# =====================================
# generator=datazen
# version=3.1.3
# hash=b58f93345cd96f4613356453688c5a3d
# hash=8df5cf822b83a463b54443ea84e6e40e
# =====================================

"""
Expand All @@ -10,7 +10,7 @@

DESCRIPTION = "A framework for implementing Python services."
PKG_NAME = "runtimepy"
VERSION = "2.12.3"
VERSION = "2.12.4"

# runtimepy-specific content.
METRICS_NAME = "metrics"
7 changes: 6 additions & 1 deletion runtimepy/primitives/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,15 @@ def scaled(self) -> Numeric:
def scaled(self, value: T) -> None:
"""Set this value but invert scaling information."""

self.value = invert( # type: ignore
val = invert(
value, scaling=self.scaling, should_round=self.kind.is_integer
)

if self.kind.int_bounds is not None:
val = self.kind.int_bounds.clamp(val) # type: ignore

self.value = val # type: ignore

def __call__(self, value: T = None) -> T:
"""
A callable interface for setting and getting the underlying value.
Expand Down
3 changes: 3 additions & 0 deletions runtimepy/primitives/type/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from struct import unpack as _unpack
from typing import BinaryIO as _BinaryIO
from typing import Generic as _Generic
from typing import Optional as _Optional
from typing import Type as _Type
from typing import TypeVar as _TypeVar
from typing import Union as _Union
Expand All @@ -19,6 +20,7 @@
DEFAULT_BYTE_ORDER as _DEFAULT_BYTE_ORDER,
)
from runtimepy.primitives.byte_order import ByteOrder as _ByteOrder
from runtimepy.primitives.type.bounds import IntegerBounds

# Integer type aliases.
Int8Ctype = _ctypes.c_byte
Expand Down Expand Up @@ -73,6 +75,7 @@ def __init__(self, struct_format: str, signed: bool = True) -> None:

self.format = struct_format
self.signed = signed
self.int_bounds: _Optional[IntegerBounds] = None

# Make sure that the struct size and ctype size match. There's
# unfortunately no obvious (or via public interfaces) way to just
Expand Down
30 changes: 30 additions & 0 deletions runtimepy/primitives/type/bounds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
A module implementing an interface for keeping track of primitive-integer
bounds (based on bit width).
"""

# built-in
from typing import NamedTuple


class IntegerBounds(NamedTuple):
"""A container for integer bounds."""

min: int
max: int

def clamp(self, val: int) -> int:
"""
Ensure that 'val' is between min and max, use the min or max value
instead of the provided value if it exceeds these bounds.
"""

return max(self.min, min(val, self.max))

@staticmethod
def create(byte_count: int, signed: bool) -> "IntegerBounds":
"""Compute maximum and minimum values given size and signedness."""

min_val = 0 if not signed else -1 * (2 ** (byte_count * 8 - 1))
width = 8 * byte_count if not signed else 8 * byte_count - 1
return IntegerBounds(min_val, (2**width) - 1)
9 changes: 9 additions & 0 deletions runtimepy/primitives/type/int.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from runtimepy.primitives.type.base import Uint16Ctype as _Uint16Ctype
from runtimepy.primitives.type.base import Uint32Ctype as _Uint32Ctype
from runtimepy.primitives.type.base import Uint64Ctype as _Uint64Ctype
from runtimepy.primitives.type.bounds import IntegerBounds


class Int8Type(_PrimitiveType[_Int8Ctype]):
Expand All @@ -24,6 +25,7 @@ def __init__(self) -> None:
"""Initialize this type."""
super().__init__("b")
assert self.is_integer
self.int_bounds = IntegerBounds.create(1, True)


Int8 = Int8Type()
Expand All @@ -39,6 +41,7 @@ def __init__(self) -> None:
"""Initialize this type."""
super().__init__("h")
assert self.is_integer
self.int_bounds = IntegerBounds.create(2, True)


Int16 = Int16Type()
Expand All @@ -54,6 +57,7 @@ def __init__(self) -> None:
"""Initialize this type."""
super().__init__("i")
assert self.is_integer
self.int_bounds = IntegerBounds.create(4, True)


Int32 = Int32Type()
Expand All @@ -69,6 +73,7 @@ def __init__(self) -> None:
"""Initialize this type."""
super().__init__("q")
assert self.is_integer
self.int_bounds = IntegerBounds.create(8, True)


Int64 = Int64Type()
Expand All @@ -84,6 +89,7 @@ def __init__(self) -> None:
"""Initialize this type."""
super().__init__("B", signed=False)
assert self.is_integer
self.int_bounds = IntegerBounds.create(1, False)


Uint8 = Uint8Type()
Expand All @@ -99,6 +105,7 @@ def __init__(self) -> None:
"""Initialize this type."""
super().__init__("H", signed=False)
assert self.is_integer
self.int_bounds = IntegerBounds.create(2, False)


Uint16 = Uint16Type()
Expand All @@ -114,6 +121,7 @@ def __init__(self) -> None:
"""Initialize this type."""
super().__init__("I", signed=False)
assert self.is_integer
self.int_bounds = IntegerBounds.create(4, False)


Uint32 = Uint32Type()
Expand All @@ -129,6 +137,7 @@ def __init__(self) -> None:
"""Initialize this type."""
super().__init__("Q", signed=False)
assert self.is_integer
self.int_bounds = IntegerBounds.create(8, False)


Uint64 = Uint64Type()
11 changes: 10 additions & 1 deletion tests/channel/environment/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ def test_channel_command_scalings():

env = ChannelEnvironment()

env.int_channel("4_20ma", commandable=True, scaling=[0.0, 3.81469727e-07])
env.int_channel(
"4_20ma",
commandable=True,
scaling=[0.0, 3.81469727e-07],
kind="uint16",
)

processor = ChannelCommandProcessor(env, getLogger(__name__))

Expand All @@ -39,6 +44,10 @@ def test_channel_command_scalings():
assert processor.command(f"set 4_20ma {val}")
val += 0.01

# Ensure that clamping works.
assert processor.command("set 4_20ma 0.030")
assert isclose(env.value("4_20ma"), 0.025, rel_tol=0.001) # type: ignore


def test_channel_command_processor_basic():
"""Test basic interactions with the channel-command processor."""
Expand Down

0 comments on commit 33c8873

Please sign in to comment.