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
oscura/guidance/wizard.py CHANGED
@@ -181,40 +181,22 @@ class AnalysisWizard:
181
181
  """
182
182
  self._predefined_answers = answers
183
183
 
184
- def run(
185
- self,
186
- *,
187
- preview_callback: Callable[[Any], None] | None = None,
188
- ) -> WizardResult:
189
- """Run the analysis wizard.
190
-
191
- Guides user through analysis steps, auto-skipping where confident,
192
- showing progress, and providing live previews.
184
+ def _execute_characterization_step(self, preview_callback: Callable[[Any], None] | None) -> Any:
185
+ """Execute signal characterization step.
193
186
 
194
187
  Args:
195
- preview_callback: Optional callback for step previews.
188
+ preview_callback: Optional callback for step preview.
196
189
 
197
190
  Returns:
198
- WizardResult with analysis summary and findings.
191
+ Characterization result object with signal_type and confidence.
199
192
 
200
193
  Example:
201
- >>> wizard = AnalysisWizard(trace)
202
- >>> result = wizard.run()
203
- >>> print(result.summary)
204
-
205
- References:
206
- DISC-006: Interactive Analysis Wizard
194
+ >>> result = wizard._execute_characterization_step(None)
195
+ >>> print(result.signal_type)
207
196
  """
208
- from oscura.discovery import (
209
- assess_data_quality,
210
- characterize_signal,
211
- decode_protocol,
212
- find_anomalies,
213
- )
214
- from oscura.guidance import suggest_next_steps
197
+ from oscura.discovery import characterize_signal
215
198
 
