oscura 0.0.1__py3-none-any.whl → 0.1.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 (465) hide show
  1. oscura/__init__.py +813 -8
  2. oscura/__main__.py +392 -0
  3. oscura/analyzers/__init__.py +37 -0
  4. oscura/analyzers/digital/__init__.py +177 -0
  5. oscura/analyzers/digital/bus.py +691 -0
  6. oscura/analyzers/digital/clock.py +805 -0
  7. oscura/analyzers/digital/correlation.py +720 -0
  8. oscura/analyzers/digital/edges.py +632 -0
  9. oscura/analyzers/digital/extraction.py +413 -0
  10. oscura/analyzers/digital/quality.py +878 -0
  11. oscura/analyzers/digital/signal_quality.py +877 -0
  12. oscura/analyzers/digital/thresholds.py +708 -0
  13. oscura/analyzers/digital/timing.py +1104 -0
  14. oscura/analyzers/eye/__init__.py +46 -0
  15. oscura/analyzers/eye/diagram.py +434 -0
  16. oscura/analyzers/eye/metrics.py +555 -0
  17. oscura/analyzers/jitter/__init__.py +83 -0
  18. oscura/analyzers/jitter/ber.py +333 -0
  19. oscura/analyzers/jitter/decomposition.py +759 -0
  20. oscura/analyzers/jitter/measurements.py +413 -0
  21. oscura/analyzers/jitter/spectrum.py +220 -0
  22. oscura/analyzers/measurements.py +40 -0
  23. oscura/analyzers/packet/__init__.py +171 -0
  24. oscura/analyzers/packet/daq.py +1077 -0
  25. oscura/analyzers/packet/metrics.py +437 -0
  26. oscura/analyzers/packet/parser.py +327 -0
  27. oscura/analyzers/packet/payload.py +2156 -0
  28. oscura/analyzers/packet/payload_analysis.py +1312 -0
  29. oscura/analyzers/packet/payload_extraction.py +236 -0
  30. oscura/analyzers/packet/payload_patterns.py +670 -0
  31. oscura/analyzers/packet/stream.py +359 -0
  32. oscura/analyzers/patterns/__init__.py +266 -0
  33. oscura/analyzers/patterns/clustering.py +1036 -0
  34. oscura/analyzers/patterns/discovery.py +539 -0
  35. oscura/analyzers/patterns/learning.py +797 -0
  36. oscura/analyzers/patterns/matching.py +1091 -0
  37. oscura/analyzers/patterns/periodic.py +650 -0
  38. oscura/analyzers/patterns/sequences.py +767 -0
  39. oscura/analyzers/power/__init__.py +116 -0
  40. oscura/analyzers/power/ac_power.py +391 -0
  41. oscura/analyzers/power/basic.py +383 -0
  42. oscura/analyzers/power/conduction.py +314 -0
  43. oscura/analyzers/power/efficiency.py +297 -0
  44. oscura/analyzers/power/ripple.py +356 -0
  45. oscura/analyzers/power/soa.py +372 -0
  46. oscura/analyzers/power/switching.py +479 -0
  47. oscura/analyzers/protocol/__init__.py +150 -0
  48. oscura/analyzers/protocols/__init__.py +150 -0
  49. oscura/analyzers/protocols/base.py +500 -0
  50. oscura/analyzers/protocols/can.py +620 -0
  51. oscura/analyzers/protocols/can_fd.py +448 -0
  52. oscura/analyzers/protocols/flexray.py +405 -0
  53. oscura/analyzers/protocols/hdlc.py +399 -0
  54. oscura/analyzers/protocols/i2c.py +368 -0
  55. oscura/analyzers/protocols/i2s.py +296 -0
  56. oscura/analyzers/protocols/jtag.py +393 -0
  57. oscura/analyzers/protocols/lin.py +445 -0
  58. oscura/analyzers/protocols/manchester.py +333 -0
  59. oscura/analyzers/protocols/onewire.py +501 -0
  60. oscura/analyzers/protocols/spi.py +334 -0
  61. oscura/analyzers/protocols/swd.py +325 -0
  62. oscura/analyzers/protocols/uart.py +393 -0
  63. oscura/analyzers/protocols/usb.py +495 -0
  64. oscura/analyzers/signal_integrity/__init__.py +63 -0
  65. oscura/analyzers/signal_integrity/embedding.py +294 -0
  66. oscura/analyzers/signal_integrity/equalization.py +370 -0
  67. oscura/analyzers/signal_integrity/sparams.py +484 -0
  68. oscura/analyzers/spectral/__init__.py +53 -0
  69. oscura/analyzers/spectral/chunked.py +273 -0
  70. oscura/analyzers/spectral/chunked_fft.py +571 -0
  71. oscura/analyzers/spectral/chunked_wavelet.py +391 -0
  72. oscura/analyzers/spectral/fft.py +92 -0
  73. oscura/analyzers/statistical/__init__.py +250 -0
  74. oscura/analyzers/statistical/checksum.py +923 -0
  75. oscura/analyzers/statistical/chunked_corr.py +228 -0
  76. oscura/analyzers/statistical/classification.py +778 -0
  77. oscura/analyzers/statistical/entropy.py +1113 -0
  78. oscura/analyzers/statistical/ngrams.py +614 -0
  79. oscura/analyzers/statistics/__init__.py +119 -0
  80. oscura/analyzers/statistics/advanced.py +885 -0
  81. oscura/analyzers/statistics/basic.py +263 -0
  82. oscura/analyzers/statistics/correlation.py +630 -0
  83. oscura/analyzers/statistics/distribution.py +298 -0
  84. oscura/analyzers/statistics/outliers.py +463 -0
  85. oscura/analyzers/statistics/streaming.py +93 -0
  86. oscura/analyzers/statistics/trend.py +520 -0
  87. oscura/analyzers/validation.py +598 -0
  88. oscura/analyzers/waveform/__init__.py +36 -0
  89. oscura/analyzers/waveform/measurements.py +943 -0
  90. oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
  91. oscura/analyzers/waveform/spectral.py +1689 -0
  92. oscura/analyzers/waveform/wavelets.py +298 -0
  93. oscura/api/__init__.py +62 -0
  94. oscura/api/dsl.py +538 -0
  95. oscura/api/fluent.py +571 -0
  96. oscura/api/operators.py +498 -0
  97. oscura/api/optimization.py +392 -0
  98. oscura/api/profiling.py +396 -0
  99. oscura/automotive/__init__.py +73 -0
  100. oscura/automotive/can/__init__.py +52 -0
  101. oscura/automotive/can/analysis.py +356 -0
  102. oscura/automotive/can/checksum.py +250 -0
  103. oscura/automotive/can/correlation.py +212 -0
  104. oscura/automotive/can/discovery.py +355 -0
  105. oscura/automotive/can/message_wrapper.py +375 -0
  106. oscura/automotive/can/models.py +385 -0
  107. oscura/automotive/can/patterns.py +381 -0
  108. oscura/automotive/can/session.py +452 -0
  109. oscura/automotive/can/state_machine.py +300 -0
  110. oscura/automotive/can/stimulus_response.py +461 -0
  111. oscura/automotive/dbc/__init__.py +15 -0
  112. oscura/automotive/dbc/generator.py +156 -0
  113. oscura/automotive/dbc/parser.py +146 -0
  114. oscura/automotive/dtc/__init__.py +30 -0
  115. oscura/automotive/dtc/database.py +3036 -0
  116. oscura/automotive/j1939/__init__.py +14 -0
  117. oscura/automotive/j1939/decoder.py +745 -0
  118. oscura/automotive/loaders/__init__.py +35 -0
  119. oscura/automotive/loaders/asc.py +98 -0
  120. oscura/automotive/loaders/blf.py +77 -0
  121. oscura/automotive/loaders/csv_can.py +136 -0
  122. oscura/automotive/loaders/dispatcher.py +136 -0
  123. oscura/automotive/loaders/mdf.py +331 -0
  124. oscura/automotive/loaders/pcap.py +132 -0
  125. oscura/automotive/obd/__init__.py +14 -0
  126. oscura/automotive/obd/decoder.py +707 -0
  127. oscura/automotive/uds/__init__.py +48 -0
  128. oscura/automotive/uds/decoder.py +265 -0
  129. oscura/automotive/uds/models.py +64 -0
  130. oscura/automotive/visualization.py +369 -0
  131. oscura/batch/__init__.py +55 -0
  132. oscura/batch/advanced.py +627 -0
  133. oscura/batch/aggregate.py +300 -0
  134. oscura/batch/analyze.py +139 -0
  135. oscura/batch/logging.py +487 -0
  136. oscura/batch/metrics.py +556 -0
  137. oscura/builders/__init__.py +41 -0
  138. oscura/builders/signal_builder.py +1131 -0
  139. oscura/cli/__init__.py +14 -0
  140. oscura/cli/batch.py +339 -0
  141. oscura/cli/characterize.py +273 -0
  142. oscura/cli/compare.py +775 -0
  143. oscura/cli/decode.py +551 -0
  144. oscura/cli/main.py +247 -0
  145. oscura/cli/shell.py +350 -0
  146. oscura/comparison/__init__.py +66 -0
  147. oscura/comparison/compare.py +397 -0
  148. oscura/comparison/golden.py +487 -0
  149. oscura/comparison/limits.py +391 -0
  150. oscura/comparison/mask.py +434 -0
  151. oscura/comparison/trace_diff.py +30 -0
  152. oscura/comparison/visualization.py +481 -0
  153. oscura/compliance/__init__.py +70 -0
  154. oscura/compliance/advanced.py +756 -0
  155. oscura/compliance/masks.py +363 -0
  156. oscura/compliance/reporting.py +483 -0
  157. oscura/compliance/testing.py +298 -0
  158. oscura/component/__init__.py +38 -0
  159. oscura/component/impedance.py +365 -0
  160. oscura/component/reactive.py +598 -0
  161. oscura/component/transmission_line.py +312 -0
  162. oscura/config/__init__.py +191 -0
  163. oscura/config/defaults.py +254 -0
  164. oscura/config/loader.py +348 -0
  165. oscura/config/memory.py +271 -0
  166. oscura/config/migration.py +458 -0
  167. oscura/config/pipeline.py +1077 -0
  168. oscura/config/preferences.py +530 -0
  169. oscura/config/protocol.py +875 -0
  170. oscura/config/schema.py +713 -0
  171. oscura/config/settings.py +420 -0
  172. oscura/config/thresholds.py +599 -0
  173. oscura/convenience.py +457 -0
  174. oscura/core/__init__.py +299 -0
  175. oscura/core/audit.py +457 -0
  176. oscura/core/backend_selector.py +405 -0
  177. oscura/core/cache.py +590 -0
  178. oscura/core/cancellation.py +439 -0
  179. oscura/core/confidence.py +225 -0
  180. oscura/core/config.py +506 -0
  181. oscura/core/correlation.py +216 -0
  182. oscura/core/cross_domain.py +422 -0
  183. oscura/core/debug.py +301 -0
  184. oscura/core/edge_cases.py +541 -0
  185. oscura/core/exceptions.py +535 -0
  186. oscura/core/gpu_backend.py +523 -0
  187. oscura/core/lazy.py +832 -0
  188. oscura/core/log_query.py +540 -0
  189. oscura/core/logging.py +931 -0
  190. oscura/core/logging_advanced.py +952 -0
  191. oscura/core/memoize.py +171 -0
  192. oscura/core/memory_check.py +274 -0
  193. oscura/core/memory_guard.py +290 -0
  194. oscura/core/memory_limits.py +336 -0
  195. oscura/core/memory_monitor.py +453 -0
  196. oscura/core/memory_progress.py +465 -0
  197. oscura/core/memory_warnings.py +315 -0
  198. oscura/core/numba_backend.py +362 -0
  199. oscura/core/performance.py +352 -0
  200. oscura/core/progress.py +524 -0
  201. oscura/core/provenance.py +358 -0
  202. oscura/core/results.py +331 -0
  203. oscura/core/types.py +504 -0
  204. oscura/core/uncertainty.py +383 -0
  205. oscura/discovery/__init__.py +52 -0
  206. oscura/discovery/anomaly_detector.py +672 -0
  207. oscura/discovery/auto_decoder.py +415 -0
  208. oscura/discovery/comparison.py +497 -0
  209. oscura/discovery/quality_validator.py +528 -0
  210. oscura/discovery/signal_detector.py +769 -0
  211. oscura/dsl/__init__.py +73 -0
  212. oscura/dsl/commands.py +246 -0
  213. oscura/dsl/interpreter.py +455 -0
  214. oscura/dsl/parser.py +689 -0
  215. oscura/dsl/repl.py +172 -0
  216. oscura/exceptions.py +59 -0
  217. oscura/exploratory/__init__.py +111 -0
  218. oscura/exploratory/error_recovery.py +642 -0
  219. oscura/exploratory/fuzzy.py +513 -0
  220. oscura/exploratory/fuzzy_advanced.py +786 -0
  221. oscura/exploratory/legacy.py +831 -0
  222. oscura/exploratory/parse.py +358 -0
  223. oscura/exploratory/recovery.py +275 -0
  224. oscura/exploratory/sync.py +382 -0
  225. oscura/exploratory/unknown.py +707 -0
  226. oscura/export/__init__.py +25 -0
  227. oscura/export/wireshark/README.md +265 -0
  228. oscura/export/wireshark/__init__.py +47 -0
  229. oscura/export/wireshark/generator.py +312 -0
  230. oscura/export/wireshark/lua_builder.py +159 -0
  231. oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
  232. oscura/export/wireshark/type_mapping.py +165 -0
  233. oscura/export/wireshark/validator.py +105 -0
  234. oscura/exporters/__init__.py +94 -0
  235. oscura/exporters/csv.py +303 -0
  236. oscura/exporters/exporters.py +44 -0
  237. oscura/exporters/hdf5.py +219 -0
  238. oscura/exporters/html_export.py +701 -0
  239. oscura/exporters/json_export.py +291 -0
  240. oscura/exporters/markdown_export.py +367 -0
  241. oscura/exporters/matlab_export.py +354 -0
  242. oscura/exporters/npz_export.py +219 -0
  243. oscura/exporters/spice_export.py +210 -0
  244. oscura/extensibility/__init__.py +131 -0
  245. oscura/extensibility/docs.py +752 -0
  246. oscura/extensibility/extensions.py +1125 -0
  247. oscura/extensibility/logging.py +259 -0
  248. oscura/extensibility/measurements.py +485 -0
  249. oscura/extensibility/plugins.py +414 -0
  250. oscura/extensibility/registry.py +346 -0
  251. oscura/extensibility/templates.py +913 -0
  252. oscura/extensibility/validation.py +651 -0
  253. oscura/filtering/__init__.py +89 -0
  254. oscura/filtering/base.py +563 -0
  255. oscura/filtering/convenience.py +564 -0
  256. oscura/filtering/design.py +725 -0
  257. oscura/filtering/filters.py +32 -0
  258. oscura/filtering/introspection.py +605 -0
  259. oscura/guidance/__init__.py +24 -0
  260. oscura/guidance/recommender.py +429 -0
  261. oscura/guidance/wizard.py +518 -0
  262. oscura/inference/__init__.py +251 -0
  263. oscura/inference/active_learning/README.md +153 -0
  264. oscura/inference/active_learning/__init__.py +38 -0
  265. oscura/inference/active_learning/lstar.py +257 -0
  266. oscura/inference/active_learning/observation_table.py +230 -0
  267. oscura/inference/active_learning/oracle.py +78 -0
  268. oscura/inference/active_learning/teachers/__init__.py +15 -0
  269. oscura/inference/active_learning/teachers/simulator.py +192 -0
  270. oscura/inference/adaptive_tuning.py +453 -0
  271. oscura/inference/alignment.py +653 -0
  272. oscura/inference/bayesian.py +943 -0
  273. oscura/inference/binary.py +1016 -0
  274. oscura/inference/crc_reverse.py +711 -0
  275. oscura/inference/logic.py +288 -0
  276. oscura/inference/message_format.py +1305 -0
  277. oscura/inference/protocol.py +417 -0
  278. oscura/inference/protocol_dsl.py +1084 -0
  279. oscura/inference/protocol_library.py +1230 -0
  280. oscura/inference/sequences.py +809 -0
  281. oscura/inference/signal_intelligence.py +1509 -0
  282. oscura/inference/spectral.py +215 -0
  283. oscura/inference/state_machine.py +634 -0
  284. oscura/inference/stream.py +918 -0
  285. oscura/integrations/__init__.py +59 -0
  286. oscura/integrations/llm.py +1827 -0
  287. oscura/jupyter/__init__.py +32 -0
  288. oscura/jupyter/display.py +268 -0
  289. oscura/jupyter/magic.py +334 -0
  290. oscura/loaders/__init__.py +526 -0
  291. oscura/loaders/binary.py +69 -0
  292. oscura/loaders/configurable.py +1255 -0
  293. oscura/loaders/csv.py +26 -0
  294. oscura/loaders/csv_loader.py +473 -0
  295. oscura/loaders/hdf5.py +9 -0
  296. oscura/loaders/hdf5_loader.py +510 -0
  297. oscura/loaders/lazy.py +370 -0
  298. oscura/loaders/mmap_loader.py +583 -0
  299. oscura/loaders/numpy_loader.py +436 -0
  300. oscura/loaders/pcap.py +432 -0
  301. oscura/loaders/preprocessing.py +368 -0
  302. oscura/loaders/rigol.py +287 -0
  303. oscura/loaders/sigrok.py +321 -0
  304. oscura/loaders/tdms.py +367 -0
  305. oscura/loaders/tektronix.py +711 -0
  306. oscura/loaders/validation.py +584 -0
  307. oscura/loaders/vcd.py +464 -0
  308. oscura/loaders/wav.py +233 -0
  309. oscura/math/__init__.py +45 -0
  310. oscura/math/arithmetic.py +824 -0
  311. oscura/math/interpolation.py +413 -0
  312. oscura/onboarding/__init__.py +39 -0
  313. oscura/onboarding/help.py +498 -0
  314. oscura/onboarding/tutorials.py +405 -0
  315. oscura/onboarding/wizard.py +466 -0
  316. oscura/optimization/__init__.py +19 -0
  317. oscura/optimization/parallel.py +440 -0
  318. oscura/optimization/search.py +532 -0
  319. oscura/pipeline/__init__.py +43 -0
  320. oscura/pipeline/base.py +338 -0
  321. oscura/pipeline/composition.py +242 -0
  322. oscura/pipeline/parallel.py +448 -0
  323. oscura/pipeline/pipeline.py +375 -0
  324. oscura/pipeline/reverse_engineering.py +1119 -0
  325. oscura/plugins/__init__.py +122 -0
  326. oscura/plugins/base.py +272 -0
  327. oscura/plugins/cli.py +497 -0
  328. oscura/plugins/discovery.py +411 -0
  329. oscura/plugins/isolation.py +418 -0
  330. oscura/plugins/lifecycle.py +959 -0
  331. oscura/plugins/manager.py +493 -0
  332. oscura/plugins/registry.py +421 -0
  333. oscura/plugins/versioning.py +372 -0
  334. oscura/py.typed +0 -0
  335. oscura/quality/__init__.py +65 -0
  336. oscura/quality/ensemble.py +740 -0
  337. oscura/quality/explainer.py +338 -0
  338. oscura/quality/scoring.py +616 -0
  339. oscura/quality/warnings.py +456 -0
  340. oscura/reporting/__init__.py +248 -0
  341. oscura/reporting/advanced.py +1234 -0
  342. oscura/reporting/analyze.py +448 -0
  343. oscura/reporting/argument_preparer.py +596 -0
  344. oscura/reporting/auto_report.py +507 -0
  345. oscura/reporting/batch.py +615 -0
  346. oscura/reporting/chart_selection.py +223 -0
  347. oscura/reporting/comparison.py +330 -0
  348. oscura/reporting/config.py +615 -0
  349. oscura/reporting/content/__init__.py +39 -0
  350. oscura/reporting/content/executive.py +127 -0
  351. oscura/reporting/content/filtering.py +191 -0
  352. oscura/reporting/content/minimal.py +257 -0
  353. oscura/reporting/content/verbosity.py +162 -0
  354. oscura/reporting/core.py +508 -0
  355. oscura/reporting/core_formats/__init__.py +17 -0
  356. oscura/reporting/core_formats/multi_format.py +210 -0
  357. oscura/reporting/engine.py +836 -0
  358. oscura/reporting/export.py +366 -0
  359. oscura/reporting/formatting/__init__.py +129 -0
  360. oscura/reporting/formatting/emphasis.py +81 -0
  361. oscura/reporting/formatting/numbers.py +403 -0
  362. oscura/reporting/formatting/standards.py +55 -0
  363. oscura/reporting/formatting.py +466 -0
  364. oscura/reporting/html.py +578 -0
  365. oscura/reporting/index.py +590 -0
  366. oscura/reporting/multichannel.py +296 -0
  367. oscura/reporting/output.py +379 -0
  368. oscura/reporting/pdf.py +373 -0
  369. oscura/reporting/plots.py +731 -0
  370. oscura/reporting/pptx_export.py +360 -0
  371. oscura/reporting/renderers/__init__.py +11 -0
  372. oscura/reporting/renderers/pdf.py +94 -0
  373. oscura/reporting/sections.py +471 -0
  374. oscura/reporting/standards.py +680 -0
  375. oscura/reporting/summary_generator.py +368 -0
  376. oscura/reporting/tables.py +397 -0
  377. oscura/reporting/template_system.py +724 -0
  378. oscura/reporting/templates/__init__.py +15 -0
  379. oscura/reporting/templates/definition.py +205 -0
  380. oscura/reporting/templates/index.html +649 -0
  381. oscura/reporting/templates/index.md +173 -0
  382. oscura/schemas/__init__.py +158 -0
  383. oscura/schemas/bus_configuration.json +322 -0
  384. oscura/schemas/device_mapping.json +182 -0
  385. oscura/schemas/packet_format.json +418 -0
  386. oscura/schemas/protocol_definition.json +363 -0
  387. oscura/search/__init__.py +16 -0
  388. oscura/search/anomaly.py +292 -0
  389. oscura/search/context.py +149 -0
  390. oscura/search/pattern.py +160 -0
  391. oscura/session/__init__.py +34 -0
  392. oscura/session/annotations.py +289 -0
  393. oscura/session/history.py +313 -0
  394. oscura/session/session.py +445 -0
  395. oscura/streaming/__init__.py +43 -0
  396. oscura/streaming/chunked.py +611 -0
  397. oscura/streaming/progressive.py +393 -0
  398. oscura/streaming/realtime.py +622 -0
  399. oscura/testing/__init__.py +54 -0
  400. oscura/testing/synthetic.py +808 -0
  401. oscura/triggering/__init__.py +68 -0
  402. oscura/triggering/base.py +229 -0
  403. oscura/triggering/edge.py +353 -0
  404. oscura/triggering/pattern.py +344 -0
  405. oscura/triggering/pulse.py +581 -0
  406. oscura/triggering/window.py +453 -0
  407. oscura/ui/__init__.py +48 -0
  408. oscura/ui/formatters.py +526 -0
  409. oscura/ui/progressive_display.py +340 -0
  410. oscura/utils/__init__.py +99 -0
  411. oscura/utils/autodetect.py +338 -0
  412. oscura/utils/buffer.py +389 -0
  413. oscura/utils/lazy.py +407 -0
  414. oscura/utils/lazy_imports.py +147 -0
  415. oscura/utils/memory.py +836 -0
  416. oscura/utils/memory_advanced.py +1326 -0
  417. oscura/utils/memory_extensions.py +465 -0
  418. oscura/utils/progressive.py +352 -0
  419. oscura/utils/windowing.py +362 -0
  420. oscura/visualization/__init__.py +321 -0
  421. oscura/visualization/accessibility.py +526 -0
  422. oscura/visualization/annotations.py +374 -0
  423. oscura/visualization/axis_scaling.py +305 -0
  424. oscura/visualization/colors.py +453 -0
  425. oscura/visualization/digital.py +337 -0
  426. oscura/visualization/eye.py +420 -0
  427. oscura/visualization/histogram.py +281 -0
  428. oscura/visualization/interactive.py +858 -0
  429. oscura/visualization/jitter.py +702 -0
  430. oscura/visualization/keyboard.py +394 -0
  431. oscura/visualization/layout.py +365 -0
  432. oscura/visualization/optimization.py +1028 -0
  433. oscura/visualization/palettes.py +446 -0
  434. oscura/visualization/plot.py +92 -0
  435. oscura/visualization/power.py +290 -0
  436. oscura/visualization/power_extended.py +626 -0
  437. oscura/visualization/presets.py +467 -0
  438. oscura/visualization/protocols.py +932 -0
  439. oscura/visualization/render.py +207 -0
  440. oscura/visualization/rendering.py +444 -0
  441. oscura/visualization/reverse_engineering.py +791 -0
  442. oscura/visualization/signal_integrity.py +808 -0
  443. oscura/visualization/specialized.py +553 -0
  444. oscura/visualization/spectral.py +811 -0
  445. oscura/visualization/styles.py +381 -0
  446. oscura/visualization/thumbnails.py +311 -0
  447. oscura/visualization/time_axis.py +351 -0
  448. oscura/visualization/waveform.py +367 -0
  449. oscura/workflow/__init__.py +13 -0
  450. oscura/workflow/dag.py +377 -0
  451. oscura/workflows/__init__.py +58 -0
  452. oscura/workflows/compliance.py +280 -0
  453. oscura/workflows/digital.py +272 -0
  454. oscura/workflows/multi_trace.py +502 -0
  455. oscura/workflows/power.py +178 -0
  456. oscura/workflows/protocol.py +492 -0
  457. oscura/workflows/reverse_engineering.py +639 -0
  458. oscura/workflows/signal_integrity.py +227 -0
  459. oscura-0.1.0.dist-info/METADATA +300 -0
  460. oscura-0.1.0.dist-info/RECORD +463 -0
  461. oscura-0.1.0.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
  463. oscura-0.0.1.dist-info/METADATA +0 -63
  464. oscura-0.0.1.dist-info/RECORD +0 -5
  465. {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,461 @@
1
+ """Stimulus-response mapping for CAN bus reverse engineering.
2
+
3
+ This module helps identify which CAN messages and signals change in response
4
+ to user actions, enabling rapid identification of relevant data during reverse
5
+ engineering work.
6
+
7
+ The primary use case is comparing:
8
+ 1. Baseline capture (no actions)
9
+ 2. Stimulus capture (button press, pedal movement, etc.)
10
+
11
+ This allows answering questions like:
12
+ - "What messages change when I press the brake?"
13
+ - "Which signals react to throttle position?"
14
+ - "What initializes when I turn the key?"
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import dataclass, field
20
+ from typing import TYPE_CHECKING
21
+
22
+ import numpy as np
23
+ from scipy import stats
24
+
25
+ if TYPE_CHECKING:
26
+ from oscura.automotive.can.models import CANMessage
27
+ from oscura.automotive.can.session import CANSession
28
+
29
+ __all__ = [
30
+ "ByteChange",
31
+ "FrequencyChange",
32
+ "StimulusResponseAnalyzer",
33
+ "StimulusResponseReport",
34
+ ]
35
+
36
+
37
+ @dataclass
38
+ class ByteChange:
39
+ """Detected change in a specific byte position.
40
+
41
+ Attributes:
42
+ byte_position: Byte position (0-7 for CAN 2.0).
43
+ baseline_values: Set of values observed in baseline.
44
+ stimulus_values: Set of values observed in stimulus.
45
+ change_magnitude: Normalized change magnitude (0.0-1.0).
46
+ value_range_change: Change in value range (max - min).
47
+ mean_change: Change in mean value.
48
+ new_values: Values that appear only in stimulus.
49
+ disappeared_values: Values that appear only in baseline.
50
+ """
51
+
52
+ byte_position: int
53
+ baseline_values: set[int]
54
+ stimulus_values: set[int]
55
+ change_magnitude: float
56
+ value_range_change: float
57
+ mean_change: float
58
+ new_values: set[int] = field(default_factory=set)
59
+ disappeared_values: set[int] = field(default_factory=set)
60
+
61
+ def __post_init__(self) -> None:
62
+ """Calculate derived fields."""
63
+ self.new_values = self.stimulus_values - self.baseline_values
64
+ self.disappeared_values = self.baseline_values - self.stimulus_values
65
+
66
+
67
+ @dataclass
68
+ class FrequencyChange:
69
+ """Detected frequency change for a message ID.
70
+
71
+ Attributes:
72
+ message_id: CAN arbitration ID.
73
+ baseline_hz: Frequency in baseline (Hz).
74
+ stimulus_hz: Frequency in stimulus (Hz).
75
+ change_ratio: Ratio of stimulus to baseline frequency.
76
+ change_type: Type of change ('increased', 'decreased', 'appeared', 'disappeared').
77
+ significance: Statistical significance (0.0-1.0).
78
+ """
79
+
80
+ message_id: int
81
+ baseline_hz: float
82
+ stimulus_hz: float
83
+ change_ratio: float
84
+ change_type: str
85
+ significance: float
86
+
87
+ def __repr__(self) -> str:
88
+ """Human-readable representation."""
89
+ return (
90
+ f"FrequencyChange(0x{self.message_id:03X}: "
91
+ f"{self.baseline_hz:.1f}Hz -> {self.stimulus_hz:.1f}Hz, "
92
+ f"ratio={self.change_ratio:.2f}, {self.change_type})"
93
+ )
94
+
95
+
96
+ @dataclass
97
+ class StimulusResponseReport:
98
+ """Complete stimulus-response analysis report.
99
+
100
+ Attributes:
101
+ changed_messages: Message IDs with detected changes.
102
+ new_messages: Message IDs only in stimulus.
103
+ disappeared_messages: Message IDs only in baseline.
104
+ frequency_changes: Frequency changes by message ID.
105
+ byte_changes: Byte-level changes by message ID.
106
+ duration_baseline: Duration of baseline session (seconds).
107
+ duration_stimulus: Duration of stimulus session (seconds).
108
+ confidence_threshold: Minimum confidence used for detection.
109
+ """
110
+
111
+ changed_messages: list[int]
112
+ new_messages: list[int]
113
+ disappeared_messages: list[int]
114
+ frequency_changes: dict[int, FrequencyChange]
115
+ byte_changes: dict[int, list[ByteChange]]
116
+ duration_baseline: float
117
+ duration_stimulus: float
118
+ confidence_threshold: float
119
+
120
+ def summary(self) -> str:
121
+ """Generate human-readable summary.
122
+
123
+ Returns:
124
+ Multi-line summary string.
125
+ """
126
+ lines = [
127
+ "=== Stimulus-Response Analysis ===",
128
+ f"Baseline duration: {self.duration_baseline:.2f}s",
129
+ f"Stimulus duration: {self.duration_stimulus:.2f}s",
130
+ f"Confidence threshold: {self.confidence_threshold:.2f}",
131
+ "",
132
+ ]
133
+
134
+ if self.new_messages:
135
+ lines.append(f"New Messages ({len(self.new_messages)}):")
136
+ for msg_id in sorted(self.new_messages):
137
+ lines.append(f" 0x{msg_id:03X}")
138
+ lines.append("")
139
+
140
+ if self.disappeared_messages:
141
+ lines.append(f"Disappeared Messages ({len(self.disappeared_messages)}):")
142
+ for msg_id in sorted(self.disappeared_messages):
143
+ lines.append(f" 0x{msg_id:03X}")
144
+ lines.append("")
145
+
146
+ if self.frequency_changes:
147
+ lines.append(f"Frequency Changes ({len(self.frequency_changes)}):")
148
+ for msg_id in sorted(self.frequency_changes.keys()):
149
+ fc = self.frequency_changes[msg_id]
150
+ lines.append(
151
+ f" 0x{msg_id:03X}: {fc.baseline_hz:.1f}Hz -> {fc.stimulus_hz:.1f}Hz "
152
+ f"({fc.change_type}, sig={fc.significance:.2f})"
153
+ )
154
+ lines.append("")
155
+
156
+ if self.byte_changes:
157
+ lines.append(f"Byte-Level Changes ({len(self.byte_changes)} messages):")
158
+ for msg_id in sorted(self.byte_changes.keys()):
159
+ changes = self.byte_changes[msg_id]
160
+ lines.append(f" 0x{msg_id:03X}: {len(changes)} bytes changed")
161
+ for bc in changes:
162
+ lines.append(
163
+ f" Byte {bc.byte_position}: "
164
+ f"magnitude={bc.change_magnitude:.2f}, "
165
+ f"mean_change={bc.mean_change:.1f}"
166
+ )
167
+ lines.append("")
168
+
169
+ if not any([self.new_messages, self.disappeared_messages, self.changed_messages]):
170
+ lines.append("No significant changes detected.")
171
+
172
+ return "\n".join(lines)
173
+
174
+
175
+ class StimulusResponseAnalyzer:
176
+ """Analyzer for detecting CAN message changes between sessions.
177
+
178
+ This class compares a baseline session (no user action) against a stimulus
179
+ session (with user action) to identify which messages and signals respond
180
+ to the stimulus.
181
+ """
182
+
183
+ def detect_responses(
184
+ self,
185
+ baseline_session: CANSession,
186
+ stimulus_session: CANSession,
187
+ time_window_ms: float = 100,
188
+ change_threshold: float = 0.1,
189
+ ) -> StimulusResponseReport:
190
+ """Detect which messages changed between sessions.
191
+
192
+ Args:
193
+ baseline_session: Baseline capture (no action).
194
+ stimulus_session: Stimulus capture (with action).
195
+ time_window_ms: Time window for aligning messages (milliseconds).
196
+ change_threshold: Minimum normalized change to report (0.0-1.0).
197
+
198
+ Returns:
199
+ StimulusResponseReport with detected changes.
200
+ """
201
+ # Get message IDs from both sessions
202
+ baseline_ids = baseline_session.unique_ids()
203
+ stimulus_ids = stimulus_session.unique_ids()
204
+
205
+ # Detect new and disappeared messages
206
+ new_messages = sorted(stimulus_ids - baseline_ids)
207
+ disappeared_messages = sorted(baseline_ids - stimulus_ids)
208
+
209
+ # Messages present in both sessions
210
+ common_ids = baseline_ids & stimulus_ids
211
+
212
+ # Detect frequency changes
213
+ frequency_changes = {}
214
+ for msg_id in common_ids:
215
+ freq_change = self._detect_frequency_change(baseline_session, stimulus_session, msg_id)
216
+ if freq_change and freq_change.significance >= change_threshold:
217
+ frequency_changes[msg_id] = freq_change
218
+
219
+ # Detect byte-level changes
220
+ byte_changes = {}
221
+ changed_messages = []
222
+ for msg_id in common_ids:
223
+ changes = self.analyze_signal_changes(
224
+ baseline_session, stimulus_session, msg_id, byte_threshold=1
225
+ )
226
+ # Filter by change threshold
227
+ significant_changes = [c for c in changes if c.change_magnitude >= change_threshold]
228
+ if significant_changes:
229
+ byte_changes[msg_id] = significant_changes
230
+ changed_messages.append(msg_id)
231
+
232
+ # Get durations
233
+ baseline_start, baseline_end = baseline_session.time_range()
234
+ stimulus_start, stimulus_end = stimulus_session.time_range()
235
+
236
+ return StimulusResponseReport(
237
+ changed_messages=sorted(set(changed_messages) | set(frequency_changes.keys())),
238
+ new_messages=new_messages,
239
+ disappeared_messages=disappeared_messages,
240
+ frequency_changes=frequency_changes,
241
+ byte_changes=byte_changes,
242
+ duration_baseline=baseline_end - baseline_start,
243
+ duration_stimulus=stimulus_end - stimulus_start,
244
+ confidence_threshold=change_threshold,
245
+ )
246
+
247
+ def analyze_signal_changes(
248
+ self,
249
+ baseline_session: CANSession,
250
+ stimulus_session: CANSession,
251
+ message_id: int,
252
+ byte_threshold: int = 1,
253
+ ) -> list[ByteChange]:
254
+ """Analyze byte-level changes in a specific message.
255
+
256
+ Args:
257
+ baseline_session: Baseline capture.
258
+ stimulus_session: Stimulus capture.
259
+ message_id: CAN arbitration ID to analyze.
260
+ byte_threshold: Minimum number of unique values to consider changing.
261
+
262
+ Returns:
263
+ List of ByteChange objects for changed bytes.
264
+ """
265
+ # Get messages for this ID from both sessions
266
+ baseline_msgs = baseline_session._messages.filter_by_id(message_id)
267
+ stimulus_msgs = stimulus_session._messages.filter_by_id(message_id)
268
+
269
+ if not baseline_msgs.messages or not stimulus_msgs.messages:
270
+ return []
271
+
272
+ # Determine max DLC
273
+ max_dlc = max(
274
+ max(msg.dlc for msg in baseline_msgs.messages),
275
+ max(msg.dlc for msg in stimulus_msgs.messages),
276
+ )
277
+
278
+ changes = []
279
+ for byte_pos in range(max_dlc):
280
+ # Extract byte values from both sessions
281
+ baseline_values = [
282
+ msg.data[byte_pos] for msg in baseline_msgs.messages if len(msg.data) > byte_pos
283
+ ]
284
+ stimulus_values = [
285
+ msg.data[byte_pos] for msg in stimulus_msgs.messages if len(msg.data) > byte_pos
286
+ ]
287
+
288
+ if not baseline_values or not stimulus_values:
289
+ continue
290
+
291
+ # Analyze changes
292
+ baseline_set = set(baseline_values)
293
+ stimulus_set = set(stimulus_values)
294
+
295
+ # Skip if not enough unique values
296
+ if len(baseline_set) < byte_threshold and len(stimulus_set) < byte_threshold:
297
+ continue
298
+
299
+ # Calculate statistics
300
+ baseline_arr = np.array(baseline_values)
301
+ stimulus_arr = np.array(stimulus_values)
302
+
303
+ baseline_mean = float(np.mean(baseline_arr))
304
+ stimulus_mean = float(np.mean(stimulus_arr))
305
+ mean_change = stimulus_mean - baseline_mean
306
+
307
+ baseline_range = float(np.max(baseline_arr) - np.min(baseline_arr))
308
+ stimulus_range = float(np.max(stimulus_arr) - np.min(stimulus_arr))
309
+ value_range_change = stimulus_range - baseline_range
310
+
311
+ # Calculate normalized change magnitude using multiple factors
312
+ # 1. Mean change (normalized by full byte range)
313
+ mean_change_norm = abs(mean_change) / 255.0
314
+
315
+ # 2. Range change (normalized by full byte range)
316
+ range_change_norm = abs(value_range_change) / 255.0
317
+
318
+ # 3. Set difference (Jaccard distance)
319
+ union_size = len(baseline_set | stimulus_set)
320
+ intersection_size = len(baseline_set & stimulus_set)
321
+ if union_size > 0:
322
+ jaccard_dist = 1.0 - (intersection_size / union_size)
323
+ else:
324
+ jaccard_dist = 0.0
325
+
326
+ # 4. Distribution change (Kolmogorov-Smirnov test)
327
+ try:
328
+ ks_stat, _ = stats.ks_2samp(baseline_arr, stimulus_arr)
329
+ ks_change_norm = float(ks_stat)
330
+ except Exception:
331
+ ks_change_norm = 0.0
332
+
333
+ # Combine factors (weighted average)
334
+ change_magnitude = (
335
+ 0.3 * mean_change_norm
336
+ + 0.2 * range_change_norm
337
+ + 0.3 * jaccard_dist
338
+ + 0.2 * ks_change_norm
339
+ )
340
+
341
+ # Only report if there's a meaningful change
342
+ if change_magnitude > 0.0:
343
+ changes.append(
344
+ ByteChange(
345
+ byte_position=byte_pos,
346
+ baseline_values=baseline_set,
347
+ stimulus_values=stimulus_set,
348
+ change_magnitude=change_magnitude,
349
+ value_range_change=value_range_change,
350
+ mean_change=mean_change,
351
+ )
352
+ )
353
+
354
+ return changes
355
+
356
+ def find_responsive_messages(
357
+ self,
358
+ baseline_session: CANSession,
359
+ stimulus_session: CANSession,
360
+ ) -> list[int]:
361
+ """Find message IDs that changed.
362
+
363
+ This is a convenience method that returns just the list of message IDs
364
+ that showed any type of change.
365
+
366
+ Args:
367
+ baseline_session: Baseline capture.
368
+ stimulus_session: Stimulus capture.
369
+
370
+ Returns:
371
+ Sorted list of message IDs that changed.
372
+ """
373
+ report = self.detect_responses(baseline_session, stimulus_session)
374
+ return report.changed_messages + report.new_messages
375
+
376
+ def _detect_frequency_change(
377
+ self,
378
+ baseline_session: CANSession,
379
+ stimulus_session: CANSession,
380
+ message_id: int,
381
+ ) -> FrequencyChange | None:
382
+ """Detect frequency change for a specific message ID.
383
+
384
+ Args:
385
+ baseline_session: Baseline capture.
386
+ stimulus_session: Stimulus capture.
387
+ message_id: CAN arbitration ID.
388
+
389
+ Returns:
390
+ FrequencyChange if detected, None otherwise.
391
+ """
392
+ # Get messages
393
+ baseline_msgs = baseline_session._messages.filter_by_id(message_id)
394
+ stimulus_msgs = stimulus_session._messages.filter_by_id(message_id)
395
+
396
+ # Calculate frequencies
397
+ baseline_hz = self._calculate_frequency(
398
+ baseline_msgs.messages, baseline_session.time_range()
399
+ )
400
+ stimulus_hz = self._calculate_frequency(
401
+ stimulus_msgs.messages, stimulus_session.time_range()
402
+ )
403
+
404
+ # Determine change type and ratio
405
+ if baseline_hz == 0.0 and stimulus_hz > 0.0:
406
+ change_type = "appeared"
407
+ change_ratio = float("inf")
408
+ significance = 1.0
409
+ elif stimulus_hz == 0.0 and baseline_hz > 0.0:
410
+ change_type = "disappeared"
411
+ change_ratio = 0.0
412
+ significance = 1.0
413
+ elif baseline_hz > 0.0:
414
+ change_ratio = stimulus_hz / baseline_hz
415
+
416
+ # Determine if change is significant
417
+ # Use a threshold of 20% change
418
+ if change_ratio > 1.2:
419
+ change_type = "increased"
420
+ significance = min(1.0, (change_ratio - 1.0) / 1.0)
421
+ elif change_ratio < 0.8:
422
+ change_type = "decreased"
423
+ significance = min(1.0, (1.0 - change_ratio) / 1.0)
424
+ else:
425
+ # No significant change
426
+ return None
427
+ else:
428
+ # Both frequencies are zero
429
+ return None
430
+
431
+ return FrequencyChange(
432
+ message_id=message_id,
433
+ baseline_hz=baseline_hz,
434
+ stimulus_hz=stimulus_hz,
435
+ change_ratio=change_ratio,
436
+ change_type=change_type,
437
+ significance=significance,
438
+ )
439
+
440
+ @staticmethod
441
+ def _calculate_frequency(messages: list[CANMessage], time_range: tuple[float, float]) -> float:
442
+ """Calculate message frequency.
443
+
444
+ Args:
445
+ messages: List of CAN messages.
446
+ time_range: Tuple of (start_time, end_time).
447
+
448
+ Returns:
449
+ Frequency in Hz.
450
+ """
451
+ if not messages or len(messages) < 2:
452
+ return 0.0
453
+
454
+ start_time, end_time = time_range
455
+ duration = end_time - start_time
456
+
457
+ if duration <= 0.0:
458
+ return 0.0
459
+
460
+ # Use message count divided by duration
461
+ return (len(messages) - 1) / duration
@@ -0,0 +1,15 @@
1
+ """DBC database support for CAN signal definitions.
2
+
3
+ This module provides DBC file parsing and generation capabilities.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ __all__ = ["DBCGenerator", "DBCParser", "load_dbc"]
9
+
10
+ try:
11
+ from oscura.automotive.dbc.generator import DBCGenerator
12
+ from oscura.automotive.dbc.parser import DBCParser, load_dbc
13
+ except ImportError:
14
+ # Optional dependencies not installed
15
+ pass
@@ -0,0 +1,156 @@
1
+ """DBC file generator from discovery documents.
2
+
3
+ This module generates standard DBC files from TraceKit discovery documents,
4
+ enabling export of reverse-engineered protocols to industry-standard format.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pathlib import Path
10
+ from typing import TYPE_CHECKING
11
+
12
+ if TYPE_CHECKING:
13
+ from oscura.automotive.can.discovery import (
14
+ DiscoveryDocument,
15
+ )
16
+ from oscura.automotive.can.session import CANSession
17
+
18
+ __all__ = ["DBCGenerator"]
19
+
20
+
21
+ class DBCGenerator:
22
+ """Generate DBC files from discovery documents."""
23
+
24
+ @staticmethod
25
+ def generate(
26
+ discovery: DiscoveryDocument,
27
+ output_path: Path | str,
28
+ min_confidence: float = 0.0,
29
+ include_comments: bool = True,
30
+ ) -> None:
31
+ """Generate DBC file from discovery document.
32
+
33
+ Args:
34
+ discovery: DiscoveryDocument with discovered signals.
35
+ output_path: Output DBC file path.
36
+ min_confidence: Minimum confidence threshold for including signals.
37
+ include_comments: Include evidence as comments in DBC.
38
+ """
39
+ path = Path(output_path)
40
+
41
+ lines = []
42
+
43
+ # DBC header
44
+ lines.append('VERSION ""')
45
+ lines.append("")
46
+ lines.append("NS_ :")
47
+ lines.append("")
48
+ lines.append("BS_:")
49
+ lines.append("")
50
+ lines.append("BU_:") # No nodes defined
51
+ lines.append("")
52
+
53
+ # Generate messages
54
+ for msg in sorted(discovery.messages.values(), key=lambda m: m.id):
55
+ # Filter signals by confidence
56
+ signals = [s for s in msg.signals if s.confidence >= min_confidence]
57
+
58
+ if not signals:
59
+ continue # Skip messages with no high-confidence signals
60
+
61
+ # Message definition
62
+ # Format: BO_ <ID> <Name>: <DLC> <Transmitter>
63
+ transmitter = msg.transmitter if msg.transmitter else "Vector__XXX"
64
+ lines.append(f"BO_ {msg.id} {msg.name}: {msg.length} {transmitter}")
65
+
66
+ # Signals
67
+ for sig in signals:
68
+ # Format: SG_ <Name> : <StartBit>|<Length>@<ByteOrder><ValueType> (<Scale>,<Offset>) [<Min>|<Max>] "<Unit>" <Receiver>
69
+ byte_order = "0" if sig.byte_order == "big_endian" else "1"
70
+ value_type = "+" if sig.value_type == "unsigned" else "-"
71
+
72
+ min_val = sig.min_value if sig.min_value is not None else 0
73
+ max_val = sig.max_value if sig.max_value is not None else 0
74
+
75
+ signal_line = (
76
+ f" SG_ {sig.name} : {sig.start_bit}|{sig.length}@{byte_order}{value_type} "
77
+ f"({sig.scale},{sig.offset}) [{min_val}|{max_val}] "
78
+ f'"{sig.unit}" Vector__XXX'
79
+ )
80
+
81
+ lines.append(signal_line)
82
+
83
+ # Add comment if evidence exists
84
+ if include_comments and sig.evidence:
85
+ evidence_str = "; ".join(sig.evidence)
86
+ # DBC comments format: CM_ SG_ <ID> <SignalName> "<Comment>";
87
+ comment_line = f'CM_ SG_ {msg.id} {sig.name} "{evidence_str}";'
88
+ lines.append("")
89
+ lines.append(comment_line)
90
+
91
+ lines.append("")
92
+
93
+ # Write file with latin-1 encoding (DBC standard)
94
+ with open(path, "w", encoding="latin-1") as f:
95
+ f.write("\n".join(lines))
96
+
97
+ @staticmethod
98
+ def generate_from_session(
99
+ session: CANSession,
100
+ output_path: Path | str,
101
+ min_confidence: float = 0.8, # noqa: ARG004 - API compatibility parameter
102
+ ) -> None:
103
+ """Generate DBC file from CANSession with documented signals.
104
+
105
+ Args:
106
+ session: CANSession with documented signals.
107
+ output_path: Output DBC file path.
108
+ min_confidence: Minimum confidence threshold (reserved for future use).
109
+ """
110
+ from oscura.automotive.can.discovery import (
111
+ DiscoveryDocument,
112
+ MessageDiscovery,
113
+ SignalDiscovery,
114
+ )
115
+
116
+ # Build discovery document from session
117
+ doc = DiscoveryDocument()
118
+
119
+ # Get all unique IDs that have documented signals
120
+ for arb_id in session.unique_ids():
121
+ try:
122
+ msg_wrapper = session.message(arb_id)
123
+ documented = msg_wrapper.get_documented_signals()
124
+
125
+ if documented:
126
+ # Create message discovery
127
+ analysis = session.analyze_message(arb_id)
128
+
129
+ signal_discoveries = []
130
+ for sig_def in documented.values():
131
+ sig_disc = SignalDiscovery.from_definition(
132
+ sig_def,
133
+ confidence=1.0,
134
+ evidence=[],
135
+ )
136
+ signal_discoveries.append(sig_disc)
137
+
138
+ msg_disc = MessageDiscovery(
139
+ id=arb_id,
140
+ name=f"Message_{arb_id:03X}",
141
+ length=max(
142
+ msg.dlc for msg in session._messages.filter_by_id(arb_id).messages
143
+ ),
144
+ cycle_time_ms=analysis.period_ms,
145
+ confidence=1.0,
146
+ signals=signal_discoveries,
147
+ )
148
+
149
+ doc.add_message(msg_disc)
150
+
151
+ except Exception:
152
+ # Skip messages without documented signals
153
+ pass
154
+
155
+ # Generate DBC
156
+ DBCGenerator.generate(doc, output_path, min_confidence=0.0)