oscura 0.0.1__py3-none-any.whl → 0.1.1__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.1.dist-info/METADATA +300 -0
  460. oscura-0.1.1.dist-info/RECORD +463 -0
  461. oscura-0.1.1.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.1.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.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,707 @@
1
+ """OBD-II protocol decoder.
2
+
3
+ This module implements OBD-II (SAE J1979) diagnostic protocol decoding
4
+ for standard vehicle diagnostics.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import Callable
10
+ from dataclasses import dataclass
11
+ from typing import TYPE_CHECKING, ClassVar
12
+
13
+ if TYPE_CHECKING:
14
+ from oscura.automotive.can.models import CANMessage
15
+
16
+ __all__ = ["PID", "OBD2Decoder", "OBD2Response"]
17
+
18
+
19
+ @dataclass
20
+ class PID:
21
+ """OBD-II Parameter ID definition.
22
+
23
+ Attributes:
24
+ mode: OBD mode (1-10).
25
+ pid: PID number (0x00-0xFF).
26
+ name: Parameter name.
27
+ description: Description.
28
+ formula: Decoding formula function.
29
+ unit: Physical unit.
30
+ min_value: Minimum value.
31
+ max_value: Maximum value.
32
+ """
33
+
34
+ mode: int
35
+ pid: int
36
+ name: str
37
+ description: str
38
+ formula: Callable[[bytes], float]
39
+ unit: str
40
+ min_value: float
41
+ max_value: float
42
+
43
+
44
+ @dataclass
45
+ class OBD2Response:
46
+ """Decoded OBD-II response.
47
+
48
+ Attributes:
49
+ mode: OBD mode.
50
+ pid: PID number.
51
+ name: Parameter name.
52
+ value: Decoded value.
53
+ unit: Physical unit.
54
+ timestamp: Message timestamp.
55
+ """
56
+
57
+ mode: int
58
+ pid: int
59
+ name: str
60
+ value: float
61
+ unit: str
62
+ timestamp: float
63
+
64
+
65
+ class OBD2Decoder:
66
+ """OBD-II protocol decoder.
67
+
68
+ Supports Mode 01 (current data) PIDs.
69
+ """
70
+
71
+ # Standard Mode 01 PIDs (most common)
72
+ PIDS: ClassVar[dict[int, PID]] = {
73
+ # PID support bitmaps
74
+ 0x00: PID(
75
+ mode=1,
76
+ pid=0x00,
77
+ name="PIDs_supported_01_20",
78
+ description="PIDs supported [01-20]",
79
+ formula=lambda data: int.from_bytes(data[1:5], "big"),
80
+ unit="bitmap",
81
+ min_value=0,
82
+ max_value=0xFFFFFFFF,
83
+ ),
84
+ 0x20: PID(
85
+ mode=1,
86
+ pid=0x20,
87
+ name="PIDs_supported_21_40",
88
+ description="PIDs supported [21-40]",
89
+ formula=lambda data: int.from_bytes(data[1:5], "big"),
90
+ unit="bitmap",
91
+ min_value=0,
92
+ max_value=0xFFFFFFFF,
93
+ ),
94
+ 0x40: PID(
95
+ mode=1,
96
+ pid=0x40,
97
+ name="PIDs_supported_41_60",
98
+ description="PIDs supported [41-60]",
99
+ formula=lambda data: int.from_bytes(data[1:5], "big"),
100
+ unit="bitmap",
101
+ min_value=0,
102
+ max_value=0xFFFFFFFF,
103
+ ),
104
+ 0x60: PID(
105
+ mode=1,
106
+ pid=0x60,
107
+ name="PIDs_supported_61_80",
108
+ description="PIDs supported [61-80]",
109
+ formula=lambda data: int.from_bytes(data[1:5], "big"),
110
+ unit="bitmap",
111
+ min_value=0,
112
+ max_value=0xFFFFFFFF,
113
+ ),
114
+ 0x80: PID(
115
+ mode=1,
116
+ pid=0x80,
117
+ name="PIDs_supported_81_A0",
118
+ description="PIDs supported [81-A0]",
119
+ formula=lambda data: int.from_bytes(data[1:5], "big"),
120
+ unit="bitmap",
121
+ min_value=0,
122
+ max_value=0xFFFFFFFF,
123
+ ),
124
+ 0xA0: PID(
125
+ mode=1,
126
+ pid=0xA0,
127
+ name="PIDs_supported_A1_C0",
128
+ description="PIDs supported [A1-C0]",
129
+ formula=lambda data: int.from_bytes(data[1:5], "big"),
130
+ unit="bitmap",
131
+ min_value=0,
132
+ max_value=0xFFFFFFFF,
133
+ ),
134
+ 0xC0: PID(
135
+ mode=1,
136
+ pid=0xC0,
137
+ name="PIDs_supported_C1_E0",
138
+ description="PIDs supported [C1-E0]",
139
+ formula=lambda data: int.from_bytes(data[1:5], "big"),
140
+ unit="bitmap",
141
+ min_value=0,
142
+ max_value=0xFFFFFFFF,
143
+ ),
144
+ # Fuel system
145
+ 0x03: PID(
146
+ mode=1,
147
+ pid=0x03,
148
+ name="fuel_system_status",
149
+ description="Fuel System Status",
150
+ formula=lambda data: data[1],
151
+ unit="bitmap",
152
+ min_value=0,
153
+ max_value=255,
154
+ ),
155
+ 0x04: PID(
156
+ mode=1,
157
+ pid=0x04,
158
+ name="calculated_engine_load",
159
+ description="Calculated Engine Load",
160
+ formula=lambda data: data[1] * 100.0 / 255,
161
+ unit="%",
162
+ min_value=0,
163
+ max_value=100,
164
+ ),
165
+ 0x05: PID(
166
+ mode=1,
167
+ pid=0x05,
168
+ name="coolant_temp",
169
+ description="Engine Coolant Temperature",
170
+ formula=lambda data: data[1] - 40,
171
+ unit="°C",
172
+ min_value=-40,
173
+ max_value=215,
174
+ ),
175
+ 0x06: PID(
176
+ mode=1,
177
+ pid=0x06,
178
+ name="short_term_fuel_trim_bank1",
179
+ description="Short Term Fuel Trim - Bank 1",
180
+ formula=lambda data: (data[1] - 128) * 100.0 / 128,
181
+ unit="%",
182
+ min_value=-100,
183
+ max_value=99.22,
184
+ ),
185
+ 0x07: PID(
186
+ mode=1,
187
+ pid=0x07,
188
+ name="long_term_fuel_trim_bank1",
189
+ description="Long Term Fuel Trim - Bank 1",
190
+ formula=lambda data: (data[1] - 128) * 100.0 / 128,
191
+ unit="%",
192
+ min_value=-100,
193
+ max_value=99.22,
194
+ ),
195
+ 0x08: PID(
196
+ mode=1,
197
+ pid=0x08,
198
+ name="short_term_fuel_trim_bank2",
199
+ description="Short Term Fuel Trim - Bank 2",
200
+ formula=lambda data: (data[1] - 128) * 100.0 / 128,
201
+ unit="%",
202
+ min_value=-100,
203
+ max_value=99.22,
204
+ ),
205
+ 0x09: PID(
206
+ mode=1,
207
+ pid=0x09,
208
+ name="long_term_fuel_trim_bank2",
209
+ description="Long Term Fuel Trim - Bank 2",
210
+ formula=lambda data: (data[1] - 128) * 100.0 / 128,
211
+ unit="%",
212
+ min_value=-100,
213
+ max_value=99.22,
214
+ ),
215
+ 0x0A: PID(
216
+ mode=1,
217
+ pid=0x0A,
218
+ name="fuel_pressure",
219
+ description="Fuel Pressure (gauge)",
220
+ formula=lambda data: data[1] * 3,
221
+ unit="kPa",
222
+ min_value=0,
223
+ max_value=765,
224
+ ),
225
+ 0x0B: PID(
226
+ mode=1,
227
+ pid=0x0B,
228
+ name="intake_manifold_pressure",
229
+ description="Intake Manifold Absolute Pressure",
230
+ formula=lambda data: data[1],
231
+ unit="kPa",
232
+ min_value=0,
233
+ max_value=255,
234
+ ),
235
+ 0x0C: PID(
236
+ mode=1,
237
+ pid=0x0C,
238
+ name="engine_rpm",
239
+ description="Engine RPM",
240
+ formula=lambda data: (256 * data[1] + data[2]) / 4,
241
+ unit="rpm",
242
+ min_value=0,
243
+ max_value=16383.75,
244
+ ),
245
+ 0x0D: PID(
246
+ mode=1,
247
+ pid=0x0D,
248
+ name="vehicle_speed",
249
+ description="Vehicle Speed",
250
+ formula=lambda data: data[1],
251
+ unit="km/h",
252
+ min_value=0,
253
+ max_value=255,
254
+ ),
255
+ 0x0E: PID(
256
+ mode=1,
257
+ pid=0x0E,
258
+ name="timing_advance",
259
+ description="Timing Advance",
260
+ formula=lambda data: (data[1] - 128) / 2,
261
+ unit="° before TDC",
262
+ min_value=-64,
263
+ max_value=63.5,
264
+ ),
265
+ 0x0F: PID(
266
+ mode=1,
267
+ pid=0x0F,
268
+ name="intake_air_temp",
269
+ description="Intake Air Temperature",
270
+ formula=lambda data: data[1] - 40,
271
+ unit="°C",
272
+ min_value=-40,
273
+ max_value=215,
274
+ ),
275
+ 0x10: PID(
276
+ mode=1,
277
+ pid=0x10,
278
+ name="maf_air_flow_rate",
279
+ description="MAF Air Flow Rate",
280
+ formula=lambda data: (256 * data[1] + data[2]) / 100,
281
+ unit="g/s",
282
+ min_value=0,
283
+ max_value=655.35,
284
+ ),
285
+ 0x11: PID(
286
+ mode=1,
287
+ pid=0x11,
288
+ name="throttle_position",
289
+ description="Throttle Position",
290
+ formula=lambda data: data[1] * 100.0 / 255,
291
+ unit="%",
292
+ min_value=0,
293
+ max_value=100,
294
+ ),
295
+ # Oxygen sensors (Bank 1)
296
+ 0x14: PID(
297
+ mode=1,
298
+ pid=0x14,
299
+ name="o2_sensor1_voltage",
300
+ description="O2 Sensor 1 Voltage",
301
+ formula=lambda data: data[1] / 200,
302
+ unit="V",
303
+ min_value=0,
304
+ max_value=1.275,
305
+ ),
306
+ 0x15: PID(
307
+ mode=1,
308
+ pid=0x15,
309
+ name="o2_sensor2_voltage",
310
+ description="O2 Sensor 2 Voltage",
311
+ formula=lambda data: data[1] / 200,
312
+ unit="V",
313
+ min_value=0,
314
+ max_value=1.275,
315
+ ),
316
+ 0x16: PID(
317
+ mode=1,
318
+ pid=0x16,
319
+ name="o2_sensor3_voltage",
320
+ description="O2 Sensor 3 Voltage",
321
+ formula=lambda data: data[1] / 200,
322
+ unit="V",
323
+ min_value=0,
324
+ max_value=1.275,
325
+ ),
326
+ 0x17: PID(
327
+ mode=1,
328
+ pid=0x17,
329
+ name="o2_sensor4_voltage",
330
+ description="O2 Sensor 4 Voltage",
331
+ formula=lambda data: data[1] / 200,
332
+ unit="V",
333
+ min_value=0,
334
+ max_value=1.275,
335
+ ),
336
+ # Oxygen sensors (Bank 2)
337
+ 0x18: PID(
338
+ mode=1,
339
+ pid=0x18,
340
+ name="o2_sensor5_voltage",
341
+ description="O2 Sensor 5 Voltage",
342
+ formula=lambda data: data[1] / 200,
343
+ unit="V",
344
+ min_value=0,
345
+ max_value=1.275,
346
+ ),
347
+ 0x19: PID(
348
+ mode=1,
349
+ pid=0x19,
350
+ name="o2_sensor6_voltage",
351
+ description="O2 Sensor 6 Voltage",
352
+ formula=lambda data: data[1] / 200,
353
+ unit="V",
354
+ min_value=0,
355
+ max_value=1.275,
356
+ ),
357
+ 0x1A: PID(
358
+ mode=1,
359
+ pid=0x1A,
360
+ name="o2_sensor7_voltage",
361
+ description="O2 Sensor 7 Voltage",
362
+ formula=lambda data: data[1] / 200,
363
+ unit="V",
364
+ min_value=0,
365
+ max_value=1.275,
366
+ ),
367
+ 0x1B: PID(
368
+ mode=1,
369
+ pid=0x1B,
370
+ name="o2_sensor8_voltage",
371
+ description="O2 Sensor 8 Voltage",
372
+ formula=lambda data: data[1] / 200,
373
+ unit="V",
374
+ min_value=0,
375
+ max_value=1.275,
376
+ ),
377
+ 0x1F: PID(
378
+ mode=1,
379
+ pid=0x1F,
380
+ name="run_time_since_engine_start",
381
+ description="Run Time Since Engine Start",
382
+ formula=lambda data: 256 * data[1] + data[2],
383
+ unit="s",
384
+ min_value=0,
385
+ max_value=65535,
386
+ ),
387
+ 0x21: PID(
388
+ mode=1,
389
+ pid=0x21,
390
+ name="distance_traveled_with_mil_on",
391
+ description="Distance Traveled with MIL On",
392
+ formula=lambda data: 256 * data[1] + data[2],
393
+ unit="km",
394
+ min_value=0,
395
+ max_value=65535,
396
+ ),
397
+ 0x2C: PID(
398
+ mode=1,
399
+ pid=0x2C,
400
+ name="commanded_egr",
401
+ description="Commanded EGR",
402
+ formula=lambda data: data[1] * 100.0 / 255,
403
+ unit="%",
404
+ min_value=0,
405
+ max_value=100,
406
+ ),
407
+ 0x2D: PID(
408
+ mode=1,
409
+ pid=0x2D,
410
+ name="egr_error",
411
+ description="EGR Error",
412
+ formula=lambda data: (data[1] - 128) * 100.0 / 128,
413
+ unit="%",
414
+ min_value=-100,
415
+ max_value=99.22,
416
+ ),
417
+ 0x2F: PID(
418
+ mode=1,
419
+ pid=0x2F,
420
+ name="fuel_level",
421
+ description="Fuel Tank Level Input",
422
+ formula=lambda data: data[1] * 100.0 / 255,
423
+ unit="%",
424
+ min_value=0,
425
+ max_value=100,
426
+ ),
427
+ 0x33: PID(
428
+ mode=1,
429
+ pid=0x33,
430
+ name="absolute_barometric_pressure",
431
+ description="Absolute Barometric Pressure",
432
+ formula=lambda data: data[1],
433
+ unit="kPa",
434
+ min_value=0,
435
+ max_value=255,
436
+ ),
437
+ # Catalyst temperatures
438
+ 0x3C: PID(
439
+ mode=1,
440
+ pid=0x3C,
441
+ name="catalyst_temp_b1s1",
442
+ description="Catalyst Temperature - Bank 1, Sensor 1",
443
+ formula=lambda data: (256 * data[1] + data[2]) / 10 - 40,
444
+ unit="°C",
445
+ min_value=-40,
446
+ max_value=6513.5,
447
+ ),
448
+ 0x3D: PID(
449
+ mode=1,
450
+ pid=0x3D,
451
+ name="catalyst_temp_b2s1",
452
+ description="Catalyst Temperature - Bank 2, Sensor 1",
453
+ formula=lambda data: (256 * data[1] + data[2]) / 10 - 40,
454
+ unit="°C",
455
+ min_value=-40,
456
+ max_value=6513.5,
457
+ ),
458
+ 0x3E: PID(
459
+ mode=1,
460
+ pid=0x3E,
461
+ name="catalyst_temp_b1s2",
462
+ description="Catalyst Temperature - Bank 1, Sensor 2",
463
+ formula=lambda data: (256 * data[1] + data[2]) / 10 - 40,
464
+ unit="°C",
465
+ min_value=-40,
466
+ max_value=6513.5,
467
+ ),
468
+ 0x3F: PID(
469
+ mode=1,
470
+ pid=0x3F,
471
+ name="catalyst_temp_b2s2",
472
+ description="Catalyst Temperature - Bank 2, Sensor 2",
473
+ formula=lambda data: (256 * data[1] + data[2]) / 10 - 40,
474
+ unit="°C",
475
+ min_value=-40,
476
+ max_value=6513.5,
477
+ ),
478
+ 0x42: PID(
479
+ mode=1,
480
+ pid=0x42,
481
+ name="control_module_voltage",
482
+ description="Control Module Voltage",
483
+ formula=lambda data: (256 * data[1] + data[2]) / 1000,
484
+ unit="V",
485
+ min_value=0,
486
+ max_value=65.535,
487
+ ),
488
+ 0x43: PID(
489
+ mode=1,
490
+ pid=0x43,
491
+ name="absolute_load_value",
492
+ description="Absolute Load Value",
493
+ formula=lambda data: (256 * data[1] + data[2]) * 100.0 / 255,
494
+ unit="%",
495
+ min_value=0,
496
+ max_value=25700,
497
+ ),
498
+ 0x44: PID(
499
+ mode=1,
500
+ pid=0x44,
501
+ name="commanded_equivalence_ratio",
502
+ description="Commanded Equivalence Ratio",
503
+ formula=lambda data: (256 * data[1] + data[2]) / 32768,
504
+ unit="ratio",
505
+ min_value=0,
506
+ max_value=2,
507
+ ),
508
+ 0x45: PID(
509
+ mode=1,
510
+ pid=0x45,
511
+ name="relative_throttle_position",
512
+ description="Relative Throttle Position",
513
+ formula=lambda data: data[1] * 100.0 / 255,
514
+ unit="%",
515
+ min_value=0,
516
+ max_value=100,
517
+ ),
518
+ 0x46: PID(
519
+ mode=1,
520
+ pid=0x46,
521
+ name="ambient_air_temp",
522
+ description="Ambient Air Temperature",
523
+ formula=lambda data: data[1] - 40,
524
+ unit="°C",
525
+ min_value=-40,
526
+ max_value=215,
527
+ ),
528
+ 0x47: PID(
529
+ mode=1,
530
+ pid=0x47,
531
+ name="absolute_throttle_position_b",
532
+ description="Absolute Throttle Position B",
533
+ formula=lambda data: data[1] * 100.0 / 255,
534
+ unit="%",
535
+ min_value=0,
536
+ max_value=100,
537
+ ),
538
+ 0x48: PID(
539
+ mode=1,
540
+ pid=0x48,
541
+ name="absolute_throttle_position_c",
542
+ description="Absolute Throttle Position C",
543
+ formula=lambda data: data[1] * 100.0 / 255,
544
+ unit="%",
545
+ min_value=0,
546
+ max_value=100,
547
+ ),
548
+ 0x49: PID(
549
+ mode=1,
550
+ pid=0x49,
551
+ name="accelerator_pedal_position_d",
552
+ description="Accelerator Pedal Position D",
553
+ formula=lambda data: data[1] * 100.0 / 255,
554
+ unit="%",
555
+ min_value=0,
556
+ max_value=100,
557
+ ),
558
+ 0x4A: PID(
559
+ mode=1,
560
+ pid=0x4A,
561
+ name="accelerator_pedal_position_e",
562
+ description="Accelerator Pedal Position E",
563
+ formula=lambda data: data[1] * 100.0 / 255,
564
+ unit="%",
565
+ min_value=0,
566
+ max_value=100,
567
+ ),
568
+ 0x4C: PID(
569
+ mode=1,
570
+ pid=0x4C,
571
+ name="commanded_throttle_actuator",
572
+ description="Commanded Throttle Actuator",
573
+ formula=lambda data: data[1] * 100.0 / 255,
574
+ unit="%",
575
+ min_value=0,
576
+ max_value=100,
577
+ ),
578
+ 0x51: PID(
579
+ mode=1,
580
+ pid=0x51,
581
+ name="fuel_type",
582
+ description="Fuel Type",
583
+ formula=lambda data: data[1],
584
+ unit="type",
585
+ min_value=0,
586
+ max_value=23,
587
+ ),
588
+ 0x52: PID(
589
+ mode=1,
590
+ pid=0x52,
591
+ name="ethanol_fuel_percentage",
592
+ description="Ethanol Fuel Percentage",
593
+ formula=lambda data: data[1] * 100.0 / 255,
594
+ unit="%",
595
+ min_value=0,
596
+ max_value=100,
597
+ ),
598
+ 0x5A: PID(
599
+ mode=1,
600
+ pid=0x5A,
601
+ name="relative_accelerator_pedal_position",
602
+ description="Relative Accelerator Pedal Position",
603
+ formula=lambda data: data[1] * 100.0 / 255,
604
+ unit="%",
605
+ min_value=0,
606
+ max_value=100,
607
+ ),
608
+ 0x5C: PID(
609
+ mode=1,
610
+ pid=0x5C,
611
+ name="engine_oil_temp",
612
+ description="Engine Oil Temperature",
613
+ formula=lambda data: data[1] - 40,
614
+ unit="°C",
615
+ min_value=-40,
616
+ max_value=215,
617
+ ),
618
+ }
619
+
620
+ @staticmethod
621
+ def is_obd2_request(message: CANMessage) -> bool:
622
+ """Check if message is an OBD-II request.
623
+
624
+ OBD-II requests are typically sent to ID 0x7DF (broadcast)
625
+ or 0x7E0-0x7E7 (specific ECU).
626
+
627
+ Args:
628
+ message: CAN message to check.
629
+
630
+ Returns:
631
+ True if message appears to be OBD-II request.
632
+ """
633
+ if message.arbitration_id == 0x7DF:
634
+ return True
635
+ return 0x7E0 <= message.arbitration_id <= 0x7E7
636
+
637
+ @staticmethod
638
+ def is_obd2_response(message: CANMessage) -> bool:
639
+ """Check if message is an OBD-II response.
640
+
641
+ OBD-II responses are typically sent from ID 0x7E8-0x7EF.
642
+
643
+ Args:
644
+ message: CAN message to check.
645
+
646
+ Returns:
647
+ True if message appears to be OBD-II response.
648
+ """
649
+ return 0x7E8 <= message.arbitration_id <= 0x7EF
650
+
651
+ @staticmethod
652
+ def decode(message: CANMessage) -> OBD2Response | None:
653
+ """Decode an OBD-II response message.
654
+
655
+ Args:
656
+ message: CAN message (must be OBD-II response).
657
+
658
+ Returns:
659
+ OBD2Response if successfully decoded, None otherwise.
660
+ """
661
+ if not OBD2Decoder.is_obd2_response(message):
662
+ return None
663
+
664
+ if len(message.data) < 4:
665
+ return None
666
+
667
+ # OBD-II response format:
668
+ # Byte 0: Number of additional bytes
669
+ # Byte 1: Mode + 0x40 (e.g., 0x41 for Mode 01 response)
670
+ # Byte 2: PID
671
+ # Byte 3+: Data
672
+
673
+ mode_response = message.data[1]
674
+ pid = message.data[2]
675
+
676
+ # Verify mode response (should be mode + 0x40)
677
+ if mode_response < 0x40:
678
+ return None
679
+
680
+ mode = mode_response - 0x40
681
+
682
+ # Only support Mode 01 for now
683
+ if mode != 1:
684
+ return None
685
+
686
+ # Look up PID
687
+ if pid not in OBD2Decoder.PIDS:
688
+ return None
689
+
690
+ pid_def = OBD2Decoder.PIDS[pid]
691
+
692
+ # Decode value
693
+ # Pass data starting from PID byte so formulas can use correct indices
694
+ # Formula expects: [PID, data_byte_0, data_byte_1, ...]
695
+ try:
696
+ value = pid_def.formula(message.data[2:])
697
+ except Exception:
698
+ return None
699
+
700
+ return OBD2Response(
701
+ mode=mode,
702
+ pid=pid,
703
+ name=pid_def.name,
704
+ value=value,
705
+ unit=pid_def.unit,
706
+ timestamp=message.timestamp,
707
+ )