libosdp 3.1.2__tar.gz → 3.2.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 (58) hide show
  1. {libosdp-3.1.2 → libosdp-3.2.3}/PKG-INFO +27 -3
  2. {libosdp-3.1.2 → libosdp-3.2.3}/README.md +25 -1
  3. {libosdp-3.1.2 → libosdp-3.2.3}/libosdp.egg-info/PKG-INFO +27 -3
  4. {libosdp-3.1.2 → libosdp-3.2.3}/libosdp.egg-info/SOURCES.txt +0 -1
  5. {libosdp-3.1.2 → libosdp-3.2.3}/osdp/__init__.py +2 -2
  6. {libosdp-3.1.2 → libosdp-3.2.3}/osdp/constants.py +7 -1
  7. {libosdp-3.1.2 → libosdp-3.2.3}/osdp/control_panel.py +53 -51
  8. {libosdp-3.1.2 → libosdp-3.2.3}/osdp/helpers.py +1 -1
  9. {libosdp-3.1.2 → libosdp-3.2.3}/osdp/key_store.py +1 -1
  10. {libosdp-3.1.2 → libosdp-3.2.3}/osdp/peripheral_device.py +27 -17
  11. {libosdp-3.1.2 → libosdp-3.2.3}/setup.py +63 -20
  12. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/include/osdp.h +95 -1
  13. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/include/osdp_export.h +3 -3
  14. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/python/osdp_sys/base.c +24 -13
  15. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/python/osdp_sys/cp.c +202 -24
  16. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/python/osdp_sys/data.c +2 -13
  17. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/python/osdp_sys/module.c +8 -1
  18. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/python/osdp_sys/module.h +18 -1
  19. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/python/osdp_sys/pd.c +154 -7
  20. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/python/osdp_sys/utils.c +137 -4
  21. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/src/crypto/tinyaes.c +1 -1
  22. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/src/osdp_common.c +1 -1
  23. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/src/osdp_common.h +177 -64
  24. libosdp-3.2.3/vendor/src/osdp_config.h +102 -0
  25. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/src/osdp_cp.c +261 -132
  26. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/src/osdp_diag.c +1 -1
  27. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/src/osdp_diag.h +1 -1
  28. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/src/osdp_file.c +18 -19
  29. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/src/osdp_file.h +1 -1
  30. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/src/osdp_pd.c +217 -91
  31. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/src/osdp_phy.c +183 -69
  32. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/src/osdp_sc.c +4 -8
  33. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/utils/include/utils/assert.h +19 -2
  34. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/utils/include/utils/disjoint_set.h +1 -1
  35. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/utils/include/utils/list.h +1 -1
  36. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/utils/include/utils/logger.h +1 -1
  37. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/utils/include/utils/pcap_gen.h +1 -1
  38. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/utils/include/utils/queue.h +1 -1
  39. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/utils/include/utils/slab.h +1 -1
  40. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/utils/include/utils/utils.h +65 -10
  41. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/utils/src/crc16.c +1 -1
  42. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/utils/src/disjoint_set.c +1 -1
  43. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/utils/src/list.c +1 -1
  44. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/utils/src/logger.c +4 -4
  45. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/utils/src/queue.c +1 -1
  46. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/utils/src/utils.c +146 -15
  47. libosdp-3.1.2/vendor/src/osdp_config.h +0 -43
  48. libosdp-3.1.2/vendor/utils/src/slab.c +0 -64
  49. {libosdp-3.1.2 → libosdp-3.2.3}/MANIFEST.in +0 -0
  50. {libosdp-3.1.2 → libosdp-3.2.3}/libosdp.egg-info/dependency_links.txt +0 -0
  51. {libosdp-3.1.2 → libosdp-3.2.3}/libosdp.egg-info/top_level.txt +0 -0
  52. {libosdp-3.1.2 → libosdp-3.2.3}/osdp/channel.py +0 -0
  53. {libosdp-3.1.2 → libosdp-3.2.3}/pyproject.toml +0 -0
  54. {libosdp-3.1.2 → libosdp-3.2.3}/setup.cfg +0 -0
  55. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/src/crypto/tinyaes_src.c +0 -0
  56. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/src/crypto/tinyaes_src.h +0 -0
  57. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/utils/include/utils/crc16.h +0 -0
  58. {libosdp-3.1.2 → libosdp-3.2.3}/vendor/utils/src/pcap_gen.c +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: libosdp