216
- # Step 1: Auto-characterization
217
- step1 = WizardStep(
199
+ step = WizardStep(
218
200
  number=1,
219
201
  id="characterization",
220
202
  question="What type of signal are you analyzing?",
@@ -228,31 +210,46 @@ class AnalysisWizard:
228
210
  skip_if_confident=False,
229
211
  )
230
212
 
231
- # Always do auto-characterization first
232
213
  char_result = characterize_signal(self.trace)
233
- step1.confidence_before = 0.0
234
- step1.confidence_after = char_result.confidence
214
+ step.confidence_before = 0.0
215
+ step.confidence_after = char_result.confidence
235
216
  self._current_state["characterization"] = char_result
236
217
 
237
218
  if self.interactive and char_result.confidence < self.auto_detect_threshold:
238
- # Ask user to confirm
239
- step1.user_response = self._predefined_answers.get("signal_type", step1.default)
219
+ step.user_response = self._predefined_answers.get("signal_type", step.default)
240
220
  self.questions_asked += 1
241
221
  else:
242
- # Auto-detected with high confidence
243
222
  signal_type = getattr(char_result, "signal_type", "Unknown")
244
- step1.user_response = f"Auto-detected: {signal_type}"
223
+ step.user_response = f"Auto-detected: {signal_type}"
245
224
  self.questions_skipped += 1
246
225
 
247
- self.step_history.append(step1)
226
+ self.step_history.append(step)
248
227
  self.steps_completed += 1
249
228
 
250
- # Preview callback
251
229
  if preview_callback and self.enable_preview:
252
230
  preview_callback(char_result)
253
231
 
254
- # Step 2: Quality assessment
255
- step2 = WizardStep(
232
+ return char_result
233
+
234
+ def _execute_quality_assessment_step(
235
+ self, char_result: Any, preview_callback: Callable[[Any], None] | None
236
+ ) -> Any:
237
+ """Execute data quality assessment step.
238
+
239
+ Args:
240
+ char_result: Result from characterization step.
241
+ preview_callback: Optional callback for step preview.
242
+
243
+ Returns:
244
+ Quality assessment result with status and confidence.
245
+
246
+ Example:
247
+ >>> quality = wizard._execute_quality_assessment_step(char_result, None)
248
+ >>> print(quality.status)
249
+ """
250
+ from oscura.discovery import assess_data_quality
251
+
252
+ step = WizardStep(
256
253
  number=2,
257
254
  id="quality",
258
255
  question="Check data quality?",
@@ -262,98 +259,159 @@ class AnalysisWizard:
262
259
  )
263
260
 
264
261
  quality = assess_data_quality(self.trace)
265
- step2.confidence_before = char_result.confidence
266
- step2.confidence_after = quality.confidence
262
+ step.confidence_before = char_result.confidence
263
+ step.confidence_after = quality.confidence
267
264
  self._current_state["quality"] = quality
268
265
 
269
266
  if self.interactive and self.questions_asked < self.max_questions:
270
- step2.user_response = self._predefined_answers.get("check_quality", "Yes")
271
- if step2.user_response == "Yes":
267
+ step.user_response = self._predefined_answers.get("check_quality", "Yes")
268
+ if step.user_response == "Yes":
272
269
  self.questions_asked += 1
273
270
  else:
274
- step2.user_response = "Skipped (auto-assessed)"
271
+ step.user_response = "Skipped (auto-assessed)"
275
272
  self.questions_skipped += 1
276
273
 
277
- self.step_history.append(step2)
274
+ self.step_history.append(step)
278
275
  self.steps_completed += 1
279
276
 
280
277
  if preview_callback and self.enable_preview:
281
278
  preview_callback(quality)
282
279
 
283
- # Step 3: Protocol decode (if applicable)
280
+ return quality
281
+
282
+ def _execute_protocol_decode_step(
283
+ self, char_result: Any, preview_callback: Callable[[Any], None] | None
284
+ ) -> Any | None:
285
+ """Execute protocol decoding step if applicable.
286
+
287
+ Args:
288
+ char_result: Result from characterization step.
289
+ preview_callback: Optional callback for step preview.
290
+
291
+ Returns:
292
+ Decode result if protocol detected, None otherwise.
293
+
294
+ Example:
295
+ >>> decode = wizard._execute_protocol_decode_step(char_result, None)
296
+ >>> if decode:
297
+ ... print(len(decode.data))
298
+ """
299
+ from oscura.discovery import decode_protocol
300
+
284
301
  decode_result = None
285
- if hasattr(char_result, "signal_type") and char_result.confidence >= 0.7:
286
- signal_type = char_result.signal_type.lower()
287
-
288
- if any(proto in signal_type for proto in ["uart", "spi", "i2c", "can"]):
289
- step3 = WizardStep(
290
- number=3,
291
- id="decode",
292
- question=f"Auto-detected {char_result.signal_type}. Decode data?",
293
- options=["Yes", "No"],
294
- default="Yes",
295
- skip_if_confident=True,
296
- )
297
-
298
- if self.interactive and self.questions_asked < self.max_questions:
299
- step3.user_response = self._predefined_answers.get("decode_data", "Yes")
300
- if step3.user_response == "Yes":
301
- decode_result = decode_protocol(self.trace)
302
- self._current_state["decode"] = decode_result
303
- self.questions_asked += 1
304
- else:
305
- # Auto-decode
306
- decode_result = decode_protocol(self.trace)
307
- self._current_state["decode"] = decode_result
308
- step3.user_response = "Auto-decoded"
309
- self.questions_skipped += 1
310
-
311
- step3.confidence_before = char_result.confidence
312
- step3.confidence_after = decode_result.overall_confidence if decode_result else 0.0
313
-
314
- self.step_history.append(step3)
315
- self.steps_completed += 1
316
-
317
- if preview_callback and self.enable_preview and decode_result:
318
- preview_callback(decode_result)
319
-
320
- # Step 4: Anomaly detection (if quality issues)
302
+
303
+ if not (hasattr(char_result, "signal_type") and char_result.confidence >= 0.7):
304
+ return decode_result
305
+
306
+ signal_type = char_result.signal_type.lower()
307
+ if not any(proto in signal_type for proto in ["uart", "spi", "i2c", "can"]):
308
+ return decode_result
309
+
310
+ step = WizardStep(
311
+ number=3,
312
+ id="decode",
313
+ question=f"Auto-detected {char_result.signal_type}. Decode data?",
314
+ options=["Yes", "No"],
315
+ default="Yes",
316
+ skip_if_confident=True,
317
+ )
318
+
319
+ if self.interactive and self.questions_asked < self.max_questions:
320
+ step.user_response = self._predefined_answers.get("decode_data", "Yes")
321
+ if step.user_response == "Yes":
322
+ decode_result = decode_protocol(self.trace)
323
+ self._current_state["decode"] = decode_result
324
+ self.questions_asked += 1
325
+ else:
326
+ decode_result = decode_protocol(self.trace)
327
+ self._current_state["decode"] = decode_result
328
+ step.user_response = "Auto-decoded"
329
+ self.questions_skipped += 1
330
+
331
+ step.confidence_before = char_result.confidence
332
+ step.confidence_after = decode_result.overall_confidence if decode_result else 0.0
333
+
334
+ self.step_history.append(step)
335
+ self.steps_completed += 1
336
+
337
+ if preview_callback and self.enable_preview and decode_result:
338
+ preview_callback(decode_result)
339
+
340
+ return decode_result
341
+
342
+ def _execute_anomaly_detection_step(
343
+ self, quality: Any, preview_callback: Callable[[Any], None] | None
344
+ ) -> Any | None:
345
+ """Execute anomaly detection step if quality issues exist.
346
+
347
+ Args:
348
+ quality: Result from quality assessment step.
349
+ preview_callback: Optional callback for step preview.
350
+
351
+ Returns:
352
+ Anomaly detection results if quality issues found, None otherwise.
353
+
354
+ Example:
355
+ >>> anomalies = wizard._execute_anomaly_detection_step(quality, None)
356
+ >>> if anomalies:
357
+ ... print(len(anomalies))
358
+ """
359
+ from oscura.discovery import find_anomalies
360
+
321
361
  anomalies = None
322
- if quality.status in ["WARNING", "FAIL"]:
323
- step4 = WizardStep(
324
- number=self.steps_completed + 1,
325
- id="anomalies",
326
- question="Quality concerns detected. Check for anomalies?",
327
- options=["Yes", "No"],
328
- default="Yes",
329
- )
330
-
331
- if self.interactive and self.questions_asked < self.max_questions:
332
- step4.user_response = self._predefined_answers.get("check_anomalies", "Yes")
333
- if step4.user_response == "Yes":
334
- anomalies = find_anomalies(self.trace)
335
- self._current_state["anomalies"] = anomalies
336
- self.questions_asked += 1
337
- else:
338
- # Auto-check
362
+
363
+ if quality.status not in ["WARNING", "FAIL"]:
364
+ return anomalies
365
+
366
+ step = WizardStep(
367
+ number=self.steps_completed + 1,
368
+ id="anomalies",
369
+ question="Quality concerns detected. Check for anomalies?",
370
+ options=["Yes", "No"],
371
+ default="Yes",
372
+ )
373
+
374
+ if self.interactive and self.questions_asked < self.max_questions:
375
+ step.user_response = self._predefined_answers.get("check_anomalies", "Yes")
376
+ if step.user_response == "Yes":
339
377
  anomalies = find_anomalies(self.trace)
340
378
  self._current_state["anomalies"] = anomalies
341
- step4.user_response = "Auto-checked"
342
- self.questions_skipped += 1
379
+ self.questions_asked += 1
380
+ else:
381
+ anomalies = find_anomalies(self.trace)
382
+ self._current_state["anomalies"] = anomalies
383
+ step.user_response = "Auto-checked"
384
+ self.questions_skipped += 1
343
385
 
344
- self.step_history.append(step4)
345
- self.steps_completed += 1
386
+ self.step_history.append(step)
387
+ self.steps_completed += 1
346
388
 
347
- if preview_callback and self.enable_preview and anomalies:
348
- preview_callback(anomalies)
389
+ if preview_callback and self.enable_preview and anomalies:
390
+ preview_callback(anomalies)
349
391
 
350
- # Get recommendations for next steps
351
- recommendations = suggest_next_steps(
352
- self.trace,
353
- current_state=self._current_state,
354
- )
392
+ return anomalies
393
+
394
+ def _build_summary(
395
+ self, char_result: Any, quality: Any, decode_result: Any | None, anomalies: Any | None
396
+ ) -> str:
397
+ """Build wizard result summary from analysis steps.
398
+
399
+ Args:
400
+ char_result: Characterization result.
401
+ quality: Quality assessment result.
402
+ decode_result: Protocol decode result (optional).
403
+ anomalies: Anomaly detection results (optional).
355
404
 
356
- # Build summary
405
+ Returns:
406
+ Multi-line summary string.
407
+
408
+ Example:
409
+ >>> summary = wizard._build_summary(char, quality, decode, anomalies)
410
+ >>> print(summary)
411
+ Signal type: UART
412
+ Quality: Good
413
+ Decoded: 1024 bytes
414
+ """
357
415
  summary_parts = []
358
416
 
359
417
  if hasattr(char_result, "signal_type") and char_result.signal_type:
@@ -377,9 +435,47 @@ class AnalysisWizard:
377
435
  if critical > 0:
378
436
  summary_parts.append(f"Anomalies: {critical} critical issues")
379
437
 
380
- summary = "\n".join(summary_parts)
438
+ return "\n".join(summary_parts)
439
+
440
+ def run(
441
+ self,
442
+ *,
443
+ preview_callback: Callable[[Any], None] | None = None,
444
+ ) -> WizardResult:
445
+ """Run the analysis wizard.
446
+
447
+ Guides user through analysis steps, auto-skipping where confident,
448
+ showing progress, and providing live previews.
449
+
450
+ Args:
451
+ preview_callback: Optional callback for step previews.
452
+
453
+ Returns:
454
+ WizardResult with analysis summary and findings.
455
+
456
+ Example:
457
+ >>> wizard = AnalysisWizard(trace)
458
+ >>> result = wizard.run()
459
+ >>> print(result.summary)
460
+
461
+ References:
462
+ DISC-006: Interactive Analysis Wizard
463
+ """
464
+ from oscura.guidance import suggest_next_steps
465
+
466
+ # Execute analysis steps
467
+ char_result = self._execute_characterization_step(preview_callback)
468
+ quality = self._execute_quality_assessment_step(char_result, preview_callback)
469
+ decode_result = self._execute_protocol_decode_step(char_result, preview_callback)
470
+ anomalies = self._execute_anomaly_detection_step(quality, preview_callback)
471
+
472
+ # Generate recommendations and results
473
+ recommendations = suggest_next_steps(
474
+ self.trace,
475
+ current_state=self._current_state,
476
+ )
381
477
 
382
- # Session duration
478
+ summary = self._build_summary(char_result, quality, decode_result, anomalies)
383
479
  self.session_duration_seconds = (datetime.now() - self._start_time).total_seconds()
384
480
 
385
481
  return WizardResult(
@@ -0,0 +1,19 @@
1
+ """Hardware abstraction layer analysis.
2
+
3
+ This module provides tools for analyzing hardware abstraction layers,
4
+ register access patterns, and peripheral drivers in firmware binaries.
5
+ """
6
+
7
+ from oscura.hardware.hal_detector import (
8
+ HALAnalysisResult,
9
+ HALDetector,
10
+ Peripheral,
11
+ RegisterAccess,
12
+ )
13
+
14
+ __all__ = [
15
+ "HALAnalysisResult",
16
+ "HALDetector",
17
+ "Peripheral",
18
+ "RegisterAccess",
19
+ ]
@@ -11,7 +11,7 @@ The Source protocol enables polymorphic data acquisition:
11
11
  All sources implement the same interface, making them interchangeable:
12
12
 
13
13
  Example:
14
- >>> from oscura.acquisition import FileSource, HardwareSource, SyntheticSource
14
+ >>> from oscura.hardware.acquisition import FileSource, HardwareSource, SyntheticSource
15
15
  >>> from oscura import SignalBuilder
16
16
  >>>
17
17
  >>> # All sources use the same interface
@@ -135,9 +135,9 @@ class Source(Protocol):
135
135
 
136
136
 
137
137
  # Import concrete implementations
138
- from oscura.acquisition.file import FileSource
139
- from oscura.acquisition.hardware import HardwareSource
140
- from oscura.acquisition.synthetic import SyntheticSource
138
+ from oscura.hardware.acquisition.file import FileSource
139
+ from oscura.hardware.acquisition.hardware import HardwareSource
140
+ from oscura.hardware.acquisition.synthetic import SyntheticSource
141
141
 
142
142
  __all__ = [
143
143
  "FileSource",
@@ -5,7 +5,7 @@ the unified Source protocol. FileSource makes file loading consistent with all
5
5
  other acquisition methods (hardware, synthetic).
6
6
 
7
7
  Example:
8
- >>> from oscura.acquisition import FileSource
8
+ >>> from oscura.hardware.acquisition import FileSource
9
9
  >>>
10
10
  >>> # Basic usage
11
11
  >>> source = FileSource("capture.wfm")
@@ -153,7 +153,7 @@ class FileSource:
153
153
 
154
154
  # Try lazy/chunked loading if available
155
155
  try:
156
- from oscura.streaming import load_trace_chunks
156
+ from oscura.utils.streaming import load_trace_chunks
157
157
 
158
158
  # load_trace_chunks expects Path-like and chunk_size
159
159
  yield from load_trace_chunks(self.path, chunk_size=chunk_size)
@@ -11,7 +11,7 @@ Supported Hardware:
11
11
  - PyVISA: Oscilloscopes and instruments (requires pyvisa)
12
12
 
13
13
  Example:
14
- >>> from oscura.acquisition import HardwareSource
14
+ >>> from oscura.hardware.acquisition import HardwareSource
15
15
  >>>
16
16
  >>> # SocketCAN source
17
17
  >>> with HardwareSource.socketcan("can0", bitrate=500000) as source:
@@ -48,9 +48,9 @@ from __future__ import annotations
48
48
  from typing import TYPE_CHECKING, Any
49
49
 
50
50
  if TYPE_CHECKING:
51
- from oscura.acquisition.saleae import SaleaeSource
52
- from oscura.acquisition.socketcan import SocketCANSource
53
- from oscura.acquisition.visa import VISASource
51
+ from oscura.hardware.acquisition.saleae import SaleaeSource
52
+ from oscura.hardware.acquisition.socketcan import SocketCANSource
53
+ from oscura.hardware.acquisition.visa import VISASource
54
54
 
55
55
 
56
56
  class HardwareSource:
@@ -107,7 +107,7 @@ class HardwareSource:
107
107
  Requires python-can library: pip install oscura[automotive]
108
108
  Linux only - uses SocketCAN kernel module.
109
109
  """
110
- from oscura.acquisition.socketcan import SocketCANSource
110
+ from oscura.hardware.acquisition.socketcan import SocketCANSource
111
111
 
112
112
  return SocketCANSource(interface=interface, bitrate=bitrate, **kwargs)
113
113
 
@@ -142,7 +142,7 @@ class HardwareSource:
142
142
  Requires saleae library: pip install saleae
143
143
  Supports Logic 8, Logic Pro 8, Logic Pro 16.
144
144
  """
145
- from oscura.acquisition.saleae import SaleaeSource
145
+ from oscura.hardware.acquisition.saleae import SaleaeSource
146
146
 
147
147
  return SaleaeSource(device_id=device_id, **kwargs)
148
148
 
@@ -178,7 +178,7 @@ class HardwareSource:
178
178
  Requires pyvisa and pyvisa-py: pip install pyvisa pyvisa-py
179
179
  Supports Tektronix, Keysight, Rigol, and other SCPI oscilloscopes.
180
180
  """
181
- from oscura.acquisition.visa import VISASource
181
+ from oscura.hardware.acquisition.visa import VISASource
182
182
 
183
183
  return VISASource(resource=resource, **kwargs)
184
184
 
@@ -8,7 +8,7 @@ directly from the hardware, returning WaveformTrace (analog) or DigitalTrace
8
8
  (digital) depending on channel configuration.
9
9
 
10
10
  Example:
11
- >>> from oscura.acquisition import HardwareSource
11
+ >>> from oscura.hardware.acquisition import HardwareSource
12
12
  >>>
13
13
  >>> # Basic digital acquisition
14
14
  >>> with HardwareSource.saleae() as source:
@@ -41,10 +41,17 @@ References:
41
41
 
42
42
  from __future__ import annotations
43
43
 
44
+ import time
44
45
  from collections.abc import Iterator
45
46
  from datetime import datetime
46
47
  from typing import TYPE_CHECKING, Any
47
48
 
49
+ # Optional dependency - import at module level for mocking in tests
50
+ try:
51
+ import saleae
52
+ except ImportError:
53
+ saleae = None # type: ignore[assignment]
54
+
48
55
  if TYPE_CHECKING:
49
56
  from oscura.core.types import Trace
50
57
 
@@ -88,7 +95,7 @@ class SaleaeSource:
88
95
  """
89
96
  self.device_id = device_id
90
97
  self.kwargs = kwargs
91
- self.saleae = None
98
+ self.saleae: Any = None # Saleae connection object (dynamic import)
92
99
  self._closed = False
93
100
 
94
101
  # Configuration
@@ -107,12 +114,10 @@ class SaleaeSource:
107
114
  if self.saleae is not None:
108
115
  return
109
116
 
110
- try:
111
- import saleae
112
- except ImportError as e:
117
+ if saleae is None:
113
118
  raise ImportError(
114
119
  "Saleae source requires saleae library. Install with: pip install saleae"
115
- ) from e
120
+ )
116
121
 
117
122
  try:
118
123
  self.saleae = saleae.Saleae()
@@ -164,12 +169,12 @@ class SaleaeSource:
164
169
  self._ensure_connection()
165
170
 
166
171
  # Set sample rate
167
- self.saleae.set_sample_rate_by_minimum(sample_rate) # type: ignore[union-attr]
172
+ self.saleae.set_sample_rate_by_minimum(sample_rate)
168
173
 
169
174
  # Enable/disable channels
170
175
  for ch in range(16): # Max 16 digital channels
171
176
  if ch in self.digital_channels:
172
- self.saleae.set_capture_pretrigger_buffer_size( # type: ignore[union-attr]
177
+ self.saleae.set_capture_pretrigger_buffer_size(
173
178
  int(sample_rate * duration), is_set=True
174
179
  )
175
180
 
@@ -204,15 +209,13 @@ class SaleaeSource:
204
209
  acquisition_start = datetime.now()
205
210
 
206
211
  # Start capture
207
- self.saleae.capture_start() # type: ignore[union-attr]
212
+ self.saleae.capture_start()
208
213
 
209
214
  # Wait for capture to complete
210
- import time
211
-
212
215
  time.sleep(self.duration)
213
216
 
214
217
  # Stop capture
215
- self.saleae.capture_stop() # type: ignore[union-attr]
218
+ self.saleae.capture_stop()
216
219
 
217
220
  # Export data
218
221
  # Note: Actual Saleae API would save to file, then we'd load it.
@@ -9,7 +9,7 @@ CAN ID represented as a separate digital channel. This enables protocol analysis
9
9
  and reverse engineering of CAN bus communications.
10
10
 
11
11
  Example:
12
- >>> from oscura.acquisition import HardwareSource
12
+ >>> from oscura.hardware.acquisition import HardwareSource
13
13
  >>>
14
14
  >>> # Basic usage
15
15
  >>> with HardwareSource.socketcan("can0", bitrate=500000) as source:
@@ -8,8 +8,8 @@ Planned features:
8
8
  - Buffering and backpressure management
9
9
 
10
10
  Example (Future):
11
- >>> from oscura.acquisition import HardwareSource
12
- >>> from oscura.acquisition.streaming import LiveProcessor
11
+ >>> from oscura.hardware.acquisition import HardwareSource
12
+ >>> from oscura.hardware.acquisition.streaming import LiveProcessor
13
13
  >>>
14
14
  >>> # Real-time streaming from hardware
15
15
  >>> processor = LiveProcessor(
@@ -5,7 +5,7 @@ the unified Source protocol. SyntheticSource makes synthetic signals consistent
5
5
  with all other acquisition methods (files, hardware).
6
6
 
7
7
  Example:
8
- >>> from oscura.acquisition import SyntheticSource
8
+ >>> from oscura.hardware.acquisition import SyntheticSource
9
9
  >>> from oscura import SignalBuilder
10
10
  >>>
11
11
  >>> # Create signal builder
@@ -41,8 +41,8 @@ from collections.abc import Iterator
41
41
  from typing import TYPE_CHECKING
42
42
 
43
43
  if TYPE_CHECKING:
44
- from oscura.builders.signal_builder import SignalBuilder
45
44
  from oscura.core.types import Trace, WaveformTrace
45
+ from oscura.utils.builders.signal_builder import SignalBuilder
46
46
 
47
47
 
48
48
  class SyntheticSource:
@@ -57,7 +57,7 @@ class SyntheticSource:
57
57
 
58
58
  Example:
59
59
  >>> from oscura import SignalBuilder
60
- >>> from oscura.acquisition import SyntheticSource
60
+ >>> from oscura.hardware.acquisition import SyntheticSource
61
61
  >>>
62
62
  >>> # Create builder
63
63
  >>> builder = (SignalBuilder(sample_rate=1e6, duration=0.01)