BDPotentiometer 0.0.2__tar.gz → 0.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {BDPotentiometer-0.0.2/src/BDPotentiometer.egg-info → BDPotentiometer-0.0.3}/PKG-INFO +9 -2
  2. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/src/BDPotentiometer/__helpers.py +31 -14
  3. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/src/BDPotentiometer/__init__.py +1 -1
  4. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/src/BDPotentiometer/digital_potentiometer.py +54 -7
  5. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/src/BDPotentiometer/digital_wiper.py +32 -12
  6. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/src/BDPotentiometer/mcp4xxx/mcp4xx1.py +1 -1
  7. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/src/BDPotentiometer/mcp4xxx/mcp4xxx.py +1 -1
  8. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/src/BDPotentiometer/potentiometer.py +23 -17
  9. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3/src/BDPotentiometer.egg-info}/PKG-INFO +9 -2
  10. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/src/BDPotentiometer.egg-info/SOURCES.txt +0 -4
  11. BDPotentiometer-0.0.3/tests/test_version.py +51 -0
  12. BDPotentiometer-0.0.2/tests/test_digital_potentiometer.py +0 -289
  13. BDPotentiometer-0.0.2/tests/test_digital_wiper.py +0 -271
  14. BDPotentiometer-0.0.2/tests/test_helpers.py +0 -186
  15. BDPotentiometer-0.0.2/tests/test_potentiometer.py +0 -210
  16. BDPotentiometer-0.0.2/tests/test_version.py +0 -29
  17. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/LICENSE +0 -0
  18. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/README.md +0 -0
  19. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/pyproject.toml +0 -0
  20. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/setup.cfg +0 -0
  21. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/src/BDPotentiometer/__gpiozero_helpers.py +0 -0
  22. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/src/BDPotentiometer/mcp4xxx/__init__.py +0 -0
  23. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/src/BDPotentiometer/mcp4xxx/mcp4xx2.py +0 -0
  24. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/src/BDPotentiometer.egg-info/dependency_links.txt +0 -0
  25. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/src/BDPotentiometer.egg-info/requires.txt +0 -0
  26. {BDPotentiometer-0.0.2 → BDPotentiometer-0.0.3}/src/BDPotentiometer.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: BDPotentiometer
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: BD Digital Potentiometer
5
5
  Author-email: Anton Bondarenko <bond.anton@gmail.com>
6
6
  License: MIT License
@@ -32,10 +32,17 @@ Classifier: License :: OSI Approved :: MIT License
32
32
  Classifier: Operating System :: OS Independent
33
33
  Requires-Python: >=3.9
34
34
  Description-Content-Type: text/markdown
35
+ License-File: LICENSE
36
+ Requires-Dist: gpiozero>=1.6.2
35
37
  Provides-Extra: dev
38
+ Requires-Dist: gpiozero>=1.6.2; extra == "dev"
39
+ Requires-Dist: numpy; extra == "dev"
36
40
  Provides-Extra: test
41
+ Requires-Dist: flake8; extra == "test"
42
+ Requires-Dist: pytest; extra == "test"
43
+ Requires-Dist: numpy; extra == "test"
37
44
  Provides-Extra: lint
38
- License-File: LICENSE
45
+ Requires-Dist: pylint; extra == "lint"
39
46
 
40
47
  # BDPotentiometer
41
48
 
@@ -1,19 +1,29 @@
1
1
  """This module contains internal helper functions and not supposed to be used outside."""
2
2
 
3
3
  from numbers import Real, Integral
4
+ from math import isnan
4
5
  from typing import Union, Callable
5
6
 
6
7
 
7
8
  def check_number(num: Union[float, int, Real]) -> Union[float, int]:
8
9
  """
9
10
  Checks if argument is a real number.
11
+ check_number does not allow argument to be `+inf` or `-inf`, or `NaN`.
12
+ Special float numbers `+0.0` and `-0.0` will be converted to just 0.0
13
+
10
14
  :param num: variable to check.
11
15
  :return: Unchanged `num` as float or int or raises ValueError if num is not a real number.
12
16
  """
13
17
  if not isinstance(num, Real):
14
- raise ValueError(f"Expected integer or float number, got {type(num)}")
18
+ raise TypeError(f"Expected integer or float number, got {type(num)}")
15
19
  if isinstance(num, Integral):