3
- Version: 3.1.2
3
+ Version: 3.2.3
4
4
  Summary: Library implementation of IEC 60839-11-5 OSDP (Open Supervised Device Protocol)
5
5
  Home-page: https://github.com/goToMain/libosdp
6
6
  Author: Siddharth Chandrasekaran
@@ -8,7 +8,7 @@ Author-email: sidcha.dev@gmail.com
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: License :: OSI Approved :: Apache Software License
10
10
  Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.8
11
+ Requires-Python: >=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  Dynamic: author
14
14
  Dynamic: author-email
@@ -85,6 +85,18 @@ while True:
85
85
 
86
86
  see [examples/cp_app.py][2] for more details.
87
87
 
88
+ Optional command completion callback:
89
+
90
+ ```python
91
+ from osdp import CompletionStatus
92
+
93
+ def on_command_complete(address, command, status):
94
+ if status == CompletionStatus.Ok:
95
+ print("command completed")
96
+
97
+ cp.set_command_completion_handler(on_command_complete)
98
+ ```
99
+
88
100
  ### Peripheral Device mode:
89
101
 
90
102
  ```python
@@ -116,6 +128,18 @@ while True:
116
128
 
117
129
  see [examples/pd_app.py][3] for more details.
118
130
 
119
- [1]: https://libosdp.sidcha.dev/api/
131
+ Optional event completion callback:
132
+
133
+ ```python
134
+ from osdp import CompletionStatus
135
+
136
+ def on_event_complete(event, status):
137
+ if status == CompletionStatus.Flushed:
138
+ print("event removed by flush")
139
+
140
+ pd.set_event_completion_handler(on_event_complete)
141
+ ```
142
+
143
+ [1]: https://doc.osdp.dev/api/
120
144
  [2]: https://github.com/goToMain/libosdp/blob/master/examples/python/cp_app.py
121
145
  [3]: https://github.com/goToMain/libosdp/blob/master/examples/python/pd_app.py
@@ -64,6 +64,18 @@ while True:
64
64
 
65
65
  see [examples/cp_app.py][2] for more details.
66
66
 
67
+ Optional command completion callback:
68
+
69
+ ```python
70
+ from osdp import CompletionStatus
71
+
72
+ def on_command_complete(address, command, status):
73
+ if status == CompletionStatus.Ok:
74
+ print("command completed")
75
+
76
+ cp.set_command_completion_handler(on_command_complete)
77
+ ```
78
+
67
79
  ### Peripheral Device mode:
68
80
 
69
81
  ```python
@@ -95,6 +107,18 @@ while True:
95
107
 
96
108
  see [examples/pd_app.py][3] for more details.
97
109
 
98
- [1]: https://libosdp.sidcha.dev/api/
110
+ Optional event completion callback:
111
+
112
+ ```python
113
+ from osdp import CompletionStatus
114
+
115
+ def on_event_complete(event, status):
116
+ if status == CompletionStatus.Flushed:
117
+ print("event removed by flush")
118
+
119
+ pd.set_event_completion_handler(on_event_complete)
120
+ ```
121
+
122
+ [1]: https://doc.osdp.dev/api/
99
123
  [2]: https://github.com/goToMain/libosdp/blob/master/examples/python/cp_app.py
100
124
  [3]: https://github.com/goToMain/libosdp/blob/master/examples/python/pd_app.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: libosdp
3
- Version: 3.1.2
3
+ Version: 3.2.3
4
4
  Summary: Library implementation of IEC 60839-11-5 OSDP (Open Supervised Device Protocol)
5
5
  Home-page: https://github.com/goToMain/libosdp
6
6
  Author: Siddharth Chandrasekaran
