ramses-rf 0.22.40__py3-none-any.whl → 0.51.2__py3-none-any.whl

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 (71) hide show
  1. ramses_cli/__init__.py +18 -0
  2. ramses_cli/client.py +597 -0
  3. ramses_cli/debug.py +20 -0
  4. ramses_cli/discovery.py +405 -0
  5. ramses_cli/utils/cat_slow.py +17 -0
  6. ramses_cli/utils/convert.py +60 -0
  7. ramses_rf/__init__.py +31 -10
  8. ramses_rf/binding_fsm.py +787 -0
  9. ramses_rf/const.py +124 -105
  10. ramses_rf/database.py +297 -0
  11. ramses_rf/device/__init__.py +69 -39
  12. ramses_rf/device/base.py +187 -376
  13. ramses_rf/device/heat.py +540 -552
  14. ramses_rf/device/hvac.py +279 -171
  15. ramses_rf/dispatcher.py +153 -177
  16. ramses_rf/entity_base.py +478 -361
  17. ramses_rf/exceptions.py +82 -0
  18. ramses_rf/gateway.py +377 -513
  19. ramses_rf/helpers.py +57 -19
  20. ramses_rf/py.typed +0 -0
  21. ramses_rf/schemas.py +148 -194
  22. ramses_rf/system/__init__.py +16 -23
  23. ramses_rf/system/faultlog.py +363 -0
  24. ramses_rf/system/heat.py +295 -302
  25. ramses_rf/system/schedule.py +312 -198
  26. ramses_rf/system/zones.py +318 -238
  27. ramses_rf/version.py +2 -8
  28. ramses_rf-0.51.2.dist-info/METADATA +72 -0
  29. ramses_rf-0.51.2.dist-info/RECORD +55 -0
  30. {ramses_rf-0.22.40.dist-info → ramses_rf-0.51.2.dist-info}/WHEEL +1 -2
  31. ramses_rf-0.51.2.dist-info/entry_points.txt +2 -0
  32. {ramses_rf-0.22.40.dist-info → ramses_rf-0.51.2.dist-info/licenses}/LICENSE +1 -1
  33. ramses_tx/__init__.py +160 -0
  34. {ramses_rf/protocol → ramses_tx}/address.py +65 -59
  35. ramses_tx/command.py +1454 -0
  36. ramses_tx/const.py +903 -0
  37. ramses_tx/exceptions.py +92 -0
  38. {ramses_rf/protocol → ramses_tx}/fingerprints.py +56 -15
  39. {ramses_rf/protocol → ramses_tx}/frame.py +132 -131
  40. ramses_tx/gateway.py +338 -0
  41. ramses_tx/helpers.py +883 -0
  42. {ramses_rf/protocol → ramses_tx}/logger.py +67 -53
  43. {ramses_rf/protocol → ramses_tx}/message.py +155 -191
  44. ramses_tx/opentherm.py +1260 -0
  45. ramses_tx/packet.py +210 -0
  46. {ramses_rf/protocol → ramses_tx}/parsers.py +1266 -1003
  47. ramses_tx/protocol.py +801 -0
  48. ramses_tx/protocol_fsm.py +672 -0
  49. ramses_tx/py.typed +0 -0
  50. {ramses_rf/protocol → ramses_tx}/ramses.py +262 -185
  51. {ramses_rf/protocol → ramses_tx}/schemas.py +150 -133
  52. ramses_tx/transport.py +1471 -0
  53. ramses_tx/typed_dicts.py +492 -0
  54. ramses_tx/typing.py +181 -0
  55. ramses_tx/version.py +4 -0
  56. ramses_rf/discovery.py +0 -398
  57. ramses_rf/protocol/__init__.py +0 -59
  58. ramses_rf/protocol/backports.py +0 -42
  59. ramses_rf/protocol/command.py +0 -1576
  60. ramses_rf/protocol/const.py +0 -697
  61. ramses_rf/protocol/exceptions.py +0 -111
  62. ramses_rf/protocol/helpers.py +0 -390
  63. ramses_rf/protocol/opentherm.py +0 -1170
  64. ramses_rf/protocol/packet.py +0 -235
  65. ramses_rf/protocol/protocol.py +0 -613
  66. ramses_rf/protocol/transport.py +0 -1011
  67. ramses_rf/protocol/version.py +0 -10
  68. ramses_rf/system/hvac.py +0 -82
  69. ramses_rf-0.22.40.dist-info/METADATA +0 -64
  70. ramses_rf-0.22.40.dist-info/RECORD +0 -42
  71. ramses_rf-0.22.40.dist-info/top_level.txt +0 -1
