oscura 0.5.0__py3-none-any.whl → 0.6.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 (513) 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/__init__.py +0 -48
  5. oscura/analyzers/digital/edges.py +325 -65
  6. oscura/analyzers/digital/extraction.py +0 -195
  7. oscura/analyzers/digital/quality.py +293 -166
  8. oscura/analyzers/digital/timing.py +260 -115
  9. oscura/analyzers/digital/timing_numba.py +334 -0
  10. oscura/analyzers/entropy.py +605 -0
  11. oscura/analyzers/eye/diagram.py +176 -109
  12. oscura/analyzers/eye/metrics.py +5 -5
  13. oscura/analyzers/jitter/__init__.py +6 -4
  14. oscura/analyzers/jitter/ber.py +52 -52
  15. oscura/analyzers/jitter/classification.py +156 -0
  16. oscura/analyzers/jitter/decomposition.py +163 -113
  17. oscura/analyzers/jitter/spectrum.py +80 -64
  18. oscura/analyzers/ml/__init__.py +39 -0
  19. oscura/analyzers/ml/features.py +600 -0
  20. oscura/analyzers/ml/signal_classifier.py +604 -0
  21. oscura/analyzers/packet/daq.py +246 -158
  22. oscura/analyzers/packet/parser.py +12 -1
  23. oscura/analyzers/packet/payload.py +50 -2110
  24. oscura/analyzers/packet/payload_analysis.py +361 -181
  25. oscura/analyzers/packet/payload_patterns.py +133 -70
  26. oscura/analyzers/packet/stream.py +84 -23
  27. oscura/analyzers/patterns/__init__.py +26 -5
  28. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  29. oscura/analyzers/patterns/clustering.py +169 -108
  30. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  31. oscura/analyzers/patterns/discovery.py +1 -1
  32. oscura/analyzers/patterns/matching.py +581 -197
  33. oscura/analyzers/patterns/pattern_mining.py +778 -0
  34. oscura/analyzers/patterns/periodic.py +121 -38
  35. oscura/analyzers/patterns/sequences.py +175 -78
  36. oscura/analyzers/power/conduction.py +1 -1
  37. oscura/analyzers/power/soa.py +6 -6
  38. oscura/analyzers/power/switching.py +250 -110
  39. oscura/analyzers/protocol/__init__.py +17 -1
  40. oscura/analyzers/protocols/__init__.py +1 -22
  41. oscura/analyzers/protocols/base.py +6 -6
  42. oscura/analyzers/protocols/ble/__init__.py +38 -0
  43. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  44. oscura/analyzers/protocols/ble/uuids.py +288 -0
  45. oscura/analyzers/protocols/can.py +257 -127
  46. oscura/analyzers/protocols/can_fd.py +107 -80
  47. oscura/analyzers/protocols/flexray.py +139 -80
  48. oscura/analyzers/protocols/hdlc.py +93 -58
  49. oscura/analyzers/protocols/i2c.py +247 -106
  50. oscura/analyzers/protocols/i2s.py +138 -86
  51. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  52. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  53. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  54. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  55. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  56. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  57. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  58. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  59. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  60. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  61. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  62. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  63. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  64. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  65. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  66. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  67. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  68. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  69. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  70. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  71. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  72. oscura/analyzers/protocols/jtag.py +180 -98
  73. oscura/analyzers/protocols/lin.py +219 -114
  74. oscura/analyzers/protocols/manchester.py +4 -4
  75. oscura/analyzers/protocols/onewire.py +253 -149
  76. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  77. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  78. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  79. oscura/analyzers/protocols/spi.py +192 -95
  80. oscura/analyzers/protocols/swd.py +321 -167
  81. oscura/analyzers/protocols/uart.py +267 -125
  82. oscura/analyzers/protocols/usb.py +235 -131
  83. oscura/analyzers/side_channel/power.py +17 -12
  84. oscura/analyzers/signal/__init__.py +15 -0
  85. oscura/analyzers/signal/timing_analysis.py +1086 -0
  86. oscura/analyzers/signal_integrity/__init__.py +4 -1
  87. oscura/analyzers/signal_integrity/sparams.py +2 -19
  88. oscura/analyzers/spectral/chunked.py +129 -60
  89. oscura/analyzers/spectral/chunked_fft.py +300 -94
  90. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  91. oscura/analyzers/statistical/checksum.py +376 -217
  92. oscura/analyzers/statistical/classification.py +229 -107
  93. oscura/analyzers/statistical/entropy.py +78 -53
  94. oscura/analyzers/statistics/correlation.py +407 -211
  95. oscura/analyzers/statistics/outliers.py +2 -2
  96. oscura/analyzers/statistics/streaming.py +30 -5
  97. oscura/analyzers/validation.py +216 -101
  98. oscura/analyzers/waveform/measurements.py +9 -0
  99. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  100. oscura/analyzers/waveform/spectral.py +500 -228
  101. oscura/api/__init__.py +31 -5
  102. oscura/api/dsl/__init__.py +582 -0
  103. oscura/{dsl → api/dsl}/commands.py +43 -76
  104. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  105. oscura/{dsl → api/dsl}/parser.py +107 -77
  106. oscura/{dsl → api/dsl}/repl.py +2 -2
  107. oscura/api/dsl.py +1 -1
  108. oscura/{integrations → api/integrations}/__init__.py +1 -1
  109. oscura/{integrations → api/integrations}/llm.py +201 -102
  110. oscura/api/operators.py +3 -3
  111. oscura/api/optimization.py +144 -30
  112. oscura/api/rest_server.py +921 -0
  113. oscura/api/server/__init__.py +17 -0
  114. oscura/api/server/dashboard.py +850 -0
  115. oscura/api/server/static/README.md +34 -0
  116. oscura/api/server/templates/base.html +181 -0
  117. oscura/api/server/templates/export.html +120 -0
  118. oscura/api/server/templates/home.html +284 -0
  119. oscura/api/server/templates/protocols.html +58 -0
  120. oscura/api/server/templates/reports.html +43 -0
  121. oscura/api/server/templates/session_detail.html +89 -0
  122. oscura/api/server/templates/sessions.html +83 -0
  123. oscura/api/server/templates/waveforms.html +73 -0
  124. oscura/automotive/__init__.py +8 -1
  125. oscura/automotive/can/__init__.py +10 -0
  126. oscura/automotive/can/checksum.py +3 -1
  127. oscura/automotive/can/dbc_generator.py +590 -0
  128. oscura/automotive/can/message_wrapper.py +121 -74
  129. oscura/automotive/can/patterns.py +98 -21
  130. oscura/automotive/can/session.py +292 -56
  131. oscura/automotive/can/state_machine.py +6 -3
  132. oscura/automotive/can/stimulus_response.py +97 -75
  133. oscura/automotive/dbc/__init__.py +10 -2
  134. oscura/automotive/dbc/generator.py +84 -56
  135. oscura/automotive/dbc/parser.py +6 -6
  136. oscura/automotive/dtc/data.json +2763 -0
  137. oscura/automotive/dtc/database.py +2 -2
  138. oscura/automotive/flexray/__init__.py +31 -0
  139. oscura/automotive/flexray/analyzer.py +504 -0
  140. oscura/automotive/flexray/crc.py +185 -0
  141. oscura/automotive/flexray/fibex.py +449 -0
  142. oscura/automotive/j1939/__init__.py +45 -8
  143. oscura/automotive/j1939/analyzer.py +605 -0
  144. oscura/automotive/j1939/spns.py +326 -0
  145. oscura/automotive/j1939/transport.py +306 -0
  146. oscura/automotive/lin/__init__.py +47 -0
  147. oscura/automotive/lin/analyzer.py +612 -0
  148. oscura/automotive/loaders/blf.py +13 -2
  149. oscura/automotive/loaders/csv_can.py +143 -72
  150. oscura/automotive/loaders/dispatcher.py +50 -2
  151. oscura/automotive/loaders/mdf.py +86 -45
  152. oscura/automotive/loaders/pcap.py +111 -61
  153. oscura/automotive/uds/__init__.py +4 -0
  154. oscura/automotive/uds/analyzer.py +725 -0
  155. oscura/automotive/uds/decoder.py +140 -58
  156. oscura/automotive/uds/models.py +7 -1
  157. oscura/automotive/visualization.py +1 -1
  158. oscura/cli/analyze.py +348 -0
  159. oscura/cli/batch.py +142 -122
  160. oscura/cli/benchmark.py +275 -0
  161. oscura/cli/characterize.py +137 -82
  162. oscura/cli/compare.py +224 -131
  163. oscura/cli/completion.py +250 -0
  164. oscura/cli/config_cmd.py +361 -0
  165. oscura/cli/decode.py +164 -87
  166. oscura/cli/export.py +286 -0
  167. oscura/cli/main.py +115 -31
  168. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  169. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  170. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  171. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  172. oscura/cli/progress.py +147 -0
  173. oscura/cli/shell.py +157 -135
  174. oscura/cli/validate_cmd.py +204 -0
  175. oscura/cli/visualize.py +158 -0
  176. oscura/convenience.py +125 -79
  177. oscura/core/__init__.py +4 -2
  178. oscura/core/backend_selector.py +3 -3
  179. oscura/core/cache.py +126 -15
  180. oscura/core/cancellation.py +1 -1
  181. oscura/{config → core/config}/__init__.py +20 -11
  182. oscura/{config → core/config}/defaults.py +1 -1
  183. oscura/{config → core/config}/loader.py +7 -5
  184. oscura/{config → core/config}/memory.py +5 -5
  185. oscura/{config → core/config}/migration.py +1 -1
  186. oscura/{config → core/config}/pipeline.py +99 -23
  187. oscura/{config → core/config}/preferences.py +1 -1
  188. oscura/{config → core/config}/protocol.py +3 -3
  189. oscura/{config → core/config}/schema.py +426 -272
  190. oscura/{config → core/config}/settings.py +1 -1
  191. oscura/{config → core/config}/thresholds.py +195 -153
  192. oscura/core/correlation.py +5 -6
  193. oscura/core/cross_domain.py +0 -2
  194. oscura/core/debug.py +9 -5
  195. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  196. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  197. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  198. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  199. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  200. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  201. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  202. oscura/core/gpu_backend.py +11 -7
  203. oscura/core/log_query.py +101 -11
  204. oscura/core/logging.py +126 -54
  205. oscura/core/logging_advanced.py +5 -5
  206. oscura/core/memory_limits.py +108 -70
  207. oscura/core/memory_monitor.py +2 -2
  208. oscura/core/memory_progress.py +7 -7
  209. oscura/core/memory_warnings.py +1 -1
  210. oscura/core/numba_backend.py +13 -13
  211. oscura/{plugins → core/plugins}/__init__.py +9 -9
  212. oscura/{plugins → core/plugins}/base.py +7 -7
  213. oscura/{plugins → core/plugins}/cli.py +3 -3
  214. oscura/{plugins → core/plugins}/discovery.py +186 -106
  215. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  216. oscura/{plugins → core/plugins}/manager.py +7 -7
  217. oscura/{plugins → core/plugins}/registry.py +3 -3
  218. oscura/{plugins → core/plugins}/versioning.py +1 -1
  219. oscura/core/progress.py +16 -1
  220. oscura/core/provenance.py +8 -2
  221. oscura/{schemas → core/schemas}/__init__.py +2 -2
  222. oscura/core/schemas/bus_configuration.json +322 -0
  223. oscura/core/schemas/device_mapping.json +182 -0
  224. oscura/core/schemas/packet_format.json +418 -0
  225. oscura/core/schemas/protocol_definition.json +363 -0
  226. oscura/core/types.py +4 -0
  227. oscura/core/uncertainty.py +3 -3
  228. oscura/correlation/__init__.py +52 -0
  229. oscura/correlation/multi_protocol.py +811 -0
  230. oscura/discovery/auto_decoder.py +117 -35
  231. oscura/discovery/comparison.py +191 -86
  232. oscura/discovery/quality_validator.py +155 -68
  233. oscura/discovery/signal_detector.py +196 -79
  234. oscura/export/__init__.py +18 -20
  235. oscura/export/kaitai_struct.py +513 -0
  236. oscura/export/scapy_layer.py +801 -0
  237. oscura/export/wireshark/README.md +15 -15
  238. oscura/export/wireshark/generator.py +1 -1
  239. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  240. oscura/export/wireshark_dissector.py +746 -0
  241. oscura/guidance/wizard.py +207 -111
  242. oscura/hardware/__init__.py +19 -0
  243. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  244. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  245. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  246. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  247. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  248. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  249. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  250. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  251. oscura/hardware/firmware/__init__.py +29 -0
  252. oscura/hardware/firmware/pattern_recognition.py +874 -0
  253. oscura/hardware/hal_detector.py +736 -0
  254. oscura/hardware/security/__init__.py +37 -0
  255. oscura/hardware/security/side_channel_detector.py +1126 -0
  256. oscura/inference/__init__.py +4 -0
  257. oscura/inference/active_learning/README.md +7 -7
  258. oscura/inference/active_learning/observation_table.py +4 -1
  259. oscura/inference/alignment.py +216 -123
  260. oscura/inference/bayesian.py +113 -33
  261. oscura/inference/crc_reverse.py +101 -55
  262. oscura/inference/logic.py +6 -2
  263. oscura/inference/message_format.py +342 -183
  264. oscura/inference/protocol.py +95 -44
  265. oscura/inference/protocol_dsl.py +180 -82
  266. oscura/inference/signal_intelligence.py +1439 -706
  267. oscura/inference/spectral.py +99 -57
  268. oscura/inference/state_machine.py +810 -158
  269. oscura/inference/stream.py +270 -110
  270. oscura/iot/__init__.py +34 -0
  271. oscura/iot/coap/__init__.py +32 -0
  272. oscura/iot/coap/analyzer.py +668 -0
  273. oscura/iot/coap/options.py +212 -0
  274. oscura/iot/lorawan/__init__.py +21 -0
  275. oscura/iot/lorawan/crypto.py +206 -0
  276. oscura/iot/lorawan/decoder.py +801 -0
  277. oscura/iot/lorawan/mac_commands.py +341 -0
  278. oscura/iot/mqtt/__init__.py +27 -0
  279. oscura/iot/mqtt/analyzer.py +999 -0
  280. oscura/iot/mqtt/properties.py +315 -0
  281. oscura/iot/zigbee/__init__.py +31 -0
  282. oscura/iot/zigbee/analyzer.py +615 -0
  283. oscura/iot/zigbee/security.py +153 -0
  284. oscura/iot/zigbee/zcl.py +349 -0
  285. oscura/jupyter/display.py +125 -45
  286. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  287. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  288. oscura/jupyter/exploratory/fuzzy.py +746 -0
  289. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  290. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  291. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  292. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  293. oscura/jupyter/exploratory/sync.py +612 -0
  294. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  295. oscura/jupyter/magic.py +4 -4
  296. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  297. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  298. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  299. oscura/loaders/__init__.py +171 -63
  300. oscura/loaders/binary.py +88 -1
  301. oscura/loaders/chipwhisperer.py +153 -137
  302. oscura/loaders/configurable.py +208 -86
  303. oscura/loaders/csv_loader.py +458 -215
  304. oscura/loaders/hdf5_loader.py +278 -119
  305. oscura/loaders/lazy.py +87 -54
  306. oscura/loaders/mmap_loader.py +1 -1
  307. oscura/loaders/numpy_loader.py +253 -116
  308. oscura/loaders/pcap.py +226 -151
  309. oscura/loaders/rigol.py +110 -49
  310. oscura/loaders/sigrok.py +201 -78
  311. oscura/loaders/tdms.py +81 -58
  312. oscura/loaders/tektronix.py +291 -174
  313. oscura/loaders/touchstone.py +182 -87
  314. oscura/loaders/vcd.py +215 -117
  315. oscura/loaders/wav.py +155 -68
  316. oscura/reporting/__init__.py +9 -7
  317. oscura/reporting/analyze.py +352 -146
  318. oscura/reporting/argument_preparer.py +69 -14
  319. oscura/reporting/auto_report.py +97 -61
  320. oscura/reporting/batch.py +131 -58
  321. oscura/reporting/chart_selection.py +57 -45
  322. oscura/reporting/comparison.py +63 -17
  323. oscura/reporting/content/executive.py +76 -24
  324. oscura/reporting/core_formats/multi_format.py +11 -8
  325. oscura/reporting/engine.py +312 -158
  326. oscura/reporting/enhanced_reports.py +949 -0
  327. oscura/reporting/export.py +86 -43
  328. oscura/reporting/formatting/numbers.py +69 -42
  329. oscura/reporting/html.py +139 -58
  330. oscura/reporting/index.py +137 -65
  331. oscura/reporting/output.py +158 -67
  332. oscura/reporting/pdf.py +67 -102
  333. oscura/reporting/plots.py +191 -112
  334. oscura/reporting/sections.py +88 -47
  335. oscura/reporting/standards.py +104 -61
  336. oscura/reporting/summary_generator.py +75 -55
  337. oscura/reporting/tables.py +138 -54
  338. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  339. oscura/reporting/templates/index.md +13 -13
  340. oscura/sessions/__init__.py +14 -23
  341. oscura/sessions/base.py +3 -3
  342. oscura/sessions/blackbox.py +106 -10
  343. oscura/sessions/generic.py +2 -2
  344. oscura/sessions/legacy.py +783 -0
  345. oscura/side_channel/__init__.py +63 -0
  346. oscura/side_channel/dpa.py +1025 -0
  347. oscura/utils/__init__.py +15 -1
  348. oscura/utils/autodetect.py +1 -5
  349. oscura/utils/bitwise.py +118 -0
  350. oscura/{builders → utils/builders}/__init__.py +1 -1
  351. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  352. oscura/{comparison → utils/comparison}/compare.py +202 -101
  353. oscura/{comparison → utils/comparison}/golden.py +83 -63
  354. oscura/{comparison → utils/comparison}/limits.py +313 -89
  355. oscura/{comparison → utils/comparison}/mask.py +151 -45
  356. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  357. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  358. oscura/{component → utils/component}/__init__.py +3 -3
  359. oscura/{component → utils/component}/impedance.py +122 -58
  360. oscura/{component → utils/component}/reactive.py +165 -168
  361. oscura/{component → utils/component}/transmission_line.py +3 -3
  362. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  363. oscura/{filtering → utils/filtering}/base.py +1 -1
  364. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  365. oscura/{filtering → utils/filtering}/design.py +169 -93
  366. oscura/{filtering → utils/filtering}/filters.py +2 -2
  367. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  368. oscura/utils/geometry.py +31 -0
  369. oscura/utils/imports.py +184 -0
  370. oscura/utils/lazy.py +1 -1
  371. oscura/{math → utils/math}/__init__.py +2 -2
  372. oscura/{math → utils/math}/arithmetic.py +114 -48
  373. oscura/{math → utils/math}/interpolation.py +139 -106
  374. oscura/utils/memory.py +129 -66
  375. oscura/utils/memory_advanced.py +92 -9
  376. oscura/utils/memory_extensions.py +10 -8
  377. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  378. oscura/{optimization → utils/optimization}/search.py +2 -2
  379. oscura/utils/performance/__init__.py +58 -0
  380. oscura/utils/performance/caching.py +889 -0
  381. oscura/utils/performance/lsh_clustering.py +333 -0
  382. oscura/utils/performance/memory_optimizer.py +699 -0
  383. oscura/utils/performance/optimizations.py +675 -0
  384. oscura/utils/performance/parallel.py +654 -0
  385. oscura/utils/performance/profiling.py +661 -0
  386. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  387. oscura/{pipeline → utils/pipeline}/composition.py +11 -3
  388. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  389. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  390. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  391. oscura/{search → utils/search}/__init__.py +3 -3
  392. oscura/{search → utils/search}/anomaly.py +188 -58
  393. oscura/utils/search/context.py +294 -0
  394. oscura/{search → utils/search}/pattern.py +138 -10
  395. oscura/utils/serial.py +51 -0
  396. oscura/utils/storage/__init__.py +61 -0
  397. oscura/utils/storage/database.py +1166 -0
  398. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  399. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  400. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  401. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  402. oscura/{triggering → utils/triggering}/base.py +6 -6
  403. oscura/{triggering → utils/triggering}/edge.py +2 -2
  404. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  405. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  406. oscura/{triggering → utils/triggering}/window.py +2 -2
  407. oscura/utils/validation.py +32 -0
  408. oscura/validation/__init__.py +121 -0
  409. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  410. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  411. oscura/{compliance → validation/compliance}/masks.py +1 -1
  412. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  413. oscura/{compliance → validation/compliance}/testing.py +114 -52
  414. oscura/validation/compliance_tests.py +915 -0
  415. oscura/validation/fuzzer.py +990 -0
  416. oscura/validation/grammar_tests.py +596 -0
  417. oscura/validation/grammar_validator.py +904 -0
  418. oscura/validation/hil_testing.py +977 -0
  419. oscura/{quality → validation/quality}/__init__.py +4 -4
  420. oscura/{quality → validation/quality}/ensemble.py +251 -171
  421. oscura/{quality → validation/quality}/explainer.py +3 -3
  422. oscura/{quality → validation/quality}/scoring.py +1 -1
  423. oscura/{quality → validation/quality}/warnings.py +4 -4
  424. oscura/validation/regression_suite.py +808 -0
  425. oscura/validation/replay.py +788 -0
  426. oscura/{testing → validation/testing}/__init__.py +2 -2
  427. oscura/{testing → validation/testing}/synthetic.py +5 -5
  428. oscura/visualization/__init__.py +9 -0
  429. oscura/visualization/accessibility.py +1 -1
  430. oscura/visualization/annotations.py +64 -67
  431. oscura/visualization/colors.py +7 -7
  432. oscura/visualization/digital.py +180 -81
  433. oscura/visualization/eye.py +236 -85
  434. oscura/visualization/interactive.py +320 -143
  435. oscura/visualization/jitter.py +587 -247
  436. oscura/visualization/layout.py +169 -134
  437. oscura/visualization/optimization.py +103 -52
  438. oscura/visualization/palettes.py +1 -1
  439. oscura/visualization/power.py +427 -211
  440. oscura/visualization/power_extended.py +626 -297
  441. oscura/visualization/presets.py +2 -0
  442. oscura/visualization/protocols.py +495 -181
  443. oscura/visualization/render.py +79 -63
  444. oscura/visualization/reverse_engineering.py +171 -124
  445. oscura/visualization/signal_integrity.py +460 -279
  446. oscura/visualization/specialized.py +190 -100
  447. oscura/visualization/spectral.py +670 -255
  448. oscura/visualization/thumbnails.py +166 -137
  449. oscura/visualization/waveform.py +150 -63
  450. oscura/workflows/__init__.py +3 -0
  451. oscura/{batch → workflows/batch}/__init__.py +5 -5
  452. oscura/{batch → workflows/batch}/advanced.py +150 -75
  453. oscura/workflows/batch/aggregate.py +531 -0
  454. oscura/workflows/batch/analyze.py +236 -0
  455. oscura/{batch → workflows/batch}/logging.py +2 -2
  456. oscura/{batch → workflows/batch}/metrics.py +1 -1
  457. oscura/workflows/complete_re.py +1144 -0
  458. oscura/workflows/compliance.py +44 -54
  459. oscura/workflows/digital.py +197 -51
  460. oscura/workflows/legacy/__init__.py +12 -0
  461. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  462. oscura/workflows/multi_trace.py +9 -9
  463. oscura/workflows/power.py +42 -62
  464. oscura/workflows/protocol.py +82 -49
  465. oscura/workflows/reverse_engineering.py +351 -150
  466. oscura/workflows/signal_integrity.py +157 -82
  467. oscura-0.6.0.dist-info/METADATA +643 -0
  468. oscura-0.6.0.dist-info/RECORD +590 -0
  469. oscura/analyzers/digital/ic_database.py +0 -498
  470. oscura/analyzers/digital/timing_paths.py +0 -339
  471. oscura/analyzers/digital/vintage.py +0 -377
  472. oscura/analyzers/digital/vintage_result.py +0 -148
  473. oscura/analyzers/protocols/parallel_bus.py +0 -449
  474. oscura/batch/aggregate.py +0 -300
  475. oscura/batch/analyze.py +0 -139
  476. oscura/dsl/__init__.py +0 -73
  477. oscura/exceptions.py +0 -59
  478. oscura/exploratory/fuzzy.py +0 -513
  479. oscura/exploratory/sync.py +0 -384
  480. oscura/export/wavedrom.py +0 -430
  481. oscura/exporters/__init__.py +0 -94
  482. oscura/exporters/csv.py +0 -303
  483. oscura/exporters/exporters.py +0 -44
  484. oscura/exporters/hdf5.py +0 -217
  485. oscura/exporters/html_export.py +0 -701
  486. oscura/exporters/json_export.py +0 -338
  487. oscura/exporters/markdown_export.py +0 -367
  488. oscura/exporters/matlab_export.py +0 -354
  489. oscura/exporters/npz_export.py +0 -219
  490. oscura/exporters/spice_export.py +0 -210
  491. oscura/exporters/vintage_logic_csv.py +0 -247
  492. oscura/reporting/vintage_logic_report.py +0 -523
  493. oscura/search/context.py +0 -149
  494. oscura/session/__init__.py +0 -34
  495. oscura/session/annotations.py +0 -289
  496. oscura/session/history.py +0 -313
  497. oscura/session/session.py +0 -520
  498. oscura/visualization/digital_advanced.py +0 -718
  499. oscura/visualization/figure_manager.py +0 -156
  500. oscura/workflow/__init__.py +0 -13
  501. oscura-0.5.0.dist-info/METADATA +0 -407
  502. oscura-0.5.0.dist-info/RECORD +0 -486
  503. /oscura/core/{config.py → config/legacy.py} +0 -0
  504. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  505. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  506. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  507. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  508. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  509. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  510. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  511. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
  512. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
  513. {oscura-0.5.0.dist-info → oscura-0.6.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,
@@ -2,7 +2,7 @@
2
2
 
3
3
  Provides protocol decoders for common serial and automotive protocols including
4
4
  UART, SPI, I2C, CAN, LIN, FlexRay, JTAG, SWD, I2S, USB, HDLC, Manchester, CAN-FD,
5
- 1-Wire, and parallel bus protocols (GPIB, Centronics, ISA).
5
+ and 1-Wire.
6
6
  """
7
7
 
8
8
  from oscura.analyzers.protocols.base import (
@@ -60,18 +60,6 @@ from oscura.analyzers.protocols.onewire import (
60
60
  OneWireTimings,
61
61
  decode_onewire,
62
62
  )
63
-
64
- # Parallel bus protocols
65
- from oscura.analyzers.protocols.parallel_bus import (
66
- CentronicsFrame,
67
- GPIBFrame,
68
- GPIBMessageType,
69
- ISACycleType,
70
- ISATransaction,
71
- decode_centronics,
72
- decode_gpib,
73
- decode_isa_bus,
74
- )
75
63
  from oscura.analyzers.protocols.spi import SPIDecoder, decode_spi
76
64
  from oscura.analyzers.protocols.swd import SWDDecoder, SWDResponse, decode_swd
77
65
  from oscura.analyzers.protocols.uart import UARTDecoder, decode_uart
@@ -103,16 +91,12 @@ __all__ = [
103
91
  "CANFDFrameType",
104
92
  "CANFrame",
105
93
  "CANFrameType",
106
- # Parallel bus protocols
107
- "CentronicsFrame",
108
94
  "ChannelDef",
109
95
  "DecoderState",
110
96
  # FlexRay (PRO-016)
111
97
  "FlexRayDecoder",
112
98
  "FlexRayFrame",
113
99
  "FlexRaySegment",
114
- "GPIBFrame",
115
- "GPIBMessageType",
116
100
  # HDLC (PRO-013)
117
101
  "HDLCDecoder",
118
102
  # I2C (PRO-004)
@@ -120,8 +104,6 @@ __all__ = [
120
104
  # I2S (PRO-011)
121
105
  "I2SDecoder",
122
106
  "I2SMode",
123
- "ISACycleType",
124
- "ISATransaction",
125
107
  # JTAG (PRO-009)
126
108
  "JTAGDecoder",
127
109
  # LIN (PRO-008)
@@ -153,13 +135,10 @@ __all__ = [
153
135
  "USBSpeed",
154
136
  "decode_can",
155
137
  "decode_can_fd",
156
- "decode_centronics",
157
138
  "decode_flexray",
158
- "decode_gpib",
159
139
  "decode_hdlc",
160
140
  "decode_i2c",
161
141
  "decode_i2s",
162
- "decode_isa_bus",
163
142
  "decode_jtag",
164
143
  "decode_lin",
165
144
  "decode_manchester",
@@ -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
+ ]