@@ -8,7 +8,7 @@ Author-email: sidcha.dev@gmail.com
8
8
  Classifier: Programming Language :: Python :: 3
9
9
  Classifier: License :: OSI Approved :: Apache Software License
10
10
  Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.8
11
+ Requires-Python: >=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  Dynamic: author
14
14
  Dynamic: author-email
@@ -85,6 +85,18 @@ while True:
85
85
 
86
86
  see [examples/cp_app.py][2] for more details.
87
87
 
88
+ Optional command completion callback:
89
+
90
+ ```python
91
+ from osdp import CompletionStatus
92
+
93
+ def on_command_complete(address, command, status):
94
+ if status == CompletionStatus.Ok:
95
+ print("command completed")
96
+
97
+ cp.set_command_completion_handler(on_command_complete)
98
+ ```
99
+
88
100
  ### Peripheral Device mode:
89
101
 
90
102
  ```python
@@ -116,6 +128,18 @@ while True:
116
128
 
117
129
  see [examples/pd_app.py][3] for more details.
118
130
 
119
- [1]: https://libosdp.sidcha.dev/api/
131
+ Optional event completion callback:
132
+
133
+ ```python
134
+ from osdp import CompletionStatus
135
+
136
+ def on_event_complete(event, status):
137
+ if status == CompletionStatus.Flushed:
138
+ print("event removed by flush")
139
+
140
+ pd.set_event_completion_handler(on_event_complete)
141
+ ```
142
+
143
+ [1]: https://doc.osdp.dev/api/
120
144
  [2]: https://github.com/goToMain/libosdp/blob/master/examples/python/cp_app.py
121
145
  [3]: https://github.com/goToMain/libosdp/blob/master/examples/python/pd_app.py
@@ -51,5 +51,4 @@ vendor/utils/src/list.c
51
51
  vendor/utils/src/logger.c
52
52
  vendor/utils/src/pcap_gen.c
53
53
  vendor/utils/src/queue.c
54
- vendor/utils/src/slab.c
55
54
  vendor/utils/src/utils.c
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2021-2024 Siddharth Chandrasekaran <sidcha.dev@gmail.com>
2
+ # Copyright (c) 2021-2026 Siddharth Chandrasekaran <sidcha.dev@gmail.com>
3
3
  #
4
4
  # SPDX-License-Identifier: Apache-2.0
5
5
  #
@@ -9,7 +9,7 @@ from .peripheral_device import PeripheralDevice
9
9
  from .key_store import KeyStore
10
10
  from .constants import (
11
11
  LibFlag, Command, CommandLEDColor, CommandFileTxFlags, Event, EventNotification,
12
- CardFormat, Capability, LogLevel, StatusReportType
12
+ CardFormat, Capability, LogLevel, StatusReportType, CompletionStatus
13
13
  )
14
14
  from .helpers import PdId, PDInfo, PDCapabilities
15
15
  from .channel import Channel
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2021-2024 Siddharth Chandrasekaran <sidcha.dev@gmail.com>
2
+ # Copyright (c) 2021-2026 Siddharth Chandrasekaran <sidcha.dev@gmail.com>
3
3
  #
4
4
  # SPDX-License-Identifier: Apache-2.0
5
5
  #
@@ -60,6 +60,12 @@ class EventNotification:
60
60
  SecureChannelStatus = osdp_sys.EVENT_NOTIFICATION_SC_STATUS
61
61
  PeripheralDeviceStatus = osdp_sys.EVENT_NOTIFICATION_PD_STATUS
62
62
 
63
+ class CompletionStatus:
64
+ Ok = getattr(osdp_sys, "COMPLETION_OK", 0)
65
+ Failed = getattr(osdp_sys, "COMPLETION_FAILED", 1)
66
+ Flushed = getattr(osdp_sys, "COMPLETION_FLUSHED", 2)
67
+ Aborted = getattr(osdp_sys, "COMPLETION_ABORTED", 3)
68
+
63
69
  class Event:
64
70
  CardRead = osdp_sys.EVENT_CARDREAD
65
71
  KeyPress = osdp_sys.EVENT_KEYPRESS
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2021-2024 Siddharth Chandrasekaran <sidcha.dev@gmail.com>
2
+ # Copyright (c) 2021-2026 Siddharth Chandrasekaran <sidcha.dev@gmail.com>
3
3
  #
4
4
  # SPDX-License-Identifier: Apache-2.0
5
5
  #
@@ -17,7 +17,8 @@ class ControlPanel():
17
17
  self,
18
18
  pd_info_list: list[PDInfo],
19
19
  log_level: LogLevel=LogLevel.Info,
20
- event_handler: Callable[[int, dict], int]=None
20
+ event_handler: Callable[[int, dict], int]=None,
21
+ command_completion_handler: Callable[[int, dict, int], None]=None
21
22
  ) -> None:
22
23
  self.pd_addr = []
23
24
  info_list = []
@@ -27,11 +28,17 @@ class ControlPanel():
27
28
  info_list.append(pd_info.get())
28
29
  self.event_queue = [ queue.Queue() for i in self.pd_addr ]
29
30
  self.user_event_handler = None
31
+ self.user_command_completion_handler = None
30
32
  osdp_sys.set_loglevel(log_level)
31
33
  self.ctx = osdp_sys.ControlPanel(info_list)
32
34
  # Always use our internal handler to ensure queue functionality
33
35
  self.ctx.set_event_callback(self._internal_event_handler)
36
+ if hasattr(self.ctx, "set_command_completion_callback"):
37
+ self.ctx.set_command_completion_callback(
38
+ self._internal_command_completion_handler
39
+ )
34
40
  self.set_event_handler(event_handler)
41
+ self.set_command_completion_handler(command_completion_handler)
35
42
  self.event = None
36
43
  self.lock = None
37
44
  self.thread = None
@@ -39,15 +46,18 @@ class ControlPanel():
39
46
  @staticmethod
40
47
  def refresh(event, lock, ctx):
41
48
  while not event.is_set():
42
- lock.acquire()
43
- ctx.refresh()
44
- lock.release()
49
+ with lock:
50
+ ctx.refresh()
45
51
  time.sleep(0.020) #sleep for 20ms
46
52
 
47
53
  def set_event_handler(self, handler: Callable[[int, dict], int]):
48
54
  """Set user event handler while maintaining queue functionality"""
49
55
  self.user_event_handler = handler
50
56
 
57
+ def set_command_completion_handler(
58
+ self, handler: Callable[[int, dict, int], None]):
59
+ self.user_command_completion_handler = handler
60
+
51
61
  def _internal_event_handler(self, pd, event) -> int:
52
62
  """Internal handler that manages both queue and user callback"""
53
63
  # Always put event in queue for get_event() compatibility
@@ -63,6 +73,15 @@ class ControlPanel():
63
73
 
64
74
  return 0
65
75
 
76
+ def _internal_command_completion_handler(self, pd, command, status) -> None:
77
+ if self.user_command_completion_handler:
78
+ try:
79
+ self.user_command_completion_handler(
80
+ self.pd_addr[pd], command, status
81
+ )
82
+ except Exception as e:
83
+ print(f"Error in user command completion handler: {e}")
84
+
66
85
  def get_event(self, address, timeout: int=5):
67
86
  pd = self.pd_addr.index(address)
68
87
  block = timeout >= 0
@@ -73,10 +92,8 @@ class ControlPanel():
73
92
  return event
74
93
 
75
94
  def status(self):
76
- self.lock.acquire()
77
- bitmask = self.ctx.status()
78
- self.lock.release()
79
- return bitmask
95
+ with self.lock:
96
+ return self.ctx.status()
80
97
 
81
98
  def is_online(self, address):
82
99
  pd = self.pd_addr.index(address)
@@ -84,9 +101,8 @@ class ControlPanel():
84
101
 
85
102
  def get_pd_id(self, address: int) -> PdId:
86
103
  pd = self.pd_addr.index(address)
87
- self.lock.acquire()
88
- pd_id_dict = self.ctx.get_pd_id(pd)
89
- self.lock.release()
104
+ with self.lock:
105
+ pd_id_dict = self.ctx.get_pd_id(pd)
90
106
  if pd_id_dict:
91
107
  # version: int, model: int, vendor_code: int, serial_number: int, firmware_version: int
92
108
  pd_id = PdId(
@@ -100,9 +116,8 @@ class ControlPanel():
100
116
 
101
117
  def check_capability(self, address: int, cap: Capability) -> Tuple[int, int]:
102
118
  pd = self.pd_addr.index(address)
103
- self.lock.acquire()
104
- compliance_level, num_items = self.ctx.check_capability(pd, cap)
105
- self.lock.release()
119
+ with self.lock:
120
+ compliance_level, num_items = self.ctx.check_capability(pd, cap)
106
121
  return (compliance_level, num_items)
107
122
 
108
123
  def get_num_online(self):
@@ -114,10 +129,8 @@ class ControlPanel():
114
129
  return online
115
130
 
116
131
  def sc_status(self):
117
- self.lock.acquire()
118
- bitmask = self.ctx.sc_status()
119
- self.lock.release()
120
- return bitmask
132
+ with self.lock:
133
+ return self.ctx.sc_status()
121
134
 
122
135
  def is_sc_active(self, address):
123
136
  pd = self.pd_addr.index(address)
@@ -133,10 +146,13 @@ class ControlPanel():
133
146
 
134
147
  def submit_command(self, address, cmd):
135
148
  pd = self.pd_addr.index(address)
136
- self.lock.acquire()
137
- ret = self.ctx.submit_command(pd, cmd)
138
- self.lock.release()
139
- return ret
149
+ with self.lock:
150
+ return self.ctx.submit_command(pd, cmd)
151
+
152
+ def flush_commands(self, address):
153
+ pd = self.pd_addr.index(address)
154
+ with self.lock:
155
+ return self.ctx.flush_commands(pd)
140
156
 
141
157
  def send_command(self, address, cmd):
142
158
  from warnings import warn
@@ -145,52 +161,38 @@ class ControlPanel():
145
161
 
146
162
  def set_flag(self, address, flag: LibFlag):
147
163
  pd = self.pd_addr.index(address)
148
- self.lock.acquire()
149
- ret = self.ctx.set_flag(pd, flag)
150
- self.lock.release()
151
- return ret
164
+ with self.lock:
165
+ return self.ctx.set_flag(pd, flag)
152
166
 
153
167
  def clear_flag(self, address, flag: LibFlag):
154
168
  pd = self.pd_addr.index(address)
155
- self.lock.acquire()
156
- ret = self.ctx.clear_flag(pd, flag)
157
- self.lock.release()
158
- return ret
169
+ with self.lock:
170
+ return self.ctx.clear_flag(pd, flag)
159
171
 
160
172
  def disable_pd(self, address: int) -> bool:
161
173
  pd = self.pd_addr.index(address)
162
- self.lock.acquire()
163
- ret = self.ctx.disable_pd(pd)
164
- self.lock.release()
165
- return ret
174
+ with self.lock:
175
+ return self.ctx.disable_pd(pd)
166
176
 
167
177
  def enable_pd(self, address: int) -> bool:
168
178
  pd = self.pd_addr.index(address)
169
- self.lock.acquire()
170
- ret = self.ctx.enable_pd(pd)
171
- self.lock.release()
172
- return ret
179
+ with self.lock:
180
+ return self.ctx.enable_pd(pd)
173
181
 
174
182
  def is_pd_enabled(self, address: int) -> bool:
175
183
  pd = self.pd_addr.index(address)
176
- self.lock.acquire()
177
- ret = self.ctx.is_pd_enabled(pd)
178
- self.lock.release()
179
- return ret
184
+ with self.lock:
185
+ return self.ctx.is_pd_enabled(pd)
180
186
 
181
187
  def register_file_ops(self, address, fops):
182
188
  pd = self.pd_addr.index(address)
183
- self.lock.acquire()
184
- ret = self.ctx.register_file_ops(pd, fops)
185
- self.lock.release()
186
- return ret
189
+ with self.lock:
190
+ return self.ctx.register_file_ops(pd, fops)
187
191
 
188
192
  def get_file_tx_status(self, address):
189
193
  pd = self.pd_addr.index(address)
190
- self.lock.acquire()
191
- ret = self.ctx.get_file_tx_status(pd)
192
- self.lock.release()
193
- return ret
194
+ with self.lock:
195
+ return self.ctx.get_file_tx_status(pd)
194
196
 
195
197
  def start(self):
196
198
  if self.thread:
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2021-2024 Siddharth Chandrasekaran <sidcha.dev@gmail.com>
2
+ # Copyright (c) 2021-2026 Siddharth Chandrasekaran <sidcha.dev@gmail.com>
3
3
  #
4
4
  # SPDX-License-Identifier: Apache-2.0
5
5
  #
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2021-2024 Siddharth Chandrasekaran <sidcha.dev@gmail.com>
2
+ # Copyright (c) 2021-2026 Siddharth Chandrasekaran <sidcha.dev@gmail.com>
3
3
  #
4
4
  # SPDX-License-Identifier: Apache-2.0
5
5
  #
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2021-2024 Siddharth Chandrasekaran <sidcha.dev@gmail.com>
2
+ # Copyright (c) 2021-2026 Siddharth Chandrasekaran <sidcha.dev@gmail.com>
3
3
  #
4
4
  # SPDX-License-Identifier: Apache-2.0
5
5
  #
@@ -16,15 +16,22 @@ from .constants import LogLevel
16
16
  class PeripheralDevice():
17
17
  def __init__(self, pd_info: PDInfo, pd_cap: PDCapabilities,
18
18
  log_level: LogLevel=LogLevel.Info,
19
- command_handler: Callable[[dict], Tuple[int, dict]]=None):
19
+ command_handler: Callable[[dict], Tuple[int, dict]]=None,
20
+ event_completion_handler: Callable[[dict, int], None]=None):
20
21
  self.command_queue = queue.Queue()
21
22
  self.address = pd_info.address
22
23
  self.user_command_handler = None
24
+ self.user_event_completion_handler = None
23
25
  osdp_sys.set_loglevel(log_level)
24
26
  self.ctx = osdp_sys.PeripheralDevice(pd_info.get(), capabilities=pd_cap.get())
25
27
  # Always use our internal handler to ensure queue functionality
26
28
  self.ctx.set_command_callback(self._internal_command_handler)
29
+ if hasattr(self.ctx, "set_event_completion_callback"):
30
+ self.ctx.set_event_completion_callback(
31
+ self._internal_event_completion_handler
32
+ )
27
33
  self.set_command_handler(command_handler)
34
+ self.set_event_completion_handler(event_completion_handler)
28
35
  self.event = None
29
36
  self.lock = None
30
37
  self.thread = None
@@ -32,9 +39,8 @@ class PeripheralDevice():
32
39
  @staticmethod
33
40
  def refresh(event, lock, ctx):
34
41
  while not event.is_set():
35
- lock.acquire()
36
- ctx.refresh()
37
- lock.release()
42
+ with lock:
43
+ ctx.refresh()
38
44
  time.sleep(0.020) #sleep for 20ms
39
45
 
40
46
  def _internal_command_handler(self, command) -> Tuple[int, dict]:
@@ -56,6 +62,16 @@ class PeripheralDevice():
56
62
  """Set user command handler while maintaining queue functionality"""
57
63
  self.user_command_handler = handler
58
64
 
65
+ def set_event_completion_handler(self, handler: Callable[[dict, int], None]):
66
+ self.user_event_completion_handler = handler
67
+
68
+ def _internal_event_completion_handler(self, event, status) -> None:
69
+ if self.user_event_completion_handler:
70
+ try:
71
+ self.user_event_completion_handler(event, status)
72
+ except Exception as e:
73
+ print(f"Error in user event completion handler: {e}")
74
+
59
75
  def get_command(self, timeout: int=5):
60
76
  block = timeout >= 0
61
77
  try:
@@ -65,10 +81,8 @@ class PeripheralDevice():
65
81
  return cmd
66
82
 
67
83
  def submit_event(self, event):
68
- self.lock.acquire()
69
- ret = self.ctx.submit_event(event)
70
- self.lock.release()
71
- return ret
84
+ with self.lock:
85
+ return self.ctx.submit_event(event)
72
86
 
73
87
  def notify_event(self, event):
74
88
  from warnings import warn
@@ -76,10 +90,8 @@ class PeripheralDevice():
76
90
  return self.submit_event(event)
77
91
 
78
92
  def register_file_ops(self, fops):
79
- self.lock.acquire()
80
- ret = self.ctx.register_file_ops(0, fops)
81
- self.lock.release()
82
- return ret
93
+ with self.lock:
94
+ return self.ctx.register_file_ops(0, fops)
83
95
 
84
96
  def is_sc_active(self):
85
97
  return self.ctx.is_sc_active()
@@ -108,10 +120,8 @@ class PeripheralDevice():
108
120
  self.thread.start()
109
121
 
110
122
  def get_file_tx_status(self):
111
- self.lock.acquire()
112
- ret = self.ctx.get_file_tx_status(0)
113
- self.lock.release()
114
- return ret
123
+ with self.lock:
124
+ return self.ctx.get_file_tx_status(0)
115
125
 
116
126
  def stop(self):
117
127
  if not self.thread:
@@ -1,17 +1,18 @@
1
1
  #
2
- # Copyright (c) 2020-2024 Siddharth Chandrasekaran <sidcha.dev@gmail.com>
2
+ # Copyright (c) 2020-2026 Siddharth Chandrasekaran <sidcha.dev@gmail.com>
3
3
  #
4
4
  # SPDX-License-Identifier: Apache-2.0
5
5
  #
6
6
 
7
7
  import os
8
8
  import re
9
+ import sys
9
10
  from setuptools import setup, Extension
10
11
  import shutil
11
12
  import subprocess
12
13
 
13
14
  project_name = "libosdp"
14
- project_version = "3.1.2"
15
+ project_version = "3.2.3"
15
16
  current_dir = os.path.dirname(os.path.realpath(__file__))
16
17
  repo_root = os.path.realpath(os.path.join(current_dir, ".."))
17
18
 
@@ -23,17 +24,47 @@ def add_prefix_to_path(src_list, path, check_files=True):
23
24
  raise RuntimeError(f"Path '{path}' does not exist")
24
25
  return paths
25
26
 
26
- def exec_cmd(cmd):
27
- r = subprocess.run(cmd, capture_output = True, text = True)
28
- return r.stdout.strip()
27
+ def exec_cmd(cmd, cwd=None):
28
+ r = subprocess.run(cmd, capture_output=True, text=True, cwd=cwd)
29
+ return r.returncode, r.stdout.strip()
29
30
 
30
31
  def get_git_info():
31
- d = {}
32
- d["branch"] = exec_cmd(["git", "rev-parse", "--abbrev-ref", "HEAD"])
33
- d["tag"] = exec_cmd([ "git", "describe", "--exact-match", "--tags" ])
34
- d["diff"] = exec_cmd([ "git", "diff", "--quiet", "--exit-code" ])
35
- d["rev"] = exec_cmd(["git", "log", "--pretty=format:%h", "-n", "1"])
36
- d["root"] = exec_cmd(["git", "rev-parse", "--show-toplevel"])
32
+ d = {
33
+ "branch": "None",
34
+ "tag": "",
35
+ "diff": "",
36
+ "rev": "",
37
+ "root": repo_root,
38
+ }
39
+
40
+ rc, _ = exec_cmd(["git", "rev-parse", "--is-inside-work-tree"], cwd=repo_root)
41
+ if rc != 0:
42
+ return d
43
+
44
+ rc, root = exec_cmd(["git", "rev-parse", "--show-toplevel"], cwd=repo_root)
45
+ if rc == 0 and root:
46
+ d["root"] = root
47
+
48
+ rc, branch = exec_cmd(["git", "symbolic-ref", "--short", "-q", "HEAD"], cwd=repo_root)
49
+ if rc == 0 and branch:
50
+ d["branch"] = branch
51
+ else:
52
+ d["branch"] = "detached"
53
+
54
+ rc, rev = exec_cmd(["git", "describe", "--tags", "--long", "--always", "--abbrev=7"], cwd=repo_root)
55
+ if rc == 0:
56
+ d["rev"] = rev
57
+
58
+ rc, tag = exec_cmd(["git", "describe", "--exact-match", "--tags", "HEAD"], cwd=repo_root)
59
+ if rc == 0 and tag:
60
+ d["tag"] = tag
61
+
62
+ rc, status = exec_cmd(["git", "status", "--porcelain", "--untracked-files=normal"], cwd=repo_root)
63
+ if rc == 0 and status:
64
+ d["diff"] = "+"
65
+ if d["tag"]:
66
+ d["tag"] = d["tag"] + "+"
67
+
37
68
  return d
38
69
 
39
70
  def configure_file(file, replacements):
@@ -78,7 +109,6 @@ def try_vendor_sources(src_dir, src_files, vendor_dir):
78
109
  utils_sources = [
79
110
  "utils/src/list.c",
80
111
  "utils/src/queue.c",
81
- "utils/src/slab.c",
82
112
  "utils/src/utils.c",
83
113
  "utils/src/logger.c",
84
114
  "utils/src/disjoint_set.c",
@@ -138,6 +168,19 @@ other_files = [
138
168
  "utils/src/pcap_gen.c",
139
169
  ]
140
170
 
171
+ definitions = [
172
+ "OPT_OSDP_PACKET_TRACE",
173
+ "OPT_OSDP_RX_ZERO_COPY",
174
+ # "OPT_OSDP_DATA_TRACE",
175
+ # "OPT_OSDP_SKIP_MARK_BYTE",
176
+ ]
177
+
178
+ app_owned_queue_data = os.getenv("OPT_OSDP_APP_OWNED_QUEUE_DATA", "").lower()
179
+ if app_owned_queue_data in ("0", "false", "no", "off"):
180
+ utils_sources.append("utils/src/slab.c")
181
+ else:
182
+ definitions.append("OPT_OSDP_APP_OWNED_QUEUE_DATA")
183
+
141
184
  source_files = utils_sources + lib_sources + osdp_sys_sources
142
185
 
143
186
  try_vendor_sources(
@@ -146,12 +189,6 @@ try_vendor_sources(
146
189
  "vendor"
147
190
  )
148
191
 
149
- definitions = [
150
- "OPT_OSDP_PACKET_TRACE",
151
- # "OPT_OSDP_DATA_TRACE",
152
- # "OPT_OSDP_SKIP_MARK_BYTE",
153
- ]
154
-
155
192
  if ("OPT_OSDP_PACKET_TRACE" in definitions or
156
193
  "OPT_OSDP_DATA_TRACE" in definitions):
157
194
  source_files += [
@@ -171,9 +208,15 @@ include_dirs = [
171
208
 
172
209
  compile_args = (
173
210
  [ "-I" + path for path in include_dirs ] +
174
- [ "-D" + define for define in definitions ]
211
+ [ "-D" + define + "=1" for define in definitions ]
175
212
  )
176
213
 
214
+ # PyPI Windows wheels are built with MSVC via cibuildwheel. Its legacy
215
+ # preprocessor breaks the IS_ENABLED() macro in utils/include/utils/utils.h;
216
+ # /Zc:preprocessor switches to the C99/C11-conformant preprocessor.
217
+ if sys.platform == "win32":
218
+ compile_args.append("/Zc:preprocessor")
219
+
177
220
  if os.path.exists("README.md"):
178
221
  with open("README.md", "r") as f:
179
222
  long_description = f.read()
@@ -205,6 +248,6 @@ setup(
205
248
  ],
206
249
  long_description = long_description,
207
250
  long_description_content_type = "text/markdown",
208
- python_requires = ">=3.8",
251
+ python_requires = ">=3.10",
209
252
  package_data = { project_name : other_files }
210
253
  )