@@ -1,111 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- #
4
- """RAMSES RF - exceptions."""
5
- from __future__ import annotations
6
-
7
-
8
- class EvohomeError(Exception):
9
- """Base class for exceptions in this module."""
10
-
11
- pass
12
-
13
-
14
- class ExpiredCallbackError(EvohomeError):
15
- """Raised when the callback has expired."""
16
-
17
- def __init__(self, *args, **kwargs):
18
- super().__init__(*args, **kwargs)
19
- self.message = args[0] if args else None
20
-
21
- def __str__(self) -> str:
22
- err_msg = "The callback has expired"
23
- err_tip = "(no hint)"
24
- if self.message:
25
- return f"{err_msg}: {self.message} {err_tip}"
26
- return f"{err_msg} {err_tip}"
27
-
28
-
29
- class CorruptEvohomeError(EvohomeError):
30
- """Base class for exceptions in this module."""
31
-
32
- pass
33
-
34
-
35
- class InvalidPacketError(CorruptEvohomeError):
36
- """Raised when the packet is inconsistent."""
37
-
38
- def __init__(self, *args, **kwargs):
39
- super().__init__(*args, **kwargs)
40
- self.message = args[0] if args else None
41
-
42
- def __str__(self) -> str:
43
- err_msg = "Corrupt packet"
44
- err_tip = ""
45
- if self.message:
46
- return f"{err_msg}: {self.message}{err_tip}"
47
- return f"{err_msg} {err_tip}"
48
-
49
-
50
- class InvalidAddrSetError(InvalidPacketError):
51
- """Raised when the packet's address set is inconsistent."""
52
-
53
- def __init__(self, *args, **kwargs):
54
- super().__init__(*args, **kwargs)
55
- self.message = args[0] if args else None
56
-
57
- def __str__(self) -> str:
58
- err_msg = "Corrupt addresses"
59
- err_tip = ""
60
- if self.message:
61
- return f"{err_msg}: {self.message}{err_tip}"
62
- return f"{err_msg} {err_tip}"
63
-
64
-
65
- class InvalidPayloadError(InvalidPacketError):
66
- """Raised when the packet's payload is inconsistent."""
67
-
68
- def __init__(self, *args, **kwargs):
69
- super().__init__(*args, **kwargs)
70
- self.message = args[0] if args else None
71
-
72
- def __str__(self) -> str:
73
- err_msg = "Corrupt payload"
74
- err_tip = ""
75
- if self.message:
76
- return f"{err_msg}: {self.message}{err_tip}"
77
- return f"{err_msg} {err_tip}"
78
-
79
-
80
- class CorruptStateError(CorruptEvohomeError):
81
- """Raised when the system state (usu. schema) is inconsistent."""
82
-
83
- def __init__(self, *args, **kwargs):
84
- super().__init__(*args, **kwargs)
85
- self.message = args[0] if args else None
86
-
87
- def __str__(self) -> str:
88
- err_msg = "Inconsistent schema"
89
- err_tip = "(try restarting the client library)"
90
- if self.message:
91
- return f"{err_msg}: {self.message}{err_tip}"
92
- return f"{err_msg} {err_tip}"
93
-
94
-
95
- class ForeignGatewayError(EvohomeError):
96
- """Raised when a foreign gateway is detected.
97
-
98
- These devices may not be gateways (set a class), or belong to a neighbout (exclude
99
- via block_list/known_list), or should be allowed (known_list).
100
- """
101
-
102
- def __init__(self, *args, **kwargs):
103
- super().__init__(*args, **kwargs)
104
- self.message = args[0] if args else None
105
-
106
- def __str__(self) -> str:
107
- err_msg = "There is more than one HGI80-compatible gateway"
108
- err_tip = " (consider enforcing a known_list)"
109
- if self.message:
110
- return f"{err_msg}: {self.message}{err_tip}"
111
- return f"{err_msg} {err_tip}"
@@ -1,390 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- #
4
- """RAMSES RF - Protocol/Transport layer.
5
-
6
- Helper functions.
7
- """
8
- # from __future__ import annotations # incompatible with @typechecked
9
-
10
- import ctypes
11
- import sys
12
- import time
13
- from datetime import datetime as dt
14
- from typing import ( # typeguard doesn't support PEP604 on 3.9.x
15
- Iterable,
16
- Optional,
17
- Union,
18
- )
19
-
20
- try:
21
- from typeguard import typechecked # type: ignore[reportMissingImports]
22
-
23
- except ImportError:
24
-
25
- def typechecked(fnc): # type: ignore[no-redef]
26
- def wrapper(*args, **kwargs):
27
- return fnc(*args, **kwargs)
28
-
29
- return wrapper
30
-
31
-
32
- class _FILE_TIME(ctypes.Structure):
33
- """Data structure for GetSystemTimePreciseAsFileTime()."""
34
-
35
- _fields_ = [("dwLowDateTime", ctypes.c_uint), ("dwHighDateTime", ctypes.c_uint)]
36
-
37
-
38
- file_time = _FILE_TIME()
39
-
40
-
41
- @typechecked
42
- def timestamp() -> float:
43
- """Return the number of seconds since the Unix epoch.
44
-
45
- Return an accurate value, even for Windows-based systems.
46
- """ # see: https://www.python.org/dev/peps/pep-0564/
47
- if sys.platform != "win32":
48
- return time.time_ns() / 1e9 # since 1970-01-01T00:00:00Z, time.gmtime(0)
49
- ctypes.windll.kernel32.GetSystemTimePreciseAsFileTime(ctypes.byref(file_time))
50
- _time = (file_time.dwLowDateTime + (file_time.dwHighDateTime << 32)) / 1e7
51
- return _time - 134774 * 24 * 60 * 60 # otherwise, is since 1601-01-01T00:00:00Z
52
-
53
-
54
- @typechecked
55
- def dt_now() -> dt:
56
- """Return the current datetime as a local/naive datetime object.
57
-
58
- This is slower, but potentially more accurate, than dt.now(), and is used mainly for
59
- packet timestamps.
60
- """
61
- if sys.platform == "win32":
62
- return dt.fromtimestamp(timestamp())
63
- return dt.now()
64
-
65
-
66
- @typechecked
67
- def dt_str() -> str:
68
- """Return the current datetime as a isoformat string."""
69
- return dt_now().isoformat(timespec="microseconds")
70
-
71
-
72
- @typechecked
73
- def bool_from_hex(value: str) -> Optional[bool]: # either False, True or None
74
- """Convert a 2-char hex string into a boolean."""
75
- if not isinstance(value, str) or len(value) != 2:
76
- raise ValueError(f"Invalid value: {value}, is not a 2-char hex string")
77
- if value == "FF":
78
- return None
79
- return {"00": False, "C8": True}[value]
80
-
81
-
82
- @typechecked
83
- def bool_to_hex(value: Optional[bool]) -> str: # either 00, C8 or FF
84
- """Convert a boolean into a 2-char hex string."""
85
- if value is None:
86
- return "FF"
87
- if not isinstance(value, bool):
88
- raise ValueError(f"Invalid value: {value}, is not bool")
89
- return {False: "00", True: "C8"}[value]
90
-
91
-
92
- @typechecked
93
- def date_from_hex(value: str) -> Optional[str]: # YY-MM-DD
94
- """Convert am 8-char hex string into a date, format YY-MM-DD."""
95
- if not isinstance(value, str) or len(value) != 8:
96
- raise ValueError(f"Invalid value: {value}, is not an 8-char hex string")
97
- if value == "FFFFFFFF":
98
- return None
99
- return dt(
100
- year=int(value[4:8], 16),
101
- month=int(value[2:4], 16),
102
- day=int(value[:2], 16) & 0b11111, # 1st 3 bits: DayOfWeek
103
- ).strftime("%Y-%m-%d")
104
-
105
-
106
- @typechecked
107
- def double_from_hex(value: str, factor: int = 1) -> Optional[float]:
108
- """Convert a 4-char hex string into a double."""
109
- if not isinstance(value, str) or len(value) != 4:
110
- raise ValueError(f"Invalid value: {value}, is not a 4-char hex string")
111
- if value == "7FFF":
112
- return None
113
- return int(value, 16) / factor
114
-
115
-
116
- @typechecked
117
- def double_to_hex(value: Optional[float], factor: int = 1) -> str:
118
- """Convert a double into 4-char hex string."""
119
- if value is None:
120
- return "7FFF"
121
- if not isinstance(value, float):
122
- raise ValueError(f"Invalid value: {value}, is not a double (a float)")
123
- return f"{int(value * factor):04X}"
124
-
125
-
126
- @typechecked
127
- def dtm_from_hex(value: str) -> Optional[str]: # from parsers
128
- """Convert a 12/14-char hex string to an isoformat datetime (naive, local)."""
129
- # 00141B0A07E3 (...HH:MM:00) for system_mode, zone_mode (schedules?)
130
- # 0400041C0A07E3 (...HH:MM:SS) for sync_datetime
131
-
132
- if not isinstance(value, str) or len(value) not in (12, 14):
133
- raise ValueError(f"Invalid value: {value}, is not a 12/14-char hex string")
134
- if value[-12:] == "FF" * 6:
135
- return None
136
- if len(value) == 12:
137
- value = f"00{value}"
138
- return dt(
139
- year=int(value[10:14], 16),
140
- month=int(value[8:10], 16),
141
- day=int(value[6:8], 16),
142
- hour=int(value[4:6], 16) & 0b11111, # 1st 3 bits: DayOfWeek
143
- minute=int(value[2:4], 16),
144
- second=int(value[:2], 16) & 0b1111111, # 1st bit: used for DST
145
- ).isoformat(timespec="seconds")
146
-
147
-
148
- @typechecked
149
- def dtm_to_hex(dtm: Union[None, dt, str], is_dst=False, incl_seconds=False) -> str:
150
- """Convert a datetime (isoformat str, or naive dtm) to a 12/14-char hex str."""
151
-
152
- def _dtm_to_hex(tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, *args):
153
- return (
154
- f"{tm_sec:02X}{tm_min:02X}{tm_hour:02X}"
155
- f"{tm_mday:02X}{tm_mon:02X}{tm_year:04X}"
156
- )
157
-
158
- if dtm is None:
159
- return "FF" * (7 if incl_seconds else 6)
160
- if isinstance(dtm, str):
161
- dtm = dt.fromisoformat(dtm)
162
- dtm_str = _dtm_to_hex(*dtm.timetuple()) # TODO: add DST for tm_isdst
163
- if is_dst:
164
- dtm_str = f"{int(dtm_str[:2], 16) | 0x80:02X}" + dtm_str[2:]
165
- return dtm_str if incl_seconds else dtm_str[2:]
166
-
167
-
168
- @typechecked
169
- def dts_from_hex(value: str) -> Optional[str]:
170
- """YY-MM-DD HH:MM:SS."""
171
- if not isinstance(value, str) or len(value) != 12:
172
- raise ValueError(f"Invalid value: {value}, is not a 12-char hex string")
173
- if value == "00000000007F":
174
- return None
175
- _seqx = int(value, 16)
176
- return dt(
177
- year=(_seqx & 0b1111111 << 24) >> 24,
178
- month=(_seqx & 0b1111 << 36) >> 36,
179
- day=(_seqx & 0b11111 << 31) >> 31,
180
- hour=(_seqx & 0b11111 << 19) >> 19,
181
- minute=(_seqx & 0b111111 << 13) >> 13,
182
- second=(_seqx & 0b111111 << 7) >> 7,
183
- ).strftime("%y-%m-%dT%H:%M:%S")
184
-
185
-
186
- @typechecked
187
- def dts_to_hex(dtm: Union[None, dt, str]) -> str: # TODO: WIP
188
- """Convert a datetime (isoformat str, or dtm) to a packed 12-char hex str."""
189
- """YY-MM-DD HH:MM:SS."""
190
- if dtm is None:
191
- return "00000000007F"
192
- if isinstance(dtm, str):
193
- dtm = dt.fromisoformat(dtm) # TODO: YY-MM-DD, not YYYY-MM-DD
194
- (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, *_) = dtm.timetuple()
195
- result = sum(
196
- (
197
- tm_year % 100 << 24,
198
- tm_mon << 36,
199
- tm_mday << 31,
200
- tm_hour << 19,
201
- tm_min << 13,
202
- tm_sec << 7,
203
- )
204
- )
205
- return f"{result:012X}"
206
-
207
-
208
- @typechecked
209
- def flag8_from_hex(byte: str, lsb: bool = False) -> list[int]: # TODO: use tuple
210
- """Split a hex str (a byte) into a list of 8 bits, MSB as first bit by default.
211
-
212
- If lsb==True, then the LSB is first.
213
- The `lsb` boolean is used so that flag[0] is `zone_idx["00"]`, etc.
214
- """
215
- if not isinstance(byte, str) or len(byte) != 2:
216
- raise ValueError(f"Invalid value: '{byte}', is not a 2-char hex string")
217
- if lsb: # make LSB is first bit
218
- return list((int(byte, 16) & (1 << x)) >> x for x in range(8))
219
- return list((int(byte, 16) & (1 << x)) >> x for x in reversed(range(8)))
220
-
221
-
222
- @typechecked
223
- def flag8_to_hex(flags: Iterable[int], lsb: bool = False) -> str:
224
- """Convert a list of 8 bits, MSB as first bit by default, into an ASCII hex string.
225
-
226
- The `lsb` boolean is used so that flag[0] is `zone_idx["00"]`, etc.
227
- """
228
- if not isinstance(flags, list) or len(flags) != 8:
229
- raise ValueError(f"Invalid value: '{flags}', is not a list of 8 bits")
230
- if lsb: # LSB is first bit
231
- return f"{sum(x<<idx for idx, x in enumerate(flags)):02X}"
232
- return f"{sum(x<<idx for idx, x in enumerate(reversed(flags))):02X}"
233
-
234
-
235
- # TODO: add a wrapper for EF, & 0xF0
236
- @typechecked
237
- def percent_from_hex(
238
- value: str, high_res: bool = True
239
- ) -> Optional[float]: # c.f. valve_demand
240
- """Convert a 2-char hex string into a percentage.
241
-
242
- The range is 0-100%, with resolution of 0.5% (high_res) or 1%.
243
- """
244
- if not isinstance(value, str) or len(value) != 2:
245
- raise ValueError(f"Invalid value: {value}, is not a 2-char hex string")
246
- if value == "EF": # TODO: when EF, when 7F?
247
- return None # TODO: raise NotImplementedError
248
- if (raw_result := int(value, 16)) & 0xF0 == 0xF0:
249
- return None # TODO: raise errors
250
- result = float(raw_result) / (200 if high_res else 100)
251
- if result > 1.0:
252
- raise ValueError(f"Invalid result: {result} (0x{value}) is > 1")
253
- return result
254
-
255
-
256
- @typechecked
257
- def str_from_hex(value: str) -> Optional[str]: # printable ASCII characters
258
- """Return a string of printable ASCII characters."""
259
- # result = bytearray.fromhex(value).split(b"\x7F")[0] # TODO: needs checking
260
- if not isinstance(value, str):
261
- raise ValueError(f"Invalid value: {value}, is not a string")
262
- result = bytearray([x for x in bytearray.fromhex(value) if 31 < x < 127])
263
- return result.decode("ascii").strip() if result else None
264
-
265
-
266
- @typechecked
267
- def str_to_hex(value: str) -> str:
268
- """Convert a string to a variable-length ASCII hex string."""
269
- if not isinstance(value, str):
270
- raise ValueError(f"Invalid value: {value}, is not a string")
271
- return "".join(f"{ord(x):02X}" for x in value) # or: value.encode().hex()
272
-
273
-
274
- @typechecked
275
- def temp_from_hex(value: str) -> Union[None, bool, float]:
276
- """Convert a 2's complement 4-byte hex string to an float."""
277
- if not isinstance(value, str) or len(value) != 4:
278
- raise ValueError(f"Invalid value: {value}, is not a 4-char hex string")
279
- if value == "31FF": # means: N/A (== 127.99, 2s complement), signed?
280
- return None
281
- if value == "7EFF": # possibly only for setpoints? unsigned?
282
- return False
283
- if value == "7FFF": # also: FFFF?, means: N/A (== 327.67)
284
- return None
285
- temp = int(value, 16)
286
- return (temp if temp < 2**15 else temp - 2**16) / 100
287
-
288
-
289
- @typechecked
290
- def temp_to_hex(value: Optional[float]) -> str:
291
- """Convert a float to a 2's complement 4-byte hex string."""
292
- if value is None:
293
- return "7FFF" # or: "31FF"?
294
- if value is False:
295
- return "7EFF"
296
- if not isinstance(value, (float, int)):
297
- raise TypeError(f"Invalid temp: {value} is not a float")
298
- if not -(2**7) <= value < 2**7: # TODO: tighten range
299
- raise ValueError(f"Invalid temp: {value} is out of range")
300
- temp = int(value * 100)
301
- return f"{temp if temp >= 0 else temp + 2 ** 16:04X}"
302
-
303
-
304
- @typechecked
305
- def valve_demand(value: str) -> Optional[dict]: # c.f. percent_from_hex()
306
- """Convert a 2-char hex string into a percentage.
307
-
308
- The range is 0-100%, with resolution of 0.5% (high_res) or 1%.
309
- """ # for a damper (restricts flow), or a valve (permits flow)
310
- if not isinstance(value, str) or len(value) != 2:
311
- raise ValueError(f"Invalid value: {value}, is not a 2-char hex string")
312
- if value == "EF":
313
- return None # TODO: raise NotImplementedError
314
- result = int(value, 16)
315
- if result & 0xF0 == 0xF0:
316
- STATE_3150 = {
317
- "F0": "open_circuit",
318
- "F1": "short_circuit",
319
- "FD": "valve_stuck", # damper/valve stuck
320
- "FE": "actuator_stuck",
321
- }
322
- return {
323
- "heat_demand": None,
324
- "fault": STATE_3150.get(value, "malfunction"),
325
- }
326
- result = result / 200 # type: ignore[assignment]
327
- if result > 1:
328
- raise ValueError(f"Invalid result: {result} (0x{value}) is > 1")
329
- return {"heat_demand": result}
330
-
331
-
332
- def _precision_v_cost():
333
- import math
334
-
335
- #
336
- LOOPS = 10**6
337
- #
338
- print("time.time_ns(): %s" % time.time_ns())
339
- print("time.time(): %s\r\n" % time.time())
340
- #
341
- starts = time.time_ns()
342
- min_dt = [abs(time.time_ns() - time.time_ns()) for _ in range(LOOPS)]
343
- min_dt = min(filter(bool, min_dt))
344
- print("min delta time_ns(): %s ns" % min_dt)
345
- print("duration time_ns(): %s ns\r\n" % (time.time_ns() - starts))
346
- #
347
- starts = time.time_ns()
348
- min_dt = [abs(time.time() - time.time()) for _ in range(LOOPS)]
349
- min_dt = min(filter(bool, min_dt))
350
- print("min delta time(): %s ns" % math.ceil(min_dt * 1e9))
351
- print("duration time(): %s ns\r\n" % (time.time_ns() - starts))
352
- #
353
- starts = time.time_ns()
354
- min_dt = [abs(timestamp() - timestamp()) for _ in range(LOOPS)]
355
- min_dt = min(filter(bool, min_dt))
356
- print("min delta timestamp(): %s ns" % math.ceil(min_dt * 1e9))
357
- print("duration timestamp(): %s ns\r\n" % (time.time_ns() - starts))
358
- #
359
- LOOPS = 10**4
360
- #
361
- starts = time.time_ns()
362
- min_td = [abs(dt.now() - dt.now()) for _ in range(LOOPS)]
363
- min_td = min(filter(bool, min_td))
364
- print("min delta dt.now(): %s ns" % math.ceil(min_dt * 1e9))
365
- print("duration dt.now(): %s ns\r\n" % (time.time_ns() - starts))
366
- #
367
- starts = time.time_ns()
368
- min_td = [abs(dt_now() - dt_now()) for _ in range(LOOPS)]
369
- min_td = min(filter(bool, min_td))
370
- print("min delta dt_now(): %s ns" % math.ceil(min_dt * 1e9))
371
- print("duration dt_now(): %s ns\r\n" % (time.time_ns() - starts))
372
- #
373
- starts = time.time_ns()
374
- min_td = [
375
- abs(
376
- (dt_now if sys.platform == "win32" else dt.now)()
377
- - (dt_now if sys.platform == "win32" else dt.now)()
378
- )
379
- for _ in range(LOOPS)
380
- ]
381
- min_td = min(filter(bool, min_td))
382
- print("min delta dt_now(): %s ns" % math.ceil(min_dt * 1e9))
383
- print("duration dt_now(): %s ns\r\n" % (time.time_ns() - starts))
384
- #
385
- dt_nov = dt_now if sys.platform == "win32" else dt.now
386
- starts = time.time_ns()
387
- min_td = [abs(dt_nov() - dt_nov()) for _ in range(LOOPS)]
388
- min_td = min(filter(bool, min_td))
389
- print("min delta dt_now(): %s ns" % math.ceil(min_dt * 1e9))
390
- print("duration dt_now(): %s ns\r\n" % (time.time_ns() - starts))