tinkerforge-async 1.5.3__tar.gz → 1.6.1__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 (42) hide show
  1. {tinkerforge_async-1.5.3/tinkerforge_async.egg-info → tinkerforge_async-1.6.1}/PKG-INFO +11 -11
  2. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/README.md +5 -4
  3. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/pyproject.toml +7 -6
  4. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/_version.py +1 -1
  5. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/async_event_bus.py +2 -2
  6. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/brick_master.py +7 -7
  7. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_ambient_light_v2.py +1 -1
  8. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_ambient_light_v3.py +2 -2
  9. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_analog_in.py +1 -1
  10. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_barometer.py +1 -1
  11. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_barometer_v2.py +4 -4
  12. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_humidity.py +1 -1
  13. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_humidity_v2.py +3 -3
  14. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_industrial_dual_analog_in_v2.py +6 -4
  15. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_io16.py +10 -10
  16. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_io4_v2.py +177 -25
  17. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_moisture.py +1 -1
  18. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_ptc.py +1 -1
  19. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_ptc_v2.py +3 -3
  20. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_rs232_v2.py +1 -1
  21. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_segment_display_4x7.py +1 -1
  22. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_segment_display_4x7_v2.py +1 -1
  23. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_temperature.py +1 -1
  24. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_temperature_v2.py +2 -2
  25. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_thermocouple_v2.py +2 -2
  26. tinkerforge_async-1.6.1/tinkerforge_async/helper.py +42 -0
  27. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1/tinkerforge_async.egg-info}/PKG-INFO +11 -11
  28. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async.egg-info/SOURCES.txt +1 -0
  29. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/LICENSE +0 -0
  30. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/setup.cfg +0 -0
  31. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/setup.py +0 -0
  32. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/__init__.py +0 -0
  33. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_industrial_ptc.py +0 -0
  34. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_isolator.py +0 -0
  35. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/bricklet_motion_detector_v2.py +0 -0
  36. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/device_factory.py +0 -0
  37. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/devices.py +0 -0
  38. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/ip_connection.py +0 -0
  39. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async/ip_connection_helper.py +0 -0
  40. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async.egg-info/dependency_links.txt +0 -0
  41. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async.egg-info/requires.txt +0 -0
  42. {tinkerforge_async-1.5.3 → tinkerforge_async-1.6.1}/tinkerforge_async.egg-info/top_level.txt +0 -0
@@ -1,29 +1,27 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: tinkerforge-async
3
- Version: 1.5.3
3
+ Version: 1.6.1
4
4
  Summary: Python3 AsyncIO Tinkerforge driver
5
5
  Author-email: Patrick Baus <patrick.baus@physik.tu-darmstadt.de>
6
- License: GNU General Public License v3 (GPLv3)
6
+ License-Expression: GPL-3.0-or-later
7
7
  Project-URL: Homepage, https://github.com/PatrickBaus/tinkerforge_async
8
8
  Project-URL: Bug Tracker, https://github.com/PatrickBaus/tinkerforge_async/issues
9
9
  Project-URL: Download, https://github.com/PatrickBaus/tinkerforge_async/releases
10
10
  Keywords: IoT,Tinkerforge,API
11
11
  Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.7
13
- Classifier: Programming Language :: Python :: 3.8
14
12
  Classifier: Programming Language :: Python :: 3.9
15
13
  Classifier: Programming Language :: Python :: 3.10
16
14
  Classifier: Programming Language :: Python :: 3.11
17
15
  Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
18
17
  Classifier: Development Status :: 5 - Production/Stable
19
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
20
18
  Classifier: Operating System :: OS Independent
21
19
  Classifier: Intended Audience :: Developers
22
20
  Classifier: Intended Audience :: Science/Research
23
21
  Classifier: Natural Language :: English
24
22
  Classifier: Topic :: Home Automation
25
23
  Classifier: Topic :: Scientific/Engineering
26
- Requires-Python: >=3.7
24
+ Requires-Python: >=3.9
27
25
  Description-Content-Type: text/markdown
28
26
  License-File: LICENSE
29
27
  Requires-Dist: typing-extensions; python_version < "3.11"
@@ -44,6 +42,7 @@ Requires-Dist: mypy; extra == "test"
44
42
  Requires-Dist: pylint; extra == "test"
45
43
  Requires-Dist: pytest; extra == "test"
46
44
  Requires-Dist: setuptools; extra == "test"
45
+ Dynamic: license-file
47
46
 
48
47
  [![pylint](../../actions/workflows/pylint.yml/badge.svg)](../../actions/workflows/pylint.yml)
