oscura 0.5.1__py3-none-any.whl → 0.7.0__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 (497) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -47,165 +47,246 @@ class SwitchingEvent:
47
47
  event_type: Literal["turn_on", "turn_off"]
48
48
 
49
49
 
50
- def switching_loss(
50
+ def _prepare_switching_data(
51
51
  voltage: WaveformTrace,
52
52
  current: WaveformTrace,
53
- *,
54
- v_threshold: float | None = None,
55
- i_threshold: float | None = None,
56
- ) -> dict[str, Any]:
57
- """Calculate switching losses for a power device.
58
-
59
- Analyzes voltage and current waveforms to find switching transitions
60
- and calculate turn-on and turn-off energy losses.
53
+ ) -> tuple[NDArray[np.floating[Any]], NDArray[np.floating[Any]], NDArray[np.floating[Any]], float]:
54
+ """Prepare synchronized voltage, current, and power arrays.
61
55
 
62
56
  Args:
63
- voltage: Drain-source (or collector-emitter) voltage trace.
64
- current: Drain (or collector) current trace.
65
- v_threshold: Voltage threshold for on/off detection.
66
- If None, uses 10% of peak voltage.
67
- i_threshold: Current threshold for on/off detection.
68
- If None, uses 10% of peak current.
57
+ voltage: Drain-source voltage trace.
58
+ current: Drain current trace.
69
59
 
70
60
  Returns:
71
- Dictionary with:
72
- - e_on: Turn-on energy per event (Joules)
73
- - e_off: Turn-off energy per event (Joules)
74
- - e_total: Total switching energy per cycle (Joules)
75
- - p_sw: Switching power at estimated frequency (Watts)
76
- - events: List of SwitchingEvent objects
77
- - n_turn_on: Number of turn-on events
78
- - n_turn_off: Number of turn-off events
79
-
80
- Example:
81
- >>> losses = switching_loss(v_ds, i_d)
82
- >>> print(f"E_on: {losses['e_on']*1e6:.2f} uJ")
83
- >>> print(f"E_off: {losses['e_off']*1e6:.2f} uJ")
84
- >>> print(f"Switching power @ 100kHz: {losses['p_sw']*100e3:.2f} W")
85
-
86
- References:
87
- Infineon Application Note AN-9010
61
+ Tuple of (v_data, i_data, p_data, sample_period).
88
62
  """
89
- # Calculate instantaneous power
90
63
  power = instantaneous_power(voltage, current)
91
-
92
- # Ensure i_data matches v_data length (handle mismatched array sizes)
93
64
  min_len = min(len(voltage.data), len(current.data))
94
65
  v_data = voltage.data[:min_len]
95
66
  i_data = current.data[:min_len]
96
67
  p_data = power.data[:min_len]
97
68
  sample_period = power.metadata.time_base
69
+ return v_data, i_data, p_data, sample_period
70
+
71
+
72
+ def _compute_thresholds(
73
+ v_data: NDArray[np.floating[Any]],
74
+ i_data: NDArray[np.floating[Any]],
75
+ v_threshold: float | None,
76
+ i_threshold: float | None,
77
+ hysteresis_factor: float = 0.2,
78
+ ) -> tuple[float, float, float, float, float, float]:
79
+ """Compute voltage and current thresholds with hysteresis.
80
+
81
+ Args:
82
+ v_data: Voltage data array.
83
+ i_data: Current data array.
84
+ v_threshold: User-provided voltage threshold (or None for auto).
85
+ i_threshold: User-provided current threshold (or None for auto).
86
+ hysteresis_factor: Hysteresis band factor (default 20%).
98
87
 
99
- # Auto-detect thresholds if not provided
88
+ Returns:
89
+ Tuple of (v_threshold, i_threshold, v_high, v_low, i_high, i_low).
90
+ """
100
91
  if v_threshold is None:
101
92
  v_threshold = 0.1 * float(np.max(np.abs(v_data)))
102
93
  if i_threshold is None:
103
94
  i_threshold = 0.1 * float(np.max(np.abs(i_data)))
104
95
 
105
- # Add hysteresis to prevent false transitions due to ringing (Schmitt trigger)
106
- # Use 20% hysteresis band around thresholds
107
- hysteresis_factor = 0.2
108
96
  v_threshold_high = v_threshold * (1 + hysteresis_factor)
109
97
  v_threshold_low = v_threshold * (1 - hysteresis_factor)
110
98
  i_threshold_high = i_threshold * (1 + hysteresis_factor)
111
99
  i_threshold_low = i_threshold * (1 - hysteresis_factor)
112
100
 
113
- # Find switching events
114
- events: list[SwitchingEvent] = []
101
+ return (
102
+ v_threshold,
103
+ i_threshold,
104
+ v_threshold_high,
105
+ v_threshold_low,
106
+ i_threshold_high,
107
+ i_threshold_low,
108
+ )
109
+
110
+
111
+ def _build_device_state(
112
+ v_data: NDArray[np.floating[Any]],
113
+ i_data: NDArray[np.floating[Any]],
114
+ v_high: float,
115
+ v_low: float,
116
+ i_high: float,
117
+ i_low: float,
118
+ ) -> NDArray[np.signedinteger[Any]]:
119
+ """Build device state array using hysteresis comparator.
115
120
 
116
- # Determine device state at each sample with hysteresis
117
- # ON: low voltage, high current
118
- # OFF: high voltage, low current
119
- # Use hysteresis to avoid rapid state changes due to noise/ringing
120
- device_state = np.zeros(min_len, dtype=int) # 0=unknown, 1=on, 2=off
121
- current_state = 0 # Start in unknown state
121
+ Args:
122
+ v_data: Voltage data array.
123
+ i_data: Current data array.
124
+ v_high: High voltage threshold.
125
+ v_low: Low voltage threshold.
126
+ i_high: High current threshold.
127
+ i_low: Low current threshold.
128
+
129
+ Returns:
130
+ State array: 0=unknown, 1=on (low V, high I), 2=off (high V, low I).
131
+ """
132
+ min_len = len(v_data)
133
+ device_state = np.zeros(min_len, dtype=int)
134
+ current_state = 0
122
135
 
123
136
  for i in range(min_len):
124
137
  v = v_data[i]
125
138
  i_val = i_data[i]
126
139
 
127
- # Determine next state based on current state and measurements
128
140
  if current_state == 1: # Currently ON
129
- # Stay ON unless voltage goes high (with hysteresis)
130
- if v > v_threshold_high:
141
+ if v > v_high:
131
142
  current_state = 2 # Transition to OFF
132
143
  elif current_state == 2: # Currently OFF
133
- # Stay OFF unless voltage goes low (with hysteresis)
134
- if v < v_threshold_low and i_val > i_threshold_low:
144
+ if v < v_low and i_val > i_low:
135
145
  current_state = 1 # Transition to ON
136
- else: # Unknown state - determine initial state
137
- if v < v_threshold_low and i_val > i_threshold_high:
146
+ else: # Unknown state
147
+ if v < v_low and i_val > i_high:
138
148
  current_state = 1 # ON
139
- elif v > v_threshold_high and i_val < i_threshold_low:
149
+ elif v > v_high and i_val < i_low:
140
150
  current_state = 2 # OFF
141
151
 
142
152
  device_state[i] = current_state
143
153
 
154
+ return device_state
155
+
156
+
157
+ def _find_transition_events(
158
+ device_state: NDArray[np.signedinteger[Any]],
159
+ p_data: NDArray[np.floating[Any]],
160
+ sample_period: float,
161
+ ) -> list[SwitchingEvent]:
162
+ """Find all switching events in device state trajectory.
163
+
164
+ Args:
165
+ device_state: State array (0=unknown, 1=on, 2=off).
166
+ p_data: Power data array.
167
+ sample_period: Sample period in seconds.
168
+
169
+ Returns:
170
+ List of switching events (turn-on and turn-off).
171
+ """
172
+ from scipy.integrate import trapezoid
173
+
174
+ events: list[SwitchingEvent] = []
144
175
  device_on = device_state == 1
145
176
  device_off = device_state == 2
146
177
 
147
- # Find transitions
148
178
  i = 0
149
179
  while i < len(device_on) - 1:
150
- # Look for turn-on: device was off, now turning on
180
+ # Check for turn-on transition
151
181
  if device_off[i] and not device_off[i + 1]:
152
- # Find end of transition (device fully on)
153
- start_idx = i
154
- end_idx = start_idx + 1
155
- while end_idx < len(device_on) and not device_on[end_idx]:
156
- end_idx += 1
157
-
158
- if end_idx < len(device_on):
159
- # Calculate transition energy (scipy for stable API)
160
- from scipy.integrate import trapezoid
161
-
162
- transition_power = p_data[start_idx : end_idx + 1]
163
- e = float(trapezoid(transition_power, dx=sample_period))
164
- peak_p = float(np.max(transition_power))
165
-
166
- events.append(
167
- SwitchingEvent(
168
- start_time=start_idx * sample_period,
169
- end_time=end_idx * sample_period,
170
- duration=(end_idx - start_idx) * sample_period,
171
- energy=e,
172
- peak_power=peak_p,
173
- event_type="turn_on",
174
- )
175
- )
176
- i = end_idx
182
+ event = _create_turn_on_event(i, device_on, p_data, sample_period, trapezoid)
183
+ if event:
184
+ events.append(event)
185
+ i = int(event.end_time / sample_period)
177
186
  continue
178
187
 
179
- # Look for turn-off: device was on, now turning off
188
+ # Check for turn-off transition
180
189
  if device_on[i] and not device_on[i + 1]:
181
- start_idx = i
182
- end_idx = start_idx + 1
183
- while end_idx < len(device_off) and not device_off[end_idx]:
184
- end_idx += 1
185
-
186
- if end_idx < len(device_off):
187
- from scipy.integrate import trapezoid
188
-
189
- transition_power = p_data[start_idx : end_idx + 1]
190
- e = float(trapezoid(transition_power, dx=sample_period))
191
- peak_p = float(np.max(transition_power))
192
-
193
- events.append(
194
- SwitchingEvent(
195
- start_time=start_idx * sample_period,
196
- end_time=end_idx * sample_period,
197
- duration=(end_idx - start_idx) * sample_period,
198
- energy=e,
199
- peak_power=peak_p,
200
- event_type="turn_off",
201
- )
202
- )
203
- i = end_idx
190
+ event = _create_turn_off_event(i, device_off, p_data, sample_period, trapezoid)
191
+ if event:
192
+ events.append(event)
193
+ i = int(event.end_time / sample_period)
204
194
  continue
205
195
 
206
196
  i += 1
207
197
 
208
- # Calculate average energies
198
+ return events
199
+
200
+
201
+ def _create_turn_on_event(
202
+ start_idx: int,
203
+ device_on: NDArray[np.bool_],
204
+ p_data: NDArray[np.floating[Any]],
205
+ sample_period: float,
206
+ trapezoid: Any,
207
+ ) -> SwitchingEvent | None:
208
+ """Create turn-on event from transition indices.
209
+
210
+ Args:
211
+ start_idx: Start index of transition.
212
+ device_on: Boolean array indicating ON state.
213
+ p_data: Power data array.
214
+ sample_period: Sample period in seconds.
215
+ trapezoid: Integration function.
216
+
217
+ Returns:
218
+ SwitchingEvent or None if invalid transition.
219
+ """
220
+ end_idx = start_idx + 1
221
+ while end_idx < len(device_on) and not device_on[end_idx]:
222
+ end_idx += 1
223
+
224
+ if end_idx >= len(device_on):
225
+ return None
226
+
227
+ transition_power = p_data[start_idx : end_idx + 1]
228
+ e = float(trapezoid(transition_power, dx=sample_period))
229
+ peak_p = float(np.max(transition_power))
230
+
231
+ return SwitchingEvent(
232
+ start_time=start_idx * sample_period,
233
+ end_time=end_idx * sample_period,
234
+ duration=(end_idx - start_idx) * sample_period,
235
+ energy=e,
236
+ peak_power=peak_p,
237
+ event_type="turn_on",
238
+ )
239
+
240
+
241
+ def _create_turn_off_event(
242
+ start_idx: int,
243
+ device_off: NDArray[np.bool_],
244
+ p_data: NDArray[np.floating[Any]],
245
+ sample_period: float,
246
+ trapezoid: Any,
247
+ ) -> SwitchingEvent | None:
248
+ """Create turn-off event from transition indices.
249
+
250
+ Args:
251
+ start_idx: Start index of transition.
252
+ device_off: Boolean array indicating OFF state.
253
+ p_data: Power data array.
254
+ sample_period: Sample period in seconds.
255
+ trapezoid: Integration function.
256
+
257
+ Returns:
258
+ SwitchingEvent or None if invalid transition.
259
+ """
260
+ end_idx = start_idx + 1
261
+ while end_idx < len(device_off) and not device_off[end_idx]:
262
+ end_idx += 1
263
+
264
+ if end_idx >= len(device_off):
265
+ return None
266
+
267
+ transition_power = p_data[start_idx : end_idx + 1]
268
+ e = float(trapezoid(transition_power, dx=sample_period))
269
+ peak_p = float(np.max(transition_power))
270
+
271
+ return SwitchingEvent(
272
+ start_time=start_idx * sample_period,
273
+ end_time=end_idx * sample_period,
274
+ duration=(end_idx - start_idx) * sample_period,
275
+ energy=e,
276
+ peak_power=peak_p,
277
+ event_type="turn_off",
278
+ )
279
+
280
+
281
+ def _calculate_loss_metrics(events: list[SwitchingEvent]) -> dict[str, Any]:
282
+ """Calculate energy and power metrics from switching events.
283
+
284
+ Args:
285
+ events: List of switching events.
286
+
287
+ Returns:
288
+ Dictionary with energy, frequency, and power metrics.
289
+ """
209
290
  turn_on_events = [e for e in events if e.event_type == "turn_on"]
210
291
  turn_off_events = [e for e in events if e.event_type == "turn_off"]
211
292
 
@@ -213,10 +294,10 @@ def switching_loss(
213
294
  e_off = float(np.mean([e.energy for e in turn_off_events])) if turn_off_events else 0.0
214
295
  e_total = e_on + e_off
215
296
 
216
- # Estimate switching frequency from event spacing
297
+ # Estimate switching frequency
217
298
  if len(events) >= 2:
218
299
  event_times = [e.start_time for e in events]
219
- avg_period = float(np.mean(np.diff(event_times))) * 2 # Full cycle
300
+ avg_period = float(np.mean(np.diff(event_times))) * 2
220
301
  f_sw = 1.0 / avg_period if avg_period > 0 else 0.0
221
302
  else:
222
303
  f_sw = 0.0
@@ -226,13 +307,61 @@ def switching_loss(
226
307
  "e_off": e_off,
227
308
  "e_total": e_total,
228
309
  "f_sw": f_sw,
229
- "p_sw": e_total * f_sw, # Switching power at this frequency
310
+ "p_sw": e_total * f_sw,
230
311
  "events": events,
231
312
  "n_turn_on": len(turn_on_events),
232
313
  "n_turn_off": len(turn_off_events),
233
314
  }
234
315
 
235
316
 
317
+ def switching_loss(
318
+ voltage: WaveformTrace,
319
+ current: WaveformTrace,
320
+ *,
321
+ v_threshold: float | None = None,
322
+ i_threshold: float | None = None,
323
+ ) -> dict[str, Any]:
324
+ """Calculate switching losses for a power device.
325
+
326
+ Analyzes voltage and current waveforms to find switching transitions
327
+ and calculate turn-on and turn-off energy losses.
328
+
329
+ Args:
330
+ voltage: Drain-source (or collector-emitter) voltage trace.
331
+ current: Drain (or collector) current trace.
332
+ v_threshold: Voltage threshold for on/off detection.
333
+ If None, uses 10% of peak voltage.
334
+ i_threshold: Current threshold for on/off detection.
335
+ If None, uses 10% of peak current.
336
+
337
+ Returns:
338
+ Dictionary with:
339
+ - e_on: Turn-on energy per event (Joules)
340
+ - e_off: Turn-off energy per event (Joules)
341
+ - e_total: Total switching energy per cycle (Joules)
342
+ - p_sw: Switching power at estimated frequency (Watts)
343
+ - events: List of SwitchingEvent objects
344
+ - n_turn_on: Number of turn-on events
345
+ - n_turn_off: Number of turn-off events
346
+
347
+ Example:
348
+ >>> losses = switching_loss(v_ds, i_d)
349
+ >>> print(f"E_on: {losses['e_on']*1e6:.2f} uJ")
350
+ >>> print(f"E_off: {losses['e_off']*1e6:.2f} uJ")
351
+ >>> print(f"Switching power @ 100kHz: {losses['p_sw']*100e3:.2f} W")
352
+
353
+ References:
354
+ Infineon Application Note AN-9010
355
+ """
356
+ v_data, i_data, p_data, sample_period = _prepare_switching_data(voltage, current)
357
+ _, _, v_high, v_low, i_high, i_low = _compute_thresholds(
358
+ v_data, i_data, v_threshold, i_threshold
359
+ )
360
+ device_state = _build_device_state(v_data, i_data, v_high, v_low, i_high, i_low)
361
+ events = _find_transition_events(device_state, p_data, sample_period)
362
+ return _calculate_loss_metrics(events)
363
+
364
+
236
365
  def switching_energy(
237
366
  voltage: WaveformTrace,
238
367
  current: WaveformTrace,
@@ -425,6 +554,17 @@ def switching_times(
425
554
  def find_transition_time(
426
555
  data: NDArray[np.floating[Any]], low: float, high: float, rising: bool
427
556
  ) -> float:
557
+ """Find transition time between threshold levels.
558
+
559
+ Args:
560
+ data: Signal data array.
561
+ low: Lower threshold level (10% point).
562
+ high: Upper threshold level (90% point).
563
+ rising: True for rising transition, False for falling.
564
+
565
+ Returns:
566
+ Transition time in seconds, or np.nan if not found.
567
+ """
428
568
  if rising:
429
569
  below_low = data < low
430
570
  start_idx_arr = np.where(below_low[:-1] & ~below_low[1:])[0]
@@ -1,15 +1,31 @@
1
1
  """Protocol decoding module.
2
2
 
3
+ .. deprecated:: 0.6.0
4
+ This module (singular 'protocol') is deprecated in favor of 'protocols' (plural).
5
+ Use ``from oscura.analyzers.protocols import UARTDecoder`` instead.
6
+ This module will be removed in v1.0.0.
7
+
3
8
  This module re-exports protocol decoders from the protocols package
4
9
  for convenient access. Both import paths are equivalent:
5
10
 
6
- from oscura.analyzers.protocol import UARTDecoder # singular (this module)
11
+ from oscura.analyzers.protocol import UARTDecoder # singular (deprecated)
7
12
  from oscura.analyzers.protocols import UARTDecoder # plural (recommended)
8
13
 
9
14
  The plural form (protocols) is recommended as the canonical import path.
10
15
  See IMPORT-PATHS.md in the repository root for detailed guidelines.
11
16
  """
12
17
 
18
+ import warnings
19
+
20
+ # Issue deprecation warning when this module is imported
21
+ warnings.warn(
22
+ "Importing from 'oscura.analyzers.protocol' (singular) is deprecated. "
23
+ "Use 'oscura.analyzers.protocols' (plural) instead. "
24
+ "This module will be removed in v1.0.0.",
25
+ DeprecationWarning,
26
+ stacklevel=2,
27
+ )
28
+
13
29
  from oscura.analyzers.protocols import (
14
30
  CAN_BITRATES,
15
31
  CANFD_DLC_TO_LENGTH,
@@ -172,18 +172,18 @@ class ProtocolDecoder(ABC):
172
172
  license: str = "MIT"
173
173
 
174
174
  # Input/output types
175
- inputs: list[str] = ["logic"] # noqa: RUF012
176
- outputs: list[str] = ["packets"] # noqa: RUF012
175
+ inputs: list[str] = ["logic"]
176
+ outputs: list[str] = ["packets"]
177
177
 
178
178
  # Channel definitions
179
- channels: list[ChannelDef] = [] # noqa: RUF012
180
- optional_channels: list[ChannelDef] = [] # noqa: RUF012
179
+ channels: list[ChannelDef] = []
180
+ optional_channels: list[ChannelDef] = []
181
181
 
182
182
  # Options
183
- options: list[OptionDef] = [] # noqa: RUF012
183
+ options: list[OptionDef] = []
184
184
 
185
185
  # Annotation definitions (override in subclass)
186
- annotations: list[tuple[str, str]] = [] # noqa: RUF012
186
+ annotations: list[tuple[str, str]] = []
187
187
 
188
188
  def __init__(self, **options: Any) -> None:
189
189
  """Initialize decoder with options.
@@ -0,0 +1,38 @@
1
+ """BLE (Bluetooth Low Energy) protocol analysis.
2
+
3
+ This module provides comprehensive BLE protocol decoding with GATT service
4
+ discovery, advertising packet parsing, and ATT operation decoding.
5
+
6
+ Example:
7
+ >>> from oscura.analyzers.protocols.ble import BLEAnalyzer
8
+ >>> analyzer = BLEAnalyzer()
9
+ >>> analyzer.add_packet(packet)
10
+ >>> services = analyzer.discover_services()
11
+
12
+ References:
13
+ Bluetooth Core Specification v5.4: https://www.bluetooth.com/specifications/specs/
14
+ """
15
+
16
+ from oscura.analyzers.protocols.ble.analyzer import (
17
+ BLEAnalyzer,
18
+ BLEPacket,
19
+ GATTCharacteristic,
20
+ GATTDescriptor,
21
+ GATTService,
22
+ )
23
+ from oscura.analyzers.protocols.ble.uuids import (
24
+ STANDARD_CHARACTERISTICS,
25
+ STANDARD_DESCRIPTORS,
26
+ STANDARD_SERVICES,
27
+ )
28
+
29
+ __all__ = [
30
+ "STANDARD_CHARACTERISTICS",
31
+ "STANDARD_DESCRIPTORS",
32
+ "STANDARD_SERVICES",
33
+ "BLEAnalyzer",
34
+ "BLEPacket",
35
+ "GATTCharacteristic",
36
+ "GATTDescriptor",
37
+ "GATTService",
38
+ ]