16
20
  return int(num)
21
+ if num in [float("inf"), float("-inf")]:
22
+ raise ValueError(f"Expected integer or float number, got {num}")
23
+ if isnan(num):
24
+ raise ValueError("Expected integer or float number, not NaN")
25
+ if num == 0:
26
+ return 0.0
17
27
  return float(num)
18
28
 
19
29
 
@@ -57,11 +67,11 @@ def check_positive(num: Union[float, int]) -> Union[float, int]:
57
67
  return num
58
68
 
59
69
 
60
- def coerce(
70
+ def clamp(
61
71
  value: Union[float, int], min_value: Union[float, int], max_value: Union[float, int]
62
72
  ) -> Union[float, int]:
63
73
  """
64
- Coerce `value` to given range between `min_value` and `max_value`.
74
+ Clamp `value` to given range between `min_value` and `max_value`.
65
75
 
66
76
  :param value: Either float or int number to be coerced.
67
77
  :param min_value: Lower coerce range boundary.
@@ -74,14 +84,18 @@ def coerce(
74
84
  if min_value > max_value:
75
85
  min_value, max_value = max_value, min_value
76
86
  if value < min_value:
87
+ if isinstance(value, float):
88
+ return float(min_value)
77
89
  return min_value
78
90
  if value > max_value:
91
+ if isinstance(value, float):
92
+ return float(max_value)
79
93
  return max_value
80
94
  return value
81
95
 
82
96
 
83
97
  def build_tuple(
84
- value: Union[float, int, tuple, list],
98
+ value: Union[float, int, tuple[float, ...], list[float, ...]],
85
99
  num: Union[int, float],
86
100
  func: Union[Callable, None] = None,
87
101
  ) -> tuple[float, ...]:
@@ -103,24 +117,24 @@ def build_tuple(
103
117
  return val
104
118
 
105
119
  if not isinstance(value, (float, int, tuple, list)):
106
- raise ValueError("Argument should be a number or list or tuple of numbers")
120
+ raise TypeError("Argument should be a number or list or tuple of numbers")
107
121
  num = check_integer(check_positive(num))
108
122
  if func is not None:
109
123
  if not callable(func):
110
- raise ValueError("function must be callable or None")
124
+ raise TypeError("function must be callable or None")
111
125
  else:
112
126
  func = identity
113
127
 
114
- if isinstance(value, (float, int)):
115
- return tuple(float(func(value)) for _ in range(num))
116
128
  if isinstance(value, (list, tuple)):
117
129
  if len(value) != num:
118
130
  raise ValueError(f"A tuple or list of length {num} expected")
119
- return tuple(float(func(value_i)) for value_i in value)
131
+ return tuple(float(func(check_number(value_i))) for value_i in value)
132
+ value = check_number(value)
133
+ return tuple(float(func(value)) for _ in range(num))
120
134
 
121
135
 
122
136
  def adjust_tuple(
123
- value: Union[tuple[float], list[float]], num: int, default_value: float
137
+ value: Union[tuple[float, ...], list[float, ...]], num: int, default_value: float
124
138
  ) -> tuple[float, ...]:
125
139
  """
126
140
  Adjusts tuple or list of floats to a given length `num` and returns a tuple.
@@ -133,11 +147,14 @@ def adjust_tuple(
133
147
  :return: A tuple of floats of length `num`.
134
148
  """
135
149
  if not isinstance(value, (tuple, list)):
136
- raise ValueError("Argument should be a list or a tuple of numbers")
150
+ raise TypeError("Argument should be a list or a tuple of numbers")
137
151
  num = check_integer(check_positive(num))
138
152
  default_value = float(check_number(default_value))
139
153
  if len(value) < num:
140
- return tuple(list(value) + [default_value] * (num - len(value)))
154
+ return tuple(
155
+ [float(check_number(value_i)) for value_i in value]
156
+ + [default_value] * (num - len(value))
157
+ )
141
158
  if len(value) > num:
142
- return tuple(value)[:num]
143
- return tuple(value)
159
+ return tuple(float(check_number(value[i])) for i in range(num))
160
+ return tuple(float(check_number(value_i)) for value_i in value)
@@ -2,7 +2,7 @@
2
2
 
3
3
  # pylint: disable=invalid-name
4
4
 
5
- __version__ = "0.0.2"
5
+ __version__ = "0.0.3"
6
6
 
7
7
  from .potentiometer import Potentiometer
8
8
  from .digital_wiper import DigitalWiper, SpiDigitalWiper
@@ -200,6 +200,51 @@ class DigitalPotentiometerDevice:
200
200
  for i in range(self.channels_num):
201
201
  self.channels[i].value = value[i]
202
202
 
203
+ def set_invert(self, channel: Union[int, str] = 0, invert: bool = False) -> int:
204
+ """
205
+ Set the invert property for a given channel.
206
+
207
+ :param channel: Channel number or label (int | str)
208
+ :param invert: bool.
209
+ :return: Wiper position value as int.
210
+ """
211
+ channel_number = self._get_channel_number_by_label_or_id(channel)
212
+ if channel_number is None:
213
+ raise ValueError(f"Channel {channel} not found.")
214
+ self.channels[channel_number].invert = invert
215
+ return self.channels[channel_number].value
216
+
217
+ def get_invert(self, channel: Union[int, str] = 0) -> bool:
218
+ """
219
+ Get the invert property value for a given channel.
220
+
221
+ :param channel: Channel number or label (int | str)
222
+ :return: invert property value as bool.
223
+ """
224
+ channel_number = self._get_channel_number_by_label_or_id(channel)
225
+ if channel_number is None:
226
+ raise ValueError(f"Channel {channel} not found.")
227
+ return self.channels[channel_number].invert
228
+
229
+ @property
230
+ def invert(self) -> tuple[bool, ...]:
231
+ """
232
+ Tuple of the invert property of channels.
233
+
234
+ :return: Invert property values for all channels as a tuple of booleans.
235
+ """
236
+ return tuple(wiper.invert for _, wiper in self.channels.items())
237
+
238
+ @invert.setter
239
+ def invert(self, invert: Union[list[bool], tuple[bool, ...]]) -> None:
240
+ if len(invert) != self.channels_num:
241
+ raise ValueError(
242
+ f"A tuple or list of length {self.channels_num} is expected."
243
+ )
244
+ for channel, wiper in self.channels.items():
245
+ wiper.invert = invert[channel]
246
+
247
+
203
248
  def set_r_wb(self, channel: Union[int, str] = 0, resistance: float = 0) -> int:
204
249
  """
205
250
  Set the resistance for given channel between B and W terminals
@@ -384,19 +429,21 @@ class DigitalPotentiometerDevice:
384
429
  for channel, wiper in self.channels.items():
385
430
  wiper.r_load = resistance[channel]
386
431
 
387
- def set_voltage_in(self, channel: Union[int, str] = 0, voltage: float = 0.0) -> int:
432
+ def set_voltage_in(
433
+ self, channel: Union[int, str] = 0, voltage: float = 0.0
434
+ ) -> float:
388
435
  """
389
436
  Set input voltage for a given channel number.
390
437
 
391
438
  :param channel: Channel number (int).
392
- :param voltage: Voltage (float).
393
- :return: Wiper position value (int).
439
+ :param voltage: Requested input voltage (float).
440
+ :return: Set input voltage (float)
394
441
  """
395
442
  channel_number = self._get_channel_number_by_label_or_id(channel)
396
443
  if channel_number is None:
397
444
  raise ValueError(f"Channel {channel} not found.")
398
445
  self.channels[channel_number].voltage_in = voltage
399
- return self.channels[channel_number].value
446
+ return self.channels[channel_number].voltage_in
400
447
 
401
448
  def get_voltage_in(self, channel: Union[int, str] = 0) -> float:
402
449
  """
@@ -430,19 +477,19 @@ class DigitalPotentiometerDevice:
430
477
 
431
478
  def set_voltage_out(
432
479
  self, channel: Union[int, str] = 0, voltage: float = 0.0
433
- ) -> int:
480
+ ) -> float:
434
481
  """
435
482
  Set voltage at wiper for a given channel number.
436
483
 
437
484
  :param channel: Channel number (int).
438
485
  :param voltage: Voltage requested (float).
439
- :return: Wiper position value (int).
486
+ :return: Actual output voltage closest to requested (float).
440
487
  """
441
488
  channel_number = self._get_channel_number_by_label_or_id(channel)
442
489
  if channel_number is None:
443
490
  raise ValueError(f"Channel {channel} not found.")
444
491
  self.channels[channel_number].voltage_out = voltage
445
- return self.channels[channel_number].value
492
+ return self.channels[channel_number].voltage_out
446
493
 
447
494
  def get_voltage_out(self, channel: Union[int, str] = 0) -> float:
448
495
  """
@@ -5,7 +5,7 @@ from typing import Union
5
5
  from gpiozero import SPI
6
6
 
7
7
  from .potentiometer import Potentiometer
8
- from .__helpers import check_integer, check_positive, check_not_negative, coerce
8
+ from .__helpers import check_integer, check_positive, check_not_negative, clamp
9
9
 
10
10
 
11
11
  class DigitalWiper:
@@ -22,23 +22,30 @@ class DigitalWiper:
22
22
  self,
23
23
  potentiometer: Potentiometer,
24
24
  max_value: int = 128,
25
+ invert: bool = False,
25
26
  parameters_locked: bool = False,
26
27
  ) -> None:
27
- self.__locked: bool = bool(parameters_locked)
28
- self.__potentiometer = potentiometer
28
+ self.__parameters_locked: bool = bool(parameters_locked)
29
+ if isinstance(potentiometer, Potentiometer):
30
+ self.__potentiometer: Potentiometer = potentiometer
31
+ else:
32
+ raise TypeError(
33
+ f"Expected an instance of Potentiometer class, got {type(potentiometer)}"
34
+ )
29
35
  self.__channel: int = 0
36
+ self.__invert: bool = bool(invert)
30
37
  self.__max_value: int = check_integer(check_positive(max_value))
31
38
  self.__value: int = 0
32
39
  self.read()
33
40
 
34
41
  @property
35
- def locked(self) -> bool:
42
+ def parameters_locked(self) -> bool:
36
43
  """
37
44
  Check if parameters of the wiper are locked.
38
45
 
39
46
  :return: True if locked and False otherwise
40
47
  """
41
- return self.__locked
48
+ return self.__parameters_locked
42
49
 
43
50
  @property
44
51
  def potentiometer(self) -> Potentiometer:
@@ -57,6 +64,14 @@ class DigitalWiper:
57
64
  def channel(self, channel: int) -> None:
58
65
  self.__channel = check_integer(check_not_negative(channel))
59
66
 
67
+ @property
68
+ def invert(self) -> bool:
69
+ return self.__invert
70
+
71
+ @invert.setter
72
+ def invert(self, invert: bool) -> None:
73
+ self.__invert = bool(invert)
74
+
60
75
  @property
61
76
  def min_value(self) -> int:
62
77
  """
@@ -76,8 +91,9 @@ class DigitalWiper:
76
91
 
77
92
  @max_value.setter
78
93
  def max_value(self, max_value: int) -> None:
79
- if not self.locked:
80
- self.__max_value = check_integer(check_positive(max_value))
94
+ new_value = check_integer(check_positive(max_value))
95
+ if not self.parameters_locked:
96
+ self.__max_value = new_value
81
97
  self.value = self.__value
82
98
 
83
99
  def _set_value(self, value: int) -> int:
@@ -87,7 +103,9 @@ class DigitalWiper:
87
103
  :param value: Requested value as int.
88
104
  :return: Value actually set as int.
89
105
  """
90
- value = int(round(coerce(value, 0, self.max_value)))
106
+ value = int(round(clamp(value, 0, self.max_value)))
107
+ if self.invert:
108
+ return self.max_value - value
91
109
  return value
92
110
 
93
111
  def _read_value(self) -> int:
@@ -112,11 +130,13 @@ class DigitalWiper:
112
130
  :return: Wiper position value (int).
113
131
  """
114
132
  self.read()
133
+ if self.invert:
134
+ return self.max_value - self.__value
115
135
  return self.__value
116
136
 
117
137
  @value.setter
118
138
  def value(self, value: int) -> None:
119
- value = check_integer(coerce(value, 0, self.max_value))
139
+ value = clamp(check_integer(value), 0, self.max_value)
120
140
  data = self._set_value(value)
121
141
  self.__value = data
122
142
 
@@ -130,7 +150,7 @@ class DigitalWiper:
130
150
 
131
151
  @value_relative.setter
132
152
  def value_relative(self, value: float) -> None:
133
- value = coerce(value, 0, 1)
153
+ value = clamp(value, 0, 1)
134
154
  value_int = int(round(value * self.max_value))
135
155
  self.value = value_int
136
156
 
@@ -141,7 +161,7 @@ class DigitalWiper:
141
161
 
142
162
  :return: Resistance between terminals B and W (float).
143
163
  """
144
- return self.potentiometer.r_wb(self.value / self.max_value)
164
+ return self.potentiometer.r_wb(self.value_relative)
145
165
 
146
166
  @r_wb.setter
147
167
  def r_wb(self, resistance: float) -> None:
@@ -156,7 +176,7 @@ class DigitalWiper:
156
176
 
157
177
  :return: Resistance between terminals A and W (float).
158
178
  """
159
- return self.potentiometer.r_wa(self.value / self.max_value)
179
+ return self.potentiometer.r_wa(self.value_relative)
160
180
 
161
181
  @r_wa.setter
162
182
  def r_wa(self, resistance: float) -> None:
@@ -68,7 +68,7 @@ class MCP4251(MCP4xx1):
68
68
 
69
69
 
70
70
  class MCP4261(MCP4xx1):
71
- """8-bit, dual channel pot with volatile wiper"""
71
+ """8-bit, dual channel pot with non-volatile wiper"""
72
72
 
73
73
  def __init__(self, r_ab: float = 10e3, **spi_args) -> None:
74
74
  super().__init__(r_ab=r_ab, max_value=256, channels=2, **spi_args)
@@ -107,7 +107,7 @@ class MCP4xxxPotentiometer(Potentiometer):
107
107
 
108
108
  def __init__(self, r_ab: float, rheostat: bool = False) -> None:
109
109
  r_ab = _coerce_r_ab(r_ab)
110
- super().__init__(r_ab=r_ab, r_w=75, rheostat=rheostat, locked=True)
110
+ super().__init__(r_ab=r_ab, r_w=75, rheostat=rheostat, parameters_locked=True)
111
111
 
112
112
 
113
113
  class MCP4xxxWiper(SpiDigitalWiper):
@@ -2,7 +2,7 @@
2
2
 
3
3
  import math as m
4
4
 
5
- from .__helpers import coerce, check_number, check_positive, check_not_negative
5
+ from .__helpers import clamp, check_number, check_positive, check_not_negative
6
6
 
7
7
 
8
8
  class Potentiometer:
@@ -78,11 +78,15 @@ class Potentiometer:
78
78
  # pylint: disable=too-many-instance-attributes
79
79
 
80
80
  def __init__(
81
- self, r_ab: float, r_w: float = 0, rheostat: bool = False, locked: bool = False
81
+ self,
82
+ r_ab: float,
83
+ r_w: float = 0,
84
+ rheostat: bool = False,
85
+ parameters_locked: bool = False,
82
86
  ) -> None:
83
- self.__locked: bool = bool(locked)
84
- self.__r_ab: float = check_positive(r_ab)
85
- self.__r_w: float = check_not_negative(r_w)
87
+ self.__parameters_locked: bool = bool(parameters_locked)
88
+ self.__r_ab: float = float(check_positive(r_ab))
89
+ self.__r_w: float = float(check_not_negative(r_w))
86
90
  self.__rheostat: bool = bool(rheostat)
87
91
 
88
92
  self.__r_lim: float = 0
@@ -100,8 +104,8 @@ class Potentiometer:
100
104
 
101
105
  @r_ab.setter
102
106
  def r_ab(self, resistance: float) -> None:
103
- if not self.locked:
104
- self.__r_ab = check_positive(resistance)
107
+ if not self.parameters_locked:
108
+ self.__r_ab = float(check_positive(resistance))
105
109
 
106
110
  @property
107
111
  def r_w(self) -> float:
@@ -114,17 +118,17 @@ class Potentiometer:
114
118
 
115
119
  @r_w.setter
116
120
  def r_w(self, r_w: float) -> None:
117
- if not self.locked:
121
+ if not self.parameters_locked:
118
122
  self.__r_w = float(check_not_negative(r_w))
119
123
 
120
124
  @property
121
- def locked(self) -> bool:
125
+ def parameters_locked(self) -> bool:
122
126
  """
123
127
  Check if parameters of the potentiometer are locked.
124
128
 
125
129
  :return: True if locked and False otherwise
126
130
  """
127
- return self.__locked
131
+ return self.__parameters_locked
128
132
 
129
133
  @property
130
134
  def rheostat(self) -> bool:
@@ -143,7 +147,7 @@ class Potentiometer:
143
147
 
144
148
  @r_lim.setter
145
149
  def r_lim(self, r_lim: float) -> None:
146
- self.__r_lim = check_not_negative(r_lim)
150
+ self.__r_lim = float(check_not_negative(r_lim))
147
151
 
148
152
  @property
149
153
  def r_load(self) -> float:
@@ -152,7 +156,7 @@ class Potentiometer:
152
156
 
153
157
  @r_load.setter
154
158
  def r_load(self, r_load: float) -> None:
155
- self.__r_load = check_not_negative(r_load)
159
+ self.__r_load = float(check_not_negative(r_load))
156
160
 
157
161
  @property
158
162
  def voltage_in(self) -> float:
@@ -165,7 +169,7 @@ class Potentiometer:
165
169
 
166
170
  @voltage_in.setter
167
171
  def voltage_in(self, voltage: float) -> None:
168
- self.__voltage_in = check_number(voltage)
172
+ self.__voltage_in = float(check_number(voltage))
169
173
 
170
174
  def r_wa(self, wiper_position: float) -> float:
171
175
  """
@@ -175,7 +179,7 @@ class Potentiometer:
175
179
  :param wiper_position: Wiper position in the range [0: 1]
176
180
  :return: Resistance between terminals A and W (float).
177
181
  """
178
- wiper_position = coerce(wiper_position, 0, 1)
182
+ wiper_position = clamp(wiper_position, 0, 1)
179
183
  return self.r_w + (1 - wiper_position) * self.r_ab
180
184
 
181
185
  def r_wb(self, wiper_position: float) -> float:
@@ -186,7 +190,7 @@ class Potentiometer:
186
190
  :param wiper_position: Wiper position in the range [0: 1]
187
191
  :return: Resistance between terminals B and W (float).
188
192
  """
189
- wiper_position = coerce(wiper_position, 0, 1)
193
+ wiper_position = clamp(wiper_position, 0, 1)
190
194
  return self.r_w + wiper_position * self.r_ab
191
195
 
192
196
  def r_wa_to_position(self, r_wa: float) -> float:
@@ -197,7 +201,7 @@ class Potentiometer:
197
201
  :param r_wa: Resistance between terminals A and W (float)
198
202
  :return: Wiper position in the range [0: 1] (float).
199
203
  """
200
- r_wa = coerce(r_wa, self.r_w, self.r_w + self.r_ab)
204
+ r_wa = clamp(r_wa, self.r_w, self.r_w + self.r_ab)
201
205
  return 1 - (r_wa - self.r_w) / self.r_ab
202
206
 
203
207
  def r_wb_to_position(self, r_wb: float) -> float:
@@ -208,7 +212,7 @@ class Potentiometer:
208
212
  :param r_wb: Resistance between terminals B and W (float)
209
213
  :return: Wiper position in the range [0: 1] (float).
210
214
  """
211
- r_wb = coerce(r_wb, self.r_w, self.r_w + self.r_ab)
215
+ r_wb = clamp(r_wb, self.r_w, self.r_w + self.r_ab)
212
216
  return (r_wb - self.r_w) / self.r_ab
213
217
 
214
218
  def voltage_out(self, wiper_position: float) -> float:
@@ -218,6 +222,7 @@ class Potentiometer:
218
222
  :param wiper_position: Wiper position as float number between 0 and 1.
219
223
  :return: Voltage (float).
220
224
  """
225
+ wiper_position = clamp(wiper_position, 0, 1)
221
226
  if self.rheostat:
222
227
  r_total = self.r_load + self.r_lim + self.r_wb(wiper_position)
223
228
  return self.voltage_in * self.r_load / r_total
@@ -234,6 +239,7 @@ class Potentiometer:
234
239
  :param voltage_out: Output voltage (float).
235
240
  :return: Wiper position as float number between 0 and 1.
236
241
  """
242
+ voltage_out = check_number(voltage_out)
237
243
  if voltage_out == 0 or self.r_load == 0 or self.voltage_in == 0:
238
244
  return 0
239
245
  if (self.voltage_in / voltage_out) / abs(self.voltage_in / voltage_out) < 0:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: BDPotentiometer
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: BD Digital Potentiometer
5
5
  Author-email: Anton Bondarenko <bond.anton@gmail.com>
6
6
  License: MIT License
@@ -32,10 +32,17 @@ Classifier: License :: OSI Approved :: MIT License
32
32
  Classifier: Operating System :: OS Independent
33
33
  Requires-Python: >=3.9
34
34
  Description-Content-Type: text/markdown
35
+ License-File: LICENSE
36
+ Requires-Dist: gpiozero>=1.6.2
35
37
  Provides-Extra: dev
38
+ Requires-Dist: gpiozero>=1.6.2; extra == "dev"
39
+ Requires-Dist: numpy; extra == "dev"
36
40
  Provides-Extra: test
41
+ Requires-Dist: flake8; extra == "test"
42
+ Requires-Dist: pytest; extra == "test"
43
+ Requires-Dist: numpy; extra == "test"
37
44
  Provides-Extra: lint
38
- License-File: LICENSE
45
+ Requires-Dist: pylint; extra == "lint"
39
46
 
40
47
  # BDPotentiometer
41
48
 
@@ -16,8 +16,4 @@ src/BDPotentiometer/mcp4xxx/__init__.py
16
16
  src/BDPotentiometer/mcp4xxx/mcp4xx1.py
17
17
  src/BDPotentiometer/mcp4xxx/mcp4xx2.py
18
18
  src/BDPotentiometer/mcp4xxx/mcp4xxx.py
19
- tests/test_digital_potentiometer.py
20
- tests/test_digital_wiper.py
21
- tests/test_helpers.py
22
- tests/test_potentiometer.py
23
19
  tests/test_version.py
@@ -0,0 +1,51 @@
1
+ """ Test package version is correctly set """
2
+ import unittest
3
+
4
+ try:
5
+ from src.BDPotentiometer import __version__ as version
6
+ except ModuleNotFoundError:
7
+ from BDPotentiometer import __version__ as version
8
+
9
+
10
+ class TestVersion(unittest.TestCase):
11
+ """
12
+ Test _version.py has correct version number assigned to global __version__ variable.
13
+ """
14
+
15
+ def test_check_version_numbering(self) -> None:
16
+ """
17
+ Version must be string with three integers separated by dot.
18
+ Example: `0.1.4` or `2.0.0`.
19
+ All three digits must not negative and must not be equal to zero at the same time.
20
+ """
21
+ # Assert version is a string.
22
+ self.assertTrue(isinstance(version, str))
23
+
24
+ # Assert no extra space present. Example: " 0.1.1 " is not correct, "0.1.1" is correct.
25
+ self.assertEqual(len(version), len(version.strip()))
26
+
27
+ # Assert all three parts are present
28
+ v_l = version.split(".")
29
+ self.assertEqual(len(v_l), 3)
30
+
31
+ # Assert no extra space present in each part.
32
+ # Example: "0. 1 .1" is not correct, "0.1.1" is correct
33
+ for i in range(3):
34
+ self.assertEqual(len(v_l[i]), len(v_l[i].strip()))
35
+
36
+ # Convert all parts of the version to int values
37
+ v_maj = int(v_l[0])
38
+ v_min = int(v_l[1])
39
+ v_patch = int(v_l[2])
40
+
41
+ # Assert all version numbers are not negative
42
+ self.assertTrue(v_maj >= 0)
43
+ self.assertTrue(v_min >= 0)
44
+ self.assertTrue(v_patch >= 0)
45
+
46
+ # Assert all version numbers are not equal to zero at the same time
47
+ self.assertTrue(v_maj + v_min + v_patch > 0)
48
+
49
+
50
+ if __name__ == "__main__":
51
+ unittest.main()