49
48
  [![PyPI](https://img.shields.io/pypi/v/tinkerforge-async)](https://pypi.org/project/tinkerforge-async/)
@@ -106,7 +105,7 @@ pip install tinkerforge-async
106
105
  # Changes made to the API
107
106
  Some design choices of the original Tinkerforge API are overly complex. I therefore replaced them with a simpler and more intuitive approach. A list of things that were changed can be found below:
108
107
  ## Design Changes
109
- - Only Python 3 is supported (3.7+)
108
+ - Only Python 3 is supported (3.9+)
110
109
  - Replaced threads with an async event loop
111
110
  - Completely rewritten how responses from bricks/bricklets work. All setters now have a `response_expected` parameter, which is set to `True` by default. If there is an error when calling the function, it will then raise an exception - either an `AttributeError` if the function is unknown, or a `ValueError` if one or more parameters are invalid.
112
111
 
@@ -188,9 +187,11 @@ Some design choices of the original Tinkerforge API are overly complex. I theref
188
187
  - `IPConnection.register_callback(callback_id, function)`: Replaced by `register_event_queue()`
189
188
  - `IPConnection.connect(host, port=4223, authentication_secret='')`: If `authentication_secret` is not empty, try to authenticate.
190
189
 
191
- - ### [IP Connection](https://www.tinkerforge.com/de/doc/Software/Bricklets/IO4V2_Bricklet_Python.html)
190
+ - ### [IO-4 Bricklet 2.0](https://www.tinkerforge.com/de/doc/Software/Bricklets/IO4V2_Bricklet_Python.html)
192
191
  - `BrickletIO4V2.set_pwm_configuration()` will now take the frequency in units of Hz and the duty cycle is normalized to 1, so it will take a float from [0...1].
193
- - `BrickletIO4V2.get_pwm_configuration()` will return the frequency in units of HZ and the duty cycle is normalized to 1.
192
+ - `BrickletIO4V2.get_pwm_configuration()` will return the frequency in units of Hz and the duty cycle is normalized to 1.
193
+ - `BrickletIO4V2.set_edge_count_callback_configuration()` sets the callback configuration for the edge counter callback. Its secondary ids are in [5...8] for channels [0...3].
194
+ - `BrickletIO4V2.get_edge_count_callback_configuration()` returns the callback configuration for the edge counter callback.
194
195
 
195
196
  - ### [Master Brick](https://www.tinkerforge.com/en/doc/Software/Bricks/Master_Brick_Python.html)
196
197
  - `BrickMaster.set_wifi_configuration()`/`BrickMaster.get_wifi_configuration()` will take/return all ips in natural order
@@ -210,7 +211,6 @@ Some design choices of the original Tinkerforge API are overly complex. I theref
210
211
  - `BrickletThermocoupleV2()` takes an additional parameter to define the type of sensor. The options are of type `BrickletThermocoupleV2.SensorType`. The default is `BrickletPtc.SensorType.TYPE_K`.
211
212
  - `BrickletThermocoupleV2.sensor_type` getter and setter to change the type of sensor used.
212
213
 
213
-
214
214
  - ### [Segment Display 4x7 Bricklet 2.0](https://www.tinkerforge.com/en/doc/Hardware/Bricklets/Segment_Display_4x7_V2.html)
215
215
  - `BrickletSegmentDisplay4x7V2.set_segments()` takes a `list`/`tuple` of 4 `int` instead of digit0, digit1, digit2, digit3. This is the same API as the older [Segment Display 4x7 Bricklet](https://www.tinkerforge.com/en/doc/Hardware/Bricklets/Segment_Display_4x7.html).
216
216
 
@@ -59,7 +59,7 @@ pip install tinkerforge-async
59
59
  # Changes made to the API
60
60
  Some design choices of the original Tinkerforge API are overly complex. I therefore replaced them with a simpler and more intuitive approach. A list of things that were changed can be found below:
61
61
  ## Design Changes
62
- - Only Python 3 is supported (3.7+)
62
+ - Only Python 3 is supported (3.9+)
63
63
  - Replaced threads with an async event loop
64
64
  - Completely rewritten how responses from bricks/bricklets work. All setters now have a `response_expected` parameter, which is set to `True` by default. If there is an error when calling the function, it will then raise an exception - either an `AttributeError` if the function is unknown, or a `ValueError` if one or more parameters are invalid.
65
65
 
@@ -141,9 +141,11 @@ Some design choices of the original Tinkerforge API are overly complex. I theref
141
141
  - `IPConnection.register_callback(callback_id, function)`: Replaced by `register_event_queue()`
142
142
  - `IPConnection.connect(host, port=4223, authentication_secret='')`: If `authentication_secret` is not empty, try to authenticate.
143
143
 
144
- - ### [IP Connection](https://www.tinkerforge.com/de/doc/Software/Bricklets/IO4V2_Bricklet_Python.html)
144
+ - ### [IO-4 Bricklet 2.0](https://www.tinkerforge.com/de/doc/Software/Bricklets/IO4V2_Bricklet_Python.html)
145
145
  - `BrickletIO4V2.set_pwm_configuration()` will now take the frequency in units of Hz and the duty cycle is normalized to 1, so it will take a float from [0...1].
146
- - `BrickletIO4V2.get_pwm_configuration()` will return the frequency in units of HZ and the duty cycle is normalized to 1.
146
+ - `BrickletIO4V2.get_pwm_configuration()` will return the frequency in units of Hz and the duty cycle is normalized to 1.
147
+ - `BrickletIO4V2.set_edge_count_callback_configuration()` sets the callback configuration for the edge counter callback. Its secondary ids are in [5...8] for channels [0...3].
148
+ - `BrickletIO4V2.get_edge_count_callback_configuration()` returns the callback configuration for the edge counter callback.
147
149
 
148
150
  - ### [Master Brick](https://www.tinkerforge.com/en/doc/Software/Bricks/Master_Brick_Python.html)
149
151
  - `BrickMaster.set_wifi_configuration()`/`BrickMaster.get_wifi_configuration()` will take/return all ips in natural order
@@ -163,7 +165,6 @@ Some design choices of the original Tinkerforge API are overly complex. I theref
163
165
  - `BrickletThermocoupleV2()` takes an additional parameter to define the type of sensor. The options are of type `BrickletThermocoupleV2.SensorType`. The default is `BrickletPtc.SensorType.TYPE_K`.
164
166
  - `BrickletThermocoupleV2.sensor_type` getter and setter to change the type of sensor used.
165
167
 
166
-
167
168
  - ### [Segment Display 4x7 Bricklet 2.0](https://www.tinkerforge.com/en/doc/Hardware/Bricklets/Segment_Display_4x7_V2.html)
168
169
  - `BrickletSegmentDisplay4x7V2.set_segments()` takes a `list`/`tuple` of 4 `int` instead of digit0, digit1, digit2, digit3. This is the same API as the older [Segment Display 4x7 Bricklet](https://www.tinkerforge.com/en/doc/Hardware/Bricklets/Segment_Display_4x7.html).
169
170
 
@@ -5,18 +5,19 @@ authors = [
5
5
  ]
6
6
  description = "Python3 AsyncIO Tinkerforge driver"
7
7
  readme = "README.md"
8
- license = { text="GNU General Public License v3 (GPLv3)" }
9
- requires-python = ">=3.7"
8
+ license = "GPL-3.0-or-later"
9
+ license-files = [
10
+ "LICENSE",
11
+ ]
12
+ requires-python = ">=3.9"
10
13
  classifiers = [
11
14
  "Programming Language :: Python :: 3",
12
- "Programming Language :: Python :: 3.7",
13
- "Programming Language :: Python :: 3.8",
14
15
  "Programming Language :: Python :: 3.9",
15
16
  "Programming Language :: Python :: 3.10",
16
17
  "Programming Language :: Python :: 3.11",
17
18
  "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
18
20
  "Development Status :: 5 - Production/Stable",
19
- "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
20
21
  "Operating System :: OS Independent",
21
22
  "Intended Audience :: Developers",
22
23
  "Intended Audience :: Science/Research",
@@ -73,7 +74,7 @@ markers = [
73
74
 
74
75
  [build-system]
75
76
  requires = [
76
- "setuptools>=61.0",
77
+ "setuptools>=77.0",
77
78
  "typing-extensions; python_version <'3.11'",
78
79
  ]
79
80
  build-backend = "setuptools.build_meta"
@@ -1,2 +1,2 @@
1
1
  # pylint: disable=missing-module-docstring
2
- __version__ = "1.5.3"
2
+ __version__ = "1.6.1"
@@ -9,7 +9,7 @@ from typing import Any, AsyncGenerator
9
9
 
10
10
  class EventBus:
11
11
  """
12
- An event bus, that is using the async generator syntax for distributing events.
12
+ An event bus that is using the async generator syntax for distributing events.
13
13
  It uses dicts and sets internally to ensure good performance.
14
14
  """
15
15
 
@@ -18,7 +18,7 @@ class EventBus:
18
18
 
19
19
  async def register(self, event_name: str) -> AsyncGenerator[Any, None]:
20
20
  """
21
- The async generator, that yields events subscribed to `event_name`.
21
+ The async generator that yields events subscribed to `event_name`.
22
22
 
23
23
  Parameters
24
24
  ----------
@@ -1252,7 +1252,7 @@ class BrickMaster(DeviceWithMCU): # pylint: disable=too-many-public-methods
1252
1252
  )
1253
1253
  return unpack_payload(payload, "!")
1254
1254
 
1255
- async def set_wifi_configuration( # pylint: disable=too-many-arguments
1255
+ async def set_wifi_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
1256
1256
  self,
1257
1257
  ssid: str | bytes,
1258
1258
  connection: _WifiConnection | int,
@@ -1334,7 +1334,7 @@ class BrickMaster(DeviceWithMCU): # pylint: disable=too-many-public-methods
1334
1334
  ssid, WifiConnection(connection), ip_addr[::-1], subnet_mask[::-1], gateway[::-1], port
1335
1335
  )
1336
1336
 
1337
- async def set_wifi_encryption( # pylint: disable=too-many-arguments
1337
+ async def set_wifi_encryption( # pylint: disable=too-many-arguments,too-many-positional-arguments
1338
1338
  self,
1339
1339
  encryption: WifiEncryptionMode,
1340
1340
  key_index: int = 1,
@@ -2084,7 +2084,7 @@ class BrickMaster(DeviceWithMCU): # pylint: disable=too-many-public-methods
2084
2084
 
2085
2085
  return unpack_payload(payload, "!")
2086
2086
 
2087
- async def set_ethernet_configuration( # pylint: disable=too-many-arguments
2087
+ async def set_ethernet_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
2088
2088
  self,
2089
2089
  connection: _EthernetConnection,
2090
2090
  ip_address: tuple[int, int, int, int] = (0, 0, 0, 0),
@@ -2510,7 +2510,7 @@ class BrickMaster(DeviceWithMCU): # pylint: disable=too-many-public-methods
2510
2510
 
2511
2511
  return unpack_payload(payload, "64s")
2512
2512
 
2513
- async def set_wifi2_configuration( # pylint: disable=too-many-arguments
2513
+ async def set_wifi2_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
2514
2514
  self,
2515
2515
  port: int = 4223,
2516
2516
  websocket_port: int = 4280,
@@ -2636,7 +2636,7 @@ class BrickMaster(DeviceWithMCU): # pylint: disable=too-many-public-methods
2636
2636
  ap_connected_count,
2637
2637
  )
2638
2638
 
2639
- async def set_wifi2_client_configuration( # pylint: disable=too-many-arguments
2639
+ async def set_wifi2_client_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
2640
2640
  self,
2641
2641
  enable: bool = True,
2642
2642
  ssid: bytes | str = "tinkerforge",
@@ -2809,7 +2809,7 @@ class BrickMaster(DeviceWithMCU): # pylint: disable=too-many-public-methods
2809
2809
  )
2810
2810
  return unpack_payload(payload, "64s")
2811
2811
 
2812
- async def set_wifi2_ap_configuration( # pylint: disable=too-many-arguments
2812
+ async def set_wifi2_ap_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
2813
2813
  self,
2814
2814
  enable: bool = True,
2815
2815
  ssid: bytes | str = "WIFI Extension 2.0 Access Point",
@@ -3026,7 +3026,7 @@ class BrickMaster(DeviceWithMCU): # pylint: disable=too-many-public-methods
3026
3026
  )
3027
3027
  return unpack_payload(payload, "!")
3028
3028
 
3029
- async def set_wifi2_mesh_configuration( # pylint: disable=too-many-arguments
3029
+ async def set_wifi2_mesh_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
3030
3030
  self,
3031
3031
  enable: bool = False,
3032
3032
  root_ip: tuple[int, int, int, int] = (0, 0, 0, 0),
@@ -131,7 +131,7 @@ class BrickletAmbientLightV2(Device):
131
131
 
132
132
  return await self.get_illuminance()
133
133
 
134
- async def set_callback_configuration( # pylint: disable=too-many-arguments,unused-argument
134
+ async def set_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments,unused-argument
135
135
  self,
136
136
  sid: int,
137
137
  period: int = 0,
@@ -124,7 +124,7 @@ class BrickletAmbientLightV3(BrickletWithMCU):
124
124
 
125
125
  return await self.get_illuminance()
126
126
 
127
- async def set_callback_configuration( # pylint: disable=too-many-arguments
127
+ async def set_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
128
128
  self,
129
129
  sid: int,
130
130
  period: int = 0,
@@ -167,7 +167,7 @@ class BrickletAmbientLightV3(BrickletWithMCU):
167
167
  print(unpack_payload(payload, "I"))
168
168
  return self.__value_to_si(unpack_payload(payload, "I"))
169
169
 
170
- async def set_illuminance_callback_configuration( # pylint: disable=too-many-arguments
170
+ async def set_illuminance_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
171
171
  self,
172
172
  period: int = 0,
173
173
  value_has_to_change: bool = False,
@@ -125,7 +125,7 @@ class BrickletAnalogIn(Device):
125
125
  return await self.get_voltage()
126
126
  return await self.get_analog_value()
127
127
 
128
- async def set_callback_configuration( # pylint: disable=too-many-arguments,unused-argument
128
+ async def set_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments,unused-argument
129
129
  self,
130
130
  sid: int,
131
131
  period: int = 0,
@@ -107,7 +107,7 @@ class BrickletBarometer(Device): # pylint: disable=too-many-public-methods
107
107
  return await self.get_air_pressure()
108
108
  return await self.get_altitude()
109
109
 
110
- async def set_callback_configuration( # pylint: disable=too-many-arguments,unused-argument
110
+ async def set_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments,unused-argument
111
111
  self,
112
112
  sid: int,
113
113
  period: int = 0,
@@ -149,7 +149,7 @@ class BrickletBarometerV2(BrickletWithMCU): # pylint: disable=too-many-public-m
149
149
  return await self.get_altitude()
150
150
  return await self.get_temperature()
151
151
 
152
- async def set_callback_configuration( # pylint: disable=too-many-arguments
152
+ async def set_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
153
153
  self,
154
154
  sid: int,
155
155
  period: int = 0,
@@ -199,7 +199,7 @@ class BrickletBarometerV2(BrickletWithMCU): # pylint: disable=too-many-public-m
199
199
  )
200
200
  return self.__air_pressure_sensor_to_si(unpack_payload(payload, "i"))
201
201
 
202
- async def set_air_pressure_callback_configuration( # pylint: disable=too-many-arguments
202
+ async def set_air_pressure_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
203
203
  self,
204
204
  period: int = 0,
205
205
  value_has_to_change: bool = False,
@@ -283,7 +283,7 @@ class BrickletBarometerV2(BrickletWithMCU): # pylint: disable=too-many-public-m
283
283
  )
284
284
  return self.__altitude_sensor_to_si(unpack_payload(payload, "i"))
285
285
 
286
- async def set_altitude_callback_configuration( # pylint: disable=too-many-arguments
286
+ async def set_altitude_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
287
287
  self,
288
288
  period: int = 0,
289
289
  value_has_to_change: bool = False,
@@ -367,7 +367,7 @@ class BrickletBarometerV2(BrickletWithMCU): # pylint: disable=too-many-public-m
367
367
  )
368
368
  return self.__temperature_sensor_to_si(unpack_payload(payload, "i"))
369
369
 
370
- async def set_temperature_callback_configuration( # pylint: disable=too-many-arguments
370
+ async def set_temperature_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
371
371
  self,
372
372
  period: int = 0,
373
373
  value_has_to_change: bool = False,
@@ -97,7 +97,7 @@ class BrickletHumidity(Device):
97
97
  return await self.get_humidity()
98
98
  return await self.get_analog_value()
99
99
 
100
- async def set_callback_configuration( # pylint: disable=too-many-arguments,unused-argument
100
+ async def set_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments,unused-argument
101
101
  self,
102
102
  sid: int,
103
103
  period: int = 0,
@@ -129,7 +129,7 @@ class BrickletHumidityV2(BrickletWithMCU):
129
129
  return await self.get_humidity()
130
130
  return await self.get_temperature()
131
131
 
132
- async def set_callback_configuration( # pylint: disable=too-many-arguments
132
+ async def set_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
133
133
  self,
134
134
  sid: int,
135
135
  period: int = 0,
@@ -176,7 +176,7 @@ class BrickletHumidityV2(BrickletWithMCU):
176
176
  )
177
177
  return self.__humidity_sensor_to_si(unpack_payload(payload, "H"))
178
178
 
179
- async def set_humidity_callback_configuration( # pylint: disable=too-many-arguments
179
+ async def set_humidity_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
180
180
  self,
181
181
  period: int = 0,
182
182
  value_has_to_change: bool = False,
@@ -267,7 +267,7 @@ class BrickletHumidityV2(BrickletWithMCU):
267
267
  )
268
268
  return self.__temperature_sensor_to_si(unpack_payload(payload, "h"))
269
269
 
270
- async def set_temperature_callback_configuration( # pylint: disable=too-many-arguments
270
+ async def set_temperature_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
271
271
  self,
272
272
  period: int = 0,
273
273
  value_has_to_change: bool = False,
@@ -21,7 +21,9 @@ from .devices import (
21
21
  SimpleCallbackConfiguration,
22
22
  )
23
23
  from .devices import ThresholdOption as Threshold
24
- from .devices import _FunctionID
24
+ from .devices import (
25
+ _FunctionID,
26
+ )
25
27
  from .ip_connection_helper import pack_payload, unpack_payload
26
28
 
27
29
  if TYPE_CHECKING:
@@ -149,7 +151,7 @@ class BrickletIndustrialDualAnalogInV2(BrickletWithMCU):
149
151
  return await self.get_voltage(sid)
150
152
  return await self.get_all_voltages()
151
153
 
152
- async def set_callback_configuration( # pylint: disable=too-many-arguments
154
+ async def set_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
153
155
  self,
154
156
  sid: int,
155
157
  period: int = 0,
@@ -197,7 +199,7 @@ class BrickletIndustrialDualAnalogInV2(BrickletWithMCU):
197
199
  )
198
200
  return self.__value_to_si(unpack_payload(payload, "i"))
199
201
 
200
- async def set_voltage_callback_configuration( # pylint: disable=too-many-arguments
202
+ async def set_voltage_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
201
203
  self,
202
204
  channel: int,
203
205
  period: int = 0,
@@ -430,7 +432,7 @@ class BrickletIndustrialDualAnalogInV2(BrickletWithMCU):
430
432
 
431
433
  return LedConfig(unpack_payload(payload, "B"))
432
434
 
433
- async def set_channel_led_status_config( # pylint: disable=too-many-arguments
435
+ async def set_channel_led_status_config( # pylint: disable=too-many-arguments,too-many-positional-arguments
434
436
  self,
435
437
  channel: int,
436
438
  minimum: float | Decimal,
@@ -210,7 +210,7 @@ class BrickletIO16(Device):
210
210
  )
211
211
  return unpack_payload(payload, "B")
212
212
 
213
- async def set_port_configuration( # pylint: disable=too-many-arguments
213
+ async def set_port_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
214
214
  self,
215
215
  port: _Port | str,
216
216
  selection_mask: int,
@@ -278,7 +278,7 @@ class BrickletIO16(Device):
278
278
  )
279
279
  return GetPortConfiguration(*unpack_payload(payload, "B B"))
280
280
 
281
- async def set_callback_configuration( # pylint: disable=too-many-arguments,unused-argument
281
+ async def set_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments,unused-argument
282
282
  self,
283
283
  sid: int,
284
284
  period: int = 0,
@@ -288,16 +288,16 @@ class BrickletIO16(Device):
288
288
  maximum: float | Decimal | None = None,
289
289
  response_expected: bool = True,
290
290
  ) -> None:
291
- port = Port.A if sid < 16 else Port.B
291
+ port = Port.A if sid < 8 else Port.B
292
292
  interrupt_mask = await self.get_port_interrupt(port)
293
- interrupt_mask &= ~(1 << (sid % 16)) # Reset the bit a position sid
294
- interrupt_mask |= int(bool(period)) << (sid % 16) # if period is non-zero, enable the interrupt
293
+ interrupt_mask &= ~(1 << (sid % 8)) # Reset the bit a position sid
294
+ interrupt_mask |= int(bool(period)) << (sid % 8) # if period is non-zero, enable the interrupt
295
295
  await self.set_port_interrupt(port, interrupt_mask)
296
296
 
297
297
  async def get_callback_configuration(self, sid: int) -> AdvancedCallbackConfiguration:
298
- port = Port.A if sid < 16 else Port.B
298
+ port = Port.A if sid < 8 else Port.B
299
299
  interrupt_mask = await self.get_port_interrupt(port)
300
- value = interrupt_mask & (1 << (sid % 16))
300
+ value = interrupt_mask & (1 << (sid % 8))
301
301
  return AdvancedCallbackConfiguration(int(bool(value)), False, None, None, None)
302
302
 
303
303
  async def set_debounce_period(self, debounce_period: int = 100, response_expected: bool = True) -> None:
@@ -360,7 +360,7 @@ class BrickletIO16(Device):
360
360
  )
361
361
  return unpack_payload(payload, "B")
362
362
 
363
- async def set_port_monoflop( # pylint: disable=too-many-arguments
363
+ async def set_port_monoflop( # pylint: disable=too-many-arguments,too-many-positional-arguments
364
364
  self, port: _Port | str, selection_mask: int, value_mask: int, time: int, response_expected: bool = True
365
365
  ) -> None:
366
366
  """
@@ -446,7 +446,7 @@ class BrickletIO16(Device):
446
446
 
447
447
  .. versionadded:: 2.0.3$nbsp;(Plugin)
448
448
  """
449
- assert isinstance(pin, int) and (0 <= pin <= 7)
449
+ assert isinstance(pin, int) and (0 <= pin <= 1)
450
450
 
451
451
  _, payload = await self.ipcon.send_request(
452
452
  device=self,
@@ -497,7 +497,7 @@ class BrickletIO16(Device):
497
497
 
498
498
  .. versionadded:: 2.0.3$nbsp;(Plugin)
499
499
  """
500
- assert isinstance(pin, int) and (0 <= pin <= 7)
500
+ assert isinstance(pin, int) and (0 <= pin <= 1)
501
501
 
502
502
  _, payload = await self.ipcon.send_request(
503
503
  device=self,
@@ -7,9 +7,11 @@ handles conversion of raw units to SI units.
7
7
  # pylint: disable=duplicate-code # Many sensors of different generations have a similar API
8
8
  from __future__ import annotations
9
9
 
10
+ import asyncio
11
+ from asyncio import CancelledError
10
12
  from decimal import Decimal
11
13
  from enum import Enum, unique
12
- from typing import TYPE_CHECKING, AsyncGenerator, NamedTuple
14
+ from typing import TYPE_CHECKING, AsyncGenerator, NamedTuple, TypeAlias, TypedDict
13
15
 
14
16
  from .devices import (
15
17
  AdvancedCallbackConfiguration,
@@ -19,11 +21,14 @@ from .devices import (
19
21
  SimpleCallbackConfiguration,
20
22
  )
21
23
  from .devices import ThresholdOption as Threshold
22
- from .devices import _FunctionID
24
+ from .devices import (
25
+ _FunctionID,
26
+ )
27
+ from .helper import join
23
28
  from .ip_connection_helper import pack_payload, unpack_payload
24
29
 
25
30
  if TYPE_CHECKING:
26
- from .ip_connection import IPConnectionAsync
31
+ from .ip_connection import HeaderPayload, IPConnectionAsync
27
32
 
28
33
 
29
34
  @unique
@@ -35,9 +40,10 @@ class CallbackID(Enum):
35
40
  INPUT_VALUE = 17
36
41
  ALL_INPUT_VALUE = 18
37
42
  MONOFLOP_DONE = 19
43
+ EDGE_COUNT = 20
38
44
 
39
45
 
40
- _CallbackID = CallbackID
46
+ _CallbackID: TypeAlias = CallbackID # pylint: disable=invalid-name
41
47
 
42
48
 
43
49
  @unique
@@ -91,6 +97,14 @@ class EdgeType(Enum):
91
97
  _EdgeType = EdgeType
92
98
 
93
99
 
100
+ class _EdgeCountTaskConfig(TypedDict):
101
+ queue: asyncio.Queue[Event]
102
+ task: asyncio.Task | None
103
+ lock: asyncio.Lock
104
+ period: int
105
+ value_has_to_change: bool
106
+
107
+
94
108
  class GetConfiguration(NamedTuple):
95
109
  direction: Direction
96
110
  value: bool
@@ -117,7 +131,7 @@ class GetPWMConfiguration(NamedTuple):
117
131
  duty_cycle: Decimal
118
132
 
119
133
 
120
- class BrickletIO4V2(BrickletWithMCU):
134
+ class BrickletIO4V2(BrickletWithMCU): # pylint: disable=too-many-public-methods
121
135
  """
122
136
  4-channel digital input/output
123
137
  """
@@ -147,6 +161,17 @@ class BrickletIO4V2(BrickletWithMCU):
147
161
  super().__init__(self.DEVICE_DISPLAY_NAME, uid, ipcon)
148
162
 
149
163
  self.api_version = (2, 0, 0)
164
+ # The queues are used by the pulse counter
165
+ self.__counter_queue: dict[int, _EdgeCountTaskConfig] = {
166
+ channel: {
167
+ "queue": asyncio.Queue(maxsize=1),
168
+ "task": None,
169
+ "lock": asyncio.Lock(),
170
+ "period": 0,
171
+ "value_has_to_change": False,
172
+ }
173
+ for channel in range(4)
174
+ }
150
175
 
151
176
  async def set_value(
152
177
  self, value: tuple[bool, bool, bool, bool] | list[bool], response_expected: bool = True
@@ -271,7 +296,7 @@ class BrickletIO4V2(BrickletWithMCU):
271
296
  direction = Direction(direction)
272
297
  return GetConfiguration(direction, value)
273
298
 
274
- async def set_callback_configuration( # pylint: disable=too-many-arguments,unused-argument
299
+ async def set_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments,unused-argument
275
300
  self,
276
301
  sid: int,
277
302
  period: int = 0,
@@ -281,23 +306,32 @@ class BrickletIO4V2(BrickletWithMCU):
281
306
  maximum: float | Decimal | None = None,
282
307
  response_expected: bool = True,
283
308
  ) -> None:
284
- assert sid in range(5)
309
+ assert sid in range(4 + 1 + 4)
285
310
 
286
311
  if sid in range(4):
287
312
  await self.set_input_value_callback_configuration(sid, period, value_has_to_change, response_expected)
288
- else:
313
+ elif sid == 4:
289
314
  await self.set_all_input_value_callback_configuration(period, value_has_to_change, response_expected)
315
+ elif sid in range(5, 5 + 4):
316
+ await self.set_edge_count_callback_configuration(sid - 5, period, value_has_to_change, response_expected)
290
317
 
291
318
  async def get_callback_configuration(self, sid: int) -> AdvancedCallbackConfiguration:
292
- assert sid in range(5)
319
+ assert sid in range(4 + 1 + 4)
293
320
 
294
321
  if sid in range(4):
295
322
  return AdvancedCallbackConfiguration(
296
323
  *(await self.get_input_value_callback_configuration(sid)), option=None, minimum=None, maximum=None
297
324
  )
298
- return AdvancedCallbackConfiguration(
299
- *(await self.get_all_input_value_callback_configuration()), option=None, minimum=None, maximum=None
300
- )
325
+ if sid == 4:
326
+ return AdvancedCallbackConfiguration(
327
+ *(await self.get_all_input_value_callback_configuration()), option=None, minimum=None, maximum=None
328
+ )
329
+ if sid in range(5, 5 + 4):
330
+ return AdvancedCallbackConfiguration(
331
+ *(await self.get_edge_count_callback_configuration(sid - 5)), option=None, minimum=None, maximum=None
332
+ )
333
+
334
+ raise ValueError(f"Invalid sid: {sid}")
301
335
 
302
336
  async def set_input_value_callback_configuration(
303
337
  self, channel: int, period: int = 0, value_has_to_change: bool = False, response_expected: bool = True
@@ -383,6 +417,53 @@ class BrickletIO4V2(BrickletWithMCU):
383
417
  )
384
418
  return SimpleCallbackConfiguration(*unpack_payload(payload, "I !"))
385
419
 
420
+ async def set_edge_count_callback_configuration( # pylint: disable=unused-argument
421
+ self, channel: int, period: int = 0, value_has_to_change: bool = False, response_expected: bool = True
422
+ ) -> None:
423
+ """
424
+ Enable an edge counter task. This task will feed a queue to be read by calling __read_edge_counter().
425
+ Do note, that the edge counter needs to sync first, so the first value will be available after period * 2.
426
+
427
+ Parameters
428
+ ----------
429
+ channel: int
430
+ The input channel used for edge counting. Must in range(0,3).
431
+ period: int
432
+ Time in ms
433
+ value_has_to_change: bool
434
+ If True, the event will be suppressed if there was no change
435
+ response_expected: bool
436
+ No effect. Parameter is used for compatibility with other callback configuration functions only.
437
+ """
438
+ assert channel in (0, 1, 2, 3)
439
+
440
+ async with self.__counter_queue[channel]["lock"]:
441
+ task = self.__counter_queue[channel]["task"]
442
+ # We either need to create a new task or just kill the existing one
443
+ if task is not None:
444
+ task.cancel()
445
+ try:
446
+ await task
447
+ except CancelledError:
448
+ pass
449
+
450
+ if period > 0:
451
+ self.__counter_queue[channel]["task"] = asyncio.create_task(
452
+ self.__edge_counter_task(channel, period, value_has_to_change)
453
+ )
454
+ else:
455
+ queue = self.__counter_queue[channel]["queue"]
456
+ if not queue.empty(): # The queue has a maximum of 1 element
457
+ queue.get_nowait()
458
+ queue.task_done()
459
+ self.__counter_queue[channel]["period"] = period
460
+ self.__counter_queue[channel]["value_has_to_change"] = value_has_to_change
461
+
462
+ async def get_edge_count_callback_configuration(self, channel: int) -> SimpleCallbackConfiguration:
463
+ period = self.__counter_queue[channel]["period"]
464
+ value_has_to_change = self.__counter_queue[channel]["value_has_to_change"]
465
+ return SimpleCallbackConfiguration(period=period, value_has_to_change=value_has_to_change)
466
+
386
467
  async def set_monoflop(self, channel: int, value: bool, time: int, response_expected: bool = True) -> None:
387
468
  """
388
469
  The first parameter is the desired state of the channel (*true* means output *high*
@@ -574,21 +655,59 @@ class BrickletIO4V2(BrickletWithMCU):
574
655
  frequency, duty_cycle = unpack_payload(payload, "I H")
575
656
  return GetPWMConfiguration(Decimal(frequency) / 10, Decimal(duty_cycle) / 10000)
576
657
 
577
- async def read_events(
658
+ async def __edge_counter_task(self, channel: int, period: int, value_has_to_change: bool) -> None:
659
+ previous_value: int | None = None
660
+ # Throw away the first value, because we need to reset and sync the internal counter of the IO bricklet.
661
+ try:
662
+ await asyncio.gather(self.get_edge_count(channel, reset_counter=True), asyncio.sleep(period / 1000))
663
+ while "not canceled":
664
+ value, _ = await asyncio.gather(
665
+ self.get_edge_count(channel, reset_counter=True), asyncio.sleep(period / 1000)
666
+ )
667
+ queue = self.__counter_queue[channel]["queue"]
668
+ if not value_has_to_change or previous_value != value:
669
+ previous_value = value
670
+ event = Event(self, sid=channel + 5, function_id=CallbackID.EDGE_COUNT, payload=value)
671
+ try:
672
+ queue.put_nowait(event)
673
+ except asyncio.QueueFull:
674
+ queue.get_nowait()
675
+ queue.put_nowait(event)
676
+ except ValueError:
677
+ # raised if the channel is set to output. We will then stop the edge counter callback without notice.
678
+ pass
679
+
680
+ async def __read_edge_counter(self, channel: int) -> AsyncGenerator[Event, None]:
681
+ while "not cancelled":
682
+ queue = self.__counter_queue[channel]["queue"]
683
+ yield await queue.get()
684
+ queue.task_done()
685
+
686
+ async def __read_callback_events(
578
687
  self,
579
- events: tuple[int | _CallbackID, ...] | list[int | _CallbackID] | None = None,
580
- sids: tuple[int, ...] | list[int] | None = None,
688
+ events: set[_CallbackID],
689
+ sids: tuple[int, ...] | list[int],
581
690
  ) -> AsyncGenerator[Event, None]:
582
- assert events is None or sids is None
691
+ """
692
+ This functions connects to the ip connection and retrieves callback events for a given list of events or sids.
693
+ The callback must be enabled prior to registering to this generator.
583
694
 
584
- registered_events = set()
585
- sids = tuple() if sids is None else sids
586
- if events:
587
- for event in events:
588
- registered_events.add(self.CallbackID(event))
695
+ Parameters
696
+ ----------
697
+ events: set of CallbackID
698
+ Any value of CallbackID.INPUT_VALUE, CallbackID.ALL_INPUT_VALUE or CallbackID.MONOFLOP_DONE. Other values
699
+ in the set will be ignored.
700
+ sids: tuple or list of int
701
+ A tuple with ints in range of range(4). Other sids will be ignored.
702
+
703
+ Yields
704
+ -------
705
+ Event
706
+ Events matching the desired sid/event filters
589
707
 
708
+ """
590
709
  if not events and not sids:
591
- registered_events = set(self.CALLBACK_FORMATS.keys())
710
+ return
592
711
 
593
712
  async for header, payload in super()._read_events():
594
713
  try:
@@ -599,19 +718,52 @@ class BrickletIO4V2(BrickletWithMCU):
599
718
 
600
719
  if function_id is CallbackID.INPUT_VALUE:
601
720
  sid, value_has_changed, value = unpack_payload(payload, self.CALLBACK_FORMATS[function_id])
602
- if function_id in registered_events or sid in sids:
721
+ if function_id in events or sid in sids:
603
722
  yield Event(self, sid, function_id, value, value_has_changed)
604
723
  continue
605
724
  elif function_id is CallbackID.MONOFLOP_DONE:
606
725
  sid, value = unpack_payload(payload, self.CALLBACK_FORMATS[function_id])
607
- if function_id in registered_events or sid in sids:
726
+ if function_id in events or sid in sids:
608
727
  yield Event(self, sid, function_id, value)
609
728
  continue
610
729
  else:
611
730
  changed_sids, values = unpack_payload(payload, self.CALLBACK_FORMATS[function_id])
612
- if function_id in registered_events:
731
+ if function_id in events:
613
732
  # Use a special sid for the CallbackID.ALL_INPUT_VALUE, because it returns a tuple
614
733
  yield Event(self, 4, function_id, values, changed_sids)
615
734
  else:
616
735
  for sid in sids:
617
736
  yield Event(self, sid, function_id, values[sid], changed_sids[sid])
737
+
738
+ async def read_events(
739
+ self,
740
+ events: tuple[int | _CallbackID, ...] | list[int | _CallbackID] | None = None,
741
+ sids: tuple[int, ...] | list[int] | None = None,
742
+ ) -> AsyncGenerator[Event, None]:
743
+ assert events is None or sids is None
744
+
745
+ sids = tuple() if sids is None else sids
746
+
747
+ registered_edge_count_sids = tuple(sid for sid in sids if sid in range(5, 5 + 4))
748
+ registered_callback_events: set[_CallbackID] = set()
749
+
750
+ if events:
751
+ for event in events:
752
+ event = self.CallbackID(event)
753
+ # CallbackID.PULSE_COUNT is not a regular callback, so it must be treated special
754
+ if event != CallbackID.EDGE_COUNT:
755
+ registered_callback_events.add(event)
756
+ else:
757
+ registered_edge_count_sids = tuple(range(5, 5 + 4))
758
+ registered_callback_sids = tuple(sid for sid in sids if sid in range(5))
759
+
760
+ # register all callback events if no specific filter is given
761
+ if not events and not sids:
762
+ registered_callback_events = set(self.CALLBACK_FORMATS.keys())
763
+ registered_edge_count_sids = tuple(range(5, 5 + 4))
764
+
765
+ async for res in join(
766
+ self.__read_callback_events(registered_callback_events, registered_callback_sids),
767
+ *(self.__read_edge_counter(sid - 5) for sid in registered_edge_count_sids),
768
+ ):
769
+ yield res
@@ -86,7 +86,7 @@ class BrickletMoisture(Device):
86
86
 
87
87
  return await self.get_moisture_value()
88
88
 
89
- async def set_callback_configuration( # pylint: disable=too-many-arguments,unused-argument
89
+ async def set_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments,unused-argument
90
90
  self,
91
91
  sid: int,
92
92
  period: int = 0,
@@ -181,7 +181,7 @@ class BrickletPtc(Device): # pylint: disable=too-many-public-methods
181
181
  return await self.get_resistance()
182
182
  return await self.is_sensor_connected()
183
183
 
184
- async def set_callback_configuration( # pylint: disable=too-many-arguments,unused-argument
184
+ async def set_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments,unused-argument
185
185
  self,
186
186
  sid: int,
187
187
  period: int = 0,
@@ -178,7 +178,7 @@ class BrickletPtcV2(BrickletWithMCU):
178
178
  return await self.get_resistance()
179
179
  return await self.is_sensor_connected()
180
180
 
181
- async def set_callback_configuration( # pylint: disable=too-many-arguments
181
+ async def set_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
182
182
  self,
183
183
  sid: int,
184
184
  period: int = 0,
@@ -227,7 +227,7 @@ class BrickletPtcV2(BrickletWithMCU):
227
227
  )
228
228
  return self.__value_to_si_temperature(unpack_payload(payload, "i"))
229
229
 
230
- async def set_temperature_callback_configuration( # pylint: disable=too-many-arguments
230
+ async def set_temperature_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
231
231
  self,
232
232
  period: int = 0,
233
233
  value_has_to_change: bool = False,
@@ -313,7 +313,7 @@ class BrickletPtcV2(BrickletWithMCU):
313
313
  )
314
314
  return self.__value_to_si_resistance(unpack_payload(payload, "i"))
315
315
 
316
- async def set_resistance_callback_configuration( # pylint: disable=too-many-arguments
316
+ async def set_resistance_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
317
317
  self,
318
318
  period: int = 0,
319
319
  value_has_to_change: bool = False,
@@ -262,7 +262,7 @@ class BrickletRS232V2(BrickletWithMCU):
262
262
 
263
263
  return unpack_payload(payload, "!")
264
264
 
265
- async def set_configuration( # pylint: disable=too-many-arguments
265
+ async def set_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
266
266
  self,
267
267
  baudrate: int = 115200,
268
268
  parity: _Parity | int = Parity.NONE,
@@ -123,7 +123,7 @@ class BrickletSegmentDisplay4x7(Device):
123
123
  )
124
124
  return GetSegments(*unpack_payload(payload, "4B B !"))
125
125
 
126
- async def start_counter( # pylint: disable=too-many-arguments
126
+ async def start_counter( # pylint: disable=too-many-arguments,too-many-positional-arguments
127
127
  self, value_from: int, value_to: int, increment: int = 1, length: int = 1000, response_expected: bool = True
128
128
  ) -> None:
129
129
  """
@@ -204,7 +204,7 @@ class BrickletSegmentDisplay4x7V2(BrickletWithMCU):
204
204
 
205
205
  return unpack_payload(payload, "!")
206
206
 
207
- async def start_counter( # pylint: disable=too-many-arguments
207
+ async def start_counter( # pylint: disable=too-many-arguments,too-many-positional-arguments
208
208
  self, value_from: int, value_to: int, increment: int, length: int, response_expected: bool = True
209
209
  ) -> None:
210
210
  """
@@ -102,7 +102,7 @@ class BrickletTemperature(Device):
102
102
 
103
103
  return await self.get_temperature()
104
104
 
105
- async def set_callback_configuration( # pylint: disable=too-many-arguments,unused-argument
105
+ async def set_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments,unused-argument
106
106
  self,
107
107
  sid: int,
108
108
  period: int = 0,
@@ -94,7 +94,7 @@ class BrickletTemperatureV2(BrickletWithMCU):
94
94
 
95
95
  return await self.get_temperature()
96
96
 
97
- async def set_callback_configuration( # pylint: disable=too-many-arguments
97
+ async def set_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
98
98
  self,
99
99
  sid: int,
100
100
  period: int = 0,
@@ -133,7 +133,7 @@ class BrickletTemperatureV2(BrickletWithMCU):
133
133
  )
134
134
  return self.__value_to_si(unpack_payload(payload, "h"))
135
135
 
136
- async def set_temperature_callback_configuration( # pylint: disable=too-many-arguments
136
+ async def set_temperature_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
137
137
  self,
138
138
  period: int = 0,
139
139
  value_has_to_change: bool = False,
@@ -182,7 +182,7 @@ class BrickletThermocoupleV2(BrickletWithMCU):
182
182
  return await self.get_error_state()
183
183
  raise ValueError(f"Invalid sid: {sid}. sid must be in (0, 1).")
184
184
 
185
- async def set_callback_configuration( # pylint: disable=too-many-arguments
185
+ async def set_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
186
186
  self,
187
187
  sid: int,
188
188
  period: int = 0,
@@ -226,7 +226,7 @@ class BrickletThermocoupleV2(BrickletWithMCU):
226
226
  )
227
227
  return self.__value_to_si(unpack_payload(payload, "i"))
228
228
 
229
- async def set_temperature_callback_configuration( # pylint: disable=too-many-arguments
229
+ async def set_temperature_callback_configuration( # pylint: disable=too-many-arguments,too-many-positional-arguments
230
230
  self,
231
231
  period: int = 0,
232
232
  value_has_to_change: bool = False,
@@ -0,0 +1,42 @@
1
+ """
2
+ General helper functions used by multiple modules.
3
+ """
4
+
5
+ import asyncio
6
+ from typing import AsyncGenerator, TypeVar
7
+
8
+ T = TypeVar("T")
9
+
10
+
11
+ async def _read_into_queue(
12
+ gen: AsyncGenerator[T, None],
13
+ queue: asyncio.Queue[T],
14
+ done: asyncio.Semaphore,
15
+ ) -> None:
16
+ try:
17
+ async for item in gen:
18
+ await queue.put(item)
19
+ finally:
20
+ # Once done, notify the semaphore
21
+ await done.acquire()
22
+
23
+
24
+ async def join(*generators: AsyncGenerator[T, None]) -> AsyncGenerator[T, None]:
25
+ queue: asyncio.Queue[T] = asyncio.Queue(maxsize=1)
26
+ done_semaphore = asyncio.Semaphore(len(generators))
27
+
28
+ # Read from each given generator into the shared queue.
29
+ producers = [asyncio.create_task(_read_into_queue(gen, queue, done_semaphore)) for gen in generators]
30
+
31
+ # Read items off the queue until it is empty and the semaphore value is down to zero.
32
+ while not done_semaphore.locked() or not queue.empty():
33
+ try:
34
+ yield await asyncio.wait_for(queue.get(), 0.001)
35
+ except TimeoutError:
36
+ continue
37
+
38
+ # Not strictly needed, but usually a good idea to await tasks, they are already finished here.
39
+ try:
40
+ await asyncio.wait_for(asyncio.gather(*producers), 0)
41
+ except TimeoutError as exc:
42
+ raise NotImplementedError("Impossible state: expected all tasks to be exhausted") from exc
@@ -1,29 +1,27 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: tinkerforge-async
3
- Version: 1.5.3
3
+ Version: 1.6.1
4
4
  Summary: Python3 AsyncIO Tinkerforge driver
5
5
  Author-email: Patrick Baus <patrick.baus@physik.tu-darmstadt.de>
6
- License: GNU General Public License v3 (GPLv3)
6
+ License-Expression: GPL-3.0-or-later
7
7
  Project-URL: Homepage, https://github.com/PatrickBaus/tinkerforge_async
8
8
  Project-URL: Bug Tracker, https://github.com/PatrickBaus/tinkerforge_async/issues
9
9
  Project-URL: Download, https://github.com/PatrickBaus/tinkerforge_async/releases
10
10
  Keywords: IoT,Tinkerforge,API
11
11
  Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.7
13
- Classifier: Programming Language :: Python :: 3.8
14
12
  Classifier: Programming Language :: Python :: 3.9
15
13
  Classifier: Programming Language :: Python :: 3.10
16
14
  Classifier: Programming Language :: Python :: 3.11
17
15
  Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
18
17
  Classifier: Development Status :: 5 - Production/Stable
19
- Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
20
18
  Classifier: Operating System :: OS Independent
21
19
  Classifier: Intended Audience :: Developers
22
20
  Classifier: Intended Audience :: Science/Research
23
21
  Classifier: Natural Language :: English
24
22
  Classifier: Topic :: Home Automation
25
23
  Classifier: Topic :: Scientific/Engineering
26
- Requires-Python: >=3.7
24
+ Requires-Python: >=3.9
27
25
  Description-Content-Type: text/markdown
28
26
  License-File: LICENSE
29
27
  Requires-Dist: typing-extensions; python_version < "3.11"
@@ -44,6 +42,7 @@ Requires-Dist: mypy; extra == "test"
44
42
  Requires-Dist: pylint; extra == "test"
45
43
  Requires-Dist: pytest; extra == "test"
46
44
  Requires-Dist: setuptools; extra == "test"
45
+ Dynamic: license-file
47
46
 
48
47
  [![pylint](../../actions/workflows/pylint.yml/badge.svg)](../../actions/workflows/pylint.yml)
49
48
  [![PyPI](https://img.shields.io/pypi/v/tinkerforge-async)](https://pypi.org/project/tinkerforge-async/)
@@ -106,7 +105,7 @@ pip install tinkerforge-async
106
105
  # Changes made to the API
107
106
  Some design choices of the original Tinkerforge API are overly complex. I therefore replaced them with a simpler and more intuitive approach. A list of things that were changed can be found below:
108
107
  ## Design Changes
109
- - Only Python 3 is supported (3.7+)
108
+ - Only Python 3 is supported (3.9+)
110
109
  - Replaced threads with an async event loop
111
110
  - Completely rewritten how responses from bricks/bricklets work. All setters now have a `response_expected` parameter, which is set to `True` by default. If there is an error when calling the function, it will then raise an exception - either an `AttributeError` if the function is unknown, or a `ValueError` if one or more parameters are invalid.
112
111
 
@@ -188,9 +187,11 @@ Some design choices of the original Tinkerforge API are overly complex. I theref
188
187
  - `IPConnection.register_callback(callback_id, function)`: Replaced by `register_event_queue()`
189
188
  - `IPConnection.connect(host, port=4223, authentication_secret='')`: If `authentication_secret` is not empty, try to authenticate.
190
189
 
191
- - ### [IP Connection](https://www.tinkerforge.com/de/doc/Software/Bricklets/IO4V2_Bricklet_Python.html)
190
+ - ### [IO-4 Bricklet 2.0](https://www.tinkerforge.com/de/doc/Software/Bricklets/IO4V2_Bricklet_Python.html)
192
191
  - `BrickletIO4V2.set_pwm_configuration()` will now take the frequency in units of Hz and the duty cycle is normalized to 1, so it will take a float from [0...1].
193
- - `BrickletIO4V2.get_pwm_configuration()` will return the frequency in units of HZ and the duty cycle is normalized to 1.
192
+ - `BrickletIO4V2.get_pwm_configuration()` will return the frequency in units of Hz and the duty cycle is normalized to 1.
193
+ - `BrickletIO4V2.set_edge_count_callback_configuration()` sets the callback configuration for the edge counter callback. Its secondary ids are in [5...8] for channels [0...3].
194
+ - `BrickletIO4V2.get_edge_count_callback_configuration()` returns the callback configuration for the edge counter callback.
194
195
 
195
196
  - ### [Master Brick](https://www.tinkerforge.com/en/doc/Software/Bricks/Master_Brick_Python.html)
196
197
  - `BrickMaster.set_wifi_configuration()`/`BrickMaster.get_wifi_configuration()` will take/return all ips in natural order
@@ -210,7 +211,6 @@ Some design choices of the original Tinkerforge API are overly complex. I theref
210
211
  - `BrickletThermocoupleV2()` takes an additional parameter to define the type of sensor. The options are of type `BrickletThermocoupleV2.SensorType`. The default is `BrickletPtc.SensorType.TYPE_K`.
211
212
  - `BrickletThermocoupleV2.sensor_type` getter and setter to change the type of sensor used.
212
213
 
213
-
214
214
  - ### [Segment Display 4x7 Bricklet 2.0](https://www.tinkerforge.com/en/doc/Hardware/Bricklets/Segment_Display_4x7_V2.html)
215
215
  - `BrickletSegmentDisplay4x7V2.set_segments()` takes a `list`/`tuple` of 4 `int` instead of digit0, digit1, digit2, digit3. This is the same API as the older [Segment Display 4x7 Bricklet](https://www.tinkerforge.com/en/doc/Hardware/Bricklets/Segment_Display_4x7.html).
216
216
 
@@ -30,6 +30,7 @@ tinkerforge_async/bricklet_temperature_v2.py
30
30
  tinkerforge_async/bricklet_thermocouple_v2.py
31
31
  tinkerforge_async/device_factory.py
32
32
  tinkerforge_async/devices.py
33
+ tinkerforge_async/helper.py
33
34
  tinkerforge_async/ip_connection.py
34
35
  tinkerforge_async/ip_connection_helper.py
35
36
  tinkerforge_async.egg-info/PKG-INFO