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,745 @@
1
+ """J1939 protocol decoder.
2
+
3
+ This module implements J1939 (SAE J1939) protocol decoding for heavy-duty
4
+ vehicles including PGN extraction and common parameter decoding.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from typing import TYPE_CHECKING, ClassVar
11
+
12
+ if TYPE_CHECKING:
13
+ from oscura.automotive.can.models import CANMessage
14
+
15
+ __all__ = ["J1939Decoder", "J1939Message", "extract_pgn"]
16
+
17
+
18
+ @dataclass
19
+ class J1939Message:
20
+ """Decoded J1939 message.
21
+
22
+ Attributes:
23
+ pgn: Parameter Group Number.
24
+ priority: Message priority (0-7).
25
+ source_address: Source address.
26
+ destination_address: Destination address (0xFF for broadcast).
27
+ data: Message data bytes.
28
+ timestamp: Message timestamp.
29
+ """
30
+
31
+ pgn: int
32
+ priority: int
33
+ source_address: int
34
+ destination_address: int
35
+ data: bytes
36
+ timestamp: float
37
+
38
+
39
+ def extract_pgn(can_id: int) -> tuple[int, int, int, int]:
40
+ """Extract J1939 components from 29-bit CAN ID.
41
+
42
+ J1939 uses extended 29-bit CAN IDs with this structure:
43
+ - Priority (bits 26-28): 3 bits
44
+ - Reserved (bit 25): 1 bit
45
+ - Data Page (bit 24): 1 bit
46
+ - PDU Format (bits 16-23): 8 bits
47
+ - PDU Specific (bits 8-15): 8 bits
48
+ - Source Address (bits 0-7): 8 bits
49
+
50
+ Args:
51
+ can_id: 29-bit extended CAN ID.
52
+
53
+ Returns:
54
+ Tuple of (pgn, priority, destination_address, source_address).
55
+ """
56
+ # Extract components
57
+ priority = (can_id >> 26) & 0x7
58
+ # reserved = (can_id >> 25) & 0x1 # Not used in PGN calculation
59
+ data_page = (can_id >> 24) & 0x1
60
+ pdu_format = (can_id >> 16) & 0xFF
61
+ pdu_specific = (can_id >> 8) & 0xFF
62
+ source_address = can_id & 0xFF
63
+
64
+ # Calculate PGN
65
+ # If PDU Format < 240, PDU Specific is Destination Address
66
+ # If PDU Format >= 240, PDU Specific is Group Extension
67
+ if pdu_format < 240:
68
+ # PDU1 format - destination-specific
69
+ pgn = (data_page << 16) | (pdu_format << 8)
70
+ destination_address = pdu_specific
71
+ else:
72
+ # PDU2 format - broadcast
73
+ pgn = (data_page << 16) | (pdu_format << 8) | pdu_specific
74
+ destination_address = 0xFF # Broadcast
75
+
76
+ return pgn, priority, destination_address, source_address
77
+
78
+
79
+ class J1939Decoder:
80
+ """J1939 protocol decoder.
81
+
82
+ Decodes J1939 messages from extended CAN frames with support for
83
+ 100+ Parameter Group Numbers (PGNs) and signal extraction.
84
+ """
85
+
86
+ # Comprehensive PGN names (100+ common PGNs)
87
+ PGN_NAMES: ClassVar[dict[int, str]] = {
88
+ # Engine Parameters - Core (61440-61695, 0xF000-0xF0FF)
89
+ 0xF000: "Electronic Retarder Controller 1 (ERC1)",
90
+ 0xF001: "Electronic Brake Controller 1 (EBC1)",
91
+ 0xF002: "Electronic Transmission Controller 1 (ETC1)",
92
+ 0xF003: "Electronic Engine Controller 2 (EEC2)",
93
+ 0xF004: "Electronic Engine Controller 1 (EEC1)",
94
+ 0xF005: "Electronic Transmission Controller 2 (ETC2)",
95
+ 0xF009: "Vehicle Dynamic Stability Control 2 (VDC2)",
96
+ 0xF010: "Aftertreatment 1 Intake Gas 1 (AT1IG1)",
97
+ 0xF011: "Aftertreatment 1 Outlet Gas 1 (AT1OG1)",
98
+ # Engine Fluid Levels & Temperatures (65248-65279, 0xFEE0-0xFEFF)
99
+ 0xFEE0: "Electronic Engine Controller 3 (EEC3)",
100
+ 0xFEE5: "Engine Hours, Revolutions",
101
+ 0xFEE6: "Time/Date",
102
+ 0xFEE7: "Vehicle Hours",
103
+ 0xFEE8: "Vehicle Direction/Speed",
104
+ 0xFEE9: "Vehicle Weight",
105
+ 0xFEEA: "Vehicle Identification",
106
+ 0xFEEB: "Component Identification",
107
+ 0xFEEC: "Vehicle Distance",
108
+ 0xFEED: "Shutdown",
109
+ 0xFEEE: "Engine Temperature 1",
110
+ 0xFEEF: "Engine Fluid Level/Pressure 1",
111
+ 0xFEF0: "Power Takeoff Information",
112
+ 0xFEF1: "Cruise Control/Vehicle Speed 1 (CCVS1)",
113
+ 0xFEF2: "Fuel Economy (Liquid)",
114
+ 0xFEF3: "Engine Configuration 1",
115
+ 0xFEF4: "Fuel Consumption (Gaseous)",
116
+ 0xFEF5: "Ambient Conditions",
117
+ 0xFEF6: "Inlet/Exhaust Conditions 1",
118
+ 0xFEF7: "Vehicle Electrical Power 1",
119
+ 0xFEF8: "Transmission Fluids 1",
120
+ 0xFEF9: "Air Supply Pressure",
121
+ 0xFEFA: "Vehicle Weight",
122
+ 0xFEFB: "Engine Speed/Load Factor",
123
+ 0xFEFC: "Fan Drive",
124
+ 0xFEFD: "Vehicle Position",
125
+ 0xFEFE: "Engine Temperature 2",
126
+ 0xFEFF: "Electronic Engine Controller 4 (EEC4)",
127
+ # Transmission & Drivetrain (65184-65247, 0xFEC0-0xFEDF)
128
+ 0xFEC0: "Transmission Configuration",
129
+ 0xFEC1: "High Resolution Vehicle Distance",
130
+ 0xFEC2: "High Resolution Fuel Consumption (Liquid)",
131
+ 0xFEC3: "High Resolution Fuel Economy (Liquid)",
132
+ 0xFEC5: "Aftertreatment 1 Diesel Exhaust Fluid Tank 1 Info",
133
+ 0xFEC6: "Aftertreatment 1 Diesel Oxidation Catalyst 1",
134
+ 0xFEC7: "Aftertreatment 1 Diesel Particulate Filter 1",
135
+ 0xFEC8: "Aftertreatment 1 Intake Gas",
136
+ 0xFEC9: "Aftertreatment 1 Outlet Gas",
137
+ 0xFECA: "DM1 - Active Diagnostic Trouble Codes",
138
+ 0xFECB: "DM2 - Previously Active Diagnostic Trouble Codes",
139
+ 0xFECC: "DM3 - Diagnostic Data Clear/Reset Previously Active DTCs",
140
+ 0xFECD: "DM4 - Freeze Frame Parameters",
141
+ 0xFECE: "DM5 - Diagnostic Readiness 1",
142
+ 0xFECF: "DM6 - Emission-Related Pending DTCs",
143
+ 0xFED0: "DM7 - Command Non-Continuously Monitored Test",
144
+ 0xFED1: "DM8 - Test Results",
145
+ 0xFED2: "DM9 - Request Test Results",
146
+ 0xFED3: "DM11 - Diagnostic Data Clear/Reset Active DTCs",
147
+ 0xFED4: "DM12 - Emissions-Related Active DTCs",
148
+ 0xFED5: "DM13 - Stop Start Broadcast",
149
+ 0xFED6: "DM14 - Memory Access Response",
150
+ 0xFED7: "DM15 - Memory Access",
151
+ 0xFED8: "DM16 - Binary Data Transfer",
152
+ 0xFED9: "DM17 - Binary Data Transfer Response",
153
+ 0xFEDA: "DM18 - Binary Data Transfer",
154
+ 0xFEDB: "DM19 - Calibration Information",
155
+ 0xFEDC: "DM20 - Monitor Performance Ratio",
156
+ 0xFEDD: "DM21 - Diagnostic Readiness 2",
157
+ 0xFEDE: "DM22 - Individual Clear/Reset Active & Previously Active DTC",
158
+ 0xFEDF: "DM23 - Emission-Related Previously Active DTC",
159
+ # Brake & Wheels (65120-65183, 0xFE80-0xFEBF)
160
+ 0xFE80: "Tire Condition",
161
+ 0xFE81: "Tire Pressure",
162
+ 0xFE82: "Tire Temperature",
163
+ 0xFE83: "Tire Pressure Control Unit",
164
+ 0xFE90: "Hydraulic Pressure 1",
165
+ 0xFE91: "Hydraulic Pressure 2",
166
+ 0xFE92: "Fuel Consumption (Liquid) 1",
167
+ 0xFE93: "Fuel Economy (Liquid) 1",
168
+ 0xFEA0: "Axle Information",
169
+ 0xFEA1: "Engine Torque/Speed",
170
+ 0xFEA2: "Electronic Transmission Controller 3 (ETC3)",
171
+ 0xFEA3: "Electronic Transmission Controller 4 (ETC4)",
172
+ 0xFEA4: "Electronic Transmission Controller 5 (ETC5)",
173
+ 0xFEA5: "Electronic Engine Controller 5 (EEC5)",
174
+ 0xFEA6: "Electronic Engine Controller 6 (EEC6)",
175
+ 0xFEA7: "Electronic Engine Controller 7 (EEC7)",
176
+ 0xFEB0: "Axle Weight",
177
+ 0xFEB1: "Trailer Weight",
178
+ 0xFEB2: "Cargo Weight",
179
+ 0xFEB3: "Trip Fuel Economy (Liquid)",
180
+ 0xFEB4: "Trip Fuel (Liquid)",
181
+ 0xFEB5: "Trip Time",
182
+ 0xFEB6: "Trip Shutdown Information",
183
+ 0xFEB7: "Fuel Level 1",
184
+ 0xFEB8: "Fuel Level 2",
185
+ 0xFEB9: "Auxiliary Water Pump Pressure",
186
+ 0xFEBA: "Coolant Filter Differential Pressure",
187
+ 0xFEBB: "Engine Exhaust Gas Recirculation 1 (EGR1)",
188
+ 0xFEBC: "Engine Exhaust Gas Recirculation 2 (EGR2)",
189
+ 0xFEBD: "Engine Exhaust Gas Recirculation 3 (EGR3)",
190
+ 0xFEBE: "Aftertreatment 1 Diesel Particulate Filter 2",
191
+ 0xFEBF: "Wheel Speed Information",
192
+ # Aftertreatment & Emissions (65024-65119, 0xFE40-0xFE7F)
193
+ 0xFE40: "Aftertreatment 1 SCR Exhaust Gas Temperature 1",
194
+ 0xFE41: "Aftertreatment 1 SCR Dosing System Information 1",
195
+ 0xFE42: "Aftertreatment 1 Intake NOx",
196
+ 0xFE43: "Aftertreatment 1 Outlet NOx",
197
+ 0xFE44: "Aftertreatment 2 Intake Gas 1",
198
+ 0xFE45: "Aftertreatment 2 Outlet Gas 1",
199
+ 0xFE46: "Aftertreatment 2 SCR Exhaust Gas Temperature 1",
200
+ 0xFE47: "Aftertreatment 2 SCR Dosing System Information 1",
201
+ 0xFE48: "Aftertreatment 2 Intake NOx",
202
+ 0xFE49: "Aftertreatment 2 Outlet NOx",
203
+ 0xFE4A: "Fuel Information 1 (Liquid)",
204
+ 0xFE4B: "Fuel Information 2 (Liquid)",
205
+ 0xFE4C: "Fuel Information 3 (Liquid)",
206
+ 0xFE4D: "Engine Gas Flow Rate",
207
+ 0xFE4E: "Engine Throttle Valve 1",
208
+ 0xFE4F: "Engine Throttle Valve 2",
209
+ 0xFE50: "Aftertreatment 1 Diesel Particulate Filter 3",
210
+ 0xFE51: "Aftertreatment 1 Diesel Particulate Filter 4",
211
+ 0xFE52: "Aftertreatment 1 SCR Dosing System Requests",
212
+ 0xFE53: "Aftertreatment 1 Fuel Control 1",
213
+ 0xFE54: "Aftertreatment 1 Fuel Control 2",
214
+ 0xFE55: "Aftertreatment 2 Diesel Particulate Filter 1",
215
+ 0xFE56: "Aftertreatment 1 Diesel Exhaust Fluid Tank 1 Information",
216
+ 0xFE57: "Aftertreatment 2 Diesel Exhaust Fluid Tank 1 Information",
217
+ 0xFE58: "Fuel Information (Gaseous)",
218
+ 0xFE59: "Aftertreatment 1 Air Control 1",
219
+ 0xFE5A: "Aftertreatment 2 Air Control 1",
220
+ 0xFE5B: "Aftertreatment 1 Diesel Particulate Filter Control 1",
221
+ 0xFE5C: "Aftertreatment 2 Diesel Particulate Filter Control 1",
222
+ # Cab Climate & Lighting (64256-64511, 0xFB00-0xFBFF)
223
+ 0xFB00: "Cab Climate Control Status 1",
224
+ 0xFB01: "Cab Climate Control Status 2",
225
+ 0xFB02: "Cab Climate Control Command 1",
226
+ 0xFB03: "Cab Climate Control Command 2",
227
+ # Additional Common PGNs
228
+ 0xC100: "DM1 - Active Diagnostic Trouble Codes (Request)",
229
+ 0xC200: "DM13 - Stop Start Broadcast (Request)",
230
+ 0xC300: "DM2 - Previously Active DTCs (Request)",
231
+ 0xFF00: "Aftertreatment 1 Diesel Particulate Filter Control",
232
+ # Proprietary PGNs (common ranges)
233
+ 61184: "Electronic Retarder Controller 1 (ERC1)",
234
+ 61185: "Electronic Brake Controller 2 (EBC2)",
235
+ 61186: "Electronic Transmission Controller 1 (ETC1)",
236
+ 61187: "Electronic Engine Controller 2 (EEC2)",
237
+ 61188: "Electronic Engine Controller 1 (EEC1)",
238
+ 61189: "Electronic Transmission Controller 2 (ETC2)",
239
+ 61190: "Turbocharger 1",
240
+ 61191: "Turbocharger 2",
241
+ 61192: "Air Intake Conditions",
242
+ 61193: "Exhaust Gas Recirculation",
243
+ 61194: "Fuel System",
244
+ 61195: "Alternator Information",
245
+ 61196: "Intake Manifold Temperature 1",
246
+ 61197: "Exhaust Port Temperature",
247
+ 61198: "Engine Oil Information",
248
+ 61199: "Engine Coolant Information",
249
+ 61200: "Fuel Delivery Information",
250
+ }
251
+
252
+ # Signal definitions for common PGNs
253
+ # Format: {pgn: {signal_name: {start_byte, bit_offset, length_bits, scale, offset, unit}}}
254
+ PGN_SIGNALS: ClassVar[dict[int, dict[str, dict[str, int | float | str]]]] = {
255
+ 0xF004: { # Electronic Engine Controller 1 (EEC1)
256
+ "engine_torque_mode": {
257
+ "byte": 0,
258
+ "bit": 0,
259
+ "length": 4,
260
+ "scale": 1,
261
+ "offset": 0,
262
+ "unit": "",
263
+ },
264
+ "driver_demand_torque": {
265
+ "byte": 1,
266
+ "bit": 0,
267
+ "length": 8,
268
+ "scale": 1,
269
+ "offset": -125,
270
+ "unit": "%",
271
+ },
272
+ "actual_engine_torque": {
273
+ "byte": 2,
274
+ "bit": 0,
275
+ "length": 8,
276
+ "scale": 1,
277
+ "offset": -125,
278
+ "unit": "%",
279
+ },
280
+ "engine_speed": {
281
+ "byte": 3,
282
+ "bit": 0,
283
+ "length": 16,
284
+ "scale": 0.125,
285
+ "offset": 0,
286
+ "unit": "rpm",
287
+ },
288
+ "source_address": {
289
+ "byte": 5,
290
+ "bit": 0,
291
+ "length": 8,
292
+ "scale": 1,
293
+ "offset": 0,
294
+ "unit": "",
295
+ },
296
+ },
297
+ 0xF003: { # Electronic Engine Controller 2 (EEC2)
298
+ "accelerator_pedal_position": {
299
+ "byte": 1,
300
+ "bit": 0,
301
+ "length": 8,
302
+ "scale": 0.4,
303
+ "offset": 0,
304
+ "unit": "%",
305
+ },
306
+ "engine_percent_load": {
307
+ "byte": 2,
308
+ "bit": 0,
309
+ "length": 8,
310
+ "scale": 1,
311
+ "offset": 0,
312
+ "unit": "%",
313
+ },
314
+ },
315
+ 0xFEF1: { # Cruise Control/Vehicle Speed 1
316
+ "wheel_based_speed": {
317
+ "byte": 1,
318
+ "bit": 0,
319
+ "length": 16,
320
+ "scale": 1 / 256,
321
+ "offset": 0,
322
+ "unit": "km/h",
323
+ },
324
+ "cruise_control_active": {
325
+ "byte": 3,
326
+ "bit": 0,
327
+ "length": 2,
328
+ "scale": 1,
329
+ "offset": 0,
330
+ "unit": "",
331
+ },
332
+ "brake_switch": {
333
+ "byte": 3,
334
+ "bit": 2,
335
+ "length": 2,
336
+ "scale": 1,
337
+ "offset": 0,
338
+ "unit": "",
339
+ },
340
+ "clutch_switch": {
341
+ "byte": 3,
342
+ "bit": 4,
343
+ "length": 2,
344
+ "scale": 1,
345
+ "offset": 0,
346
+ "unit": "",
347
+ },
348
+ },
349
+ 0xFEEE: { # Engine Temperature 1
350
+ "coolant_temperature": {
351
+ "byte": 0,
352
+ "bit": 0,
353
+ "length": 8,
354
+ "scale": 1,
355
+ "offset": -40,
356
+ "unit": "°C",
357
+ },
358
+ "fuel_temperature": {
359
+ "byte": 1,
360
+ "bit": 0,
361
+ "length": 8,
362
+ "scale": 1,
363
+ "offset": -40,
364
+ "unit": "°C",
365
+ },
366
+ "oil_temperature": {
367
+ "byte": 2,
368
+ "bit": 0,
369
+ "length": 16,
370
+ "scale": 0.03125,
371
+ "offset": -273,
372
+ "unit": "°C",
373
+ },
374
+ "turbo_oil_temperature": {
375
+ "byte": 4,
376
+ "bit": 0,
377
+ "length": 16,
378
+ "scale": 0.03125,
379
+ "offset": -273,
380
+ "unit": "°C",
381
+ },
382
+ },
383
+ 0xFEEF: { # Engine Fluid Level/Pressure 1
384
+ "fuel_delivery_pressure": {
385
+ "byte": 0,
386
+ "bit": 0,
387
+ "length": 8,
388
+ "scale": 4,
389
+ "offset": 0,
390
+ "unit": "kPa",
391
+ },
392
+ "oil_pressure": {
393
+ "byte": 3,
394
+ "bit": 0,
395
+ "length": 8,
396
+ "scale": 4,
397
+ "offset": 0,
398
+ "unit": "kPa",
399
+ },
400
+ "crankcase_pressure": {
401
+ "byte": 5,
402
+ "bit": 0,
403
+ "length": 16,
404
+ "scale": 0.125,
405
+ "offset": -250,
406
+ "unit": "kPa",
407
+ },
408
+ "coolant_pressure": {
409
+ "byte": 7,
410
+ "bit": 0,
411
+ "length": 8,
412
+ "scale": 2,
413
+ "offset": 0,
414
+ "unit": "kPa",
415
+ },
416
+ },
417
+ 0xFEF2: { # Fuel Economy (Liquid)
418
+ "fuel_rate": {
419
+ "byte": 0,
420
+ "bit": 0,
421
+ "length": 16,
422
+ "scale": 0.05,
423
+ "offset": 0,
424
+ "unit": "L/h",
425
+ },
426
+ "instantaneous_fuel_economy": {
427
+ "byte": 2,
428
+ "bit": 0,
429
+ "length": 16,
430
+ "scale": 1 / 512,
431
+ "offset": 0,
432
+ "unit": "km/L",
433
+ },
434
+ "average_fuel_economy": {
435
+ "byte": 4,
436
+ "bit": 0,
437
+ "length": 16,
438
+ "scale": 1 / 512,
439
+ "offset": 0,
440
+ "unit": "km/L",
441
+ },
442
+ },
443
+ 0xFEF5: { # Ambient Conditions
444
+ "barometric_pressure": {
445
+ "byte": 0,
446
+ "bit": 0,
447
+ "length": 8,
448
+ "scale": 0.5,
449
+ "offset": 0,
450
+ "unit": "kPa",
451
+ },
452
+ "ambient_air_temperature": {
453
+ "byte": 3,
454
+ "bit": 0,
455
+ "length": 16,
456
+ "scale": 0.03125,
457
+ "offset": -273,
458
+ "unit": "°C",
459
+ },
460
+ "ambient_air_humidity": {
461
+ "byte": 6,
462
+ "bit": 0,
463
+ "length": 8,
464
+ "scale": 0.4,
465
+ "offset": 0,
466
+ "unit": "%",
467
+ },
468
+ },
469
+ 0xFEF7: { # Vehicle Electrical Power 1
470
+ "battery_potential": {
471
+ "byte": 4,
472
+ "bit": 0,
473
+ "length": 16,
474
+ "scale": 0.05,
475
+ "offset": 0,
476
+ "unit": "V",
477
+ },
478
+ "alternator_current": {
479
+ "byte": 6,
480
+ "bit": 0,
481
+ "length": 16,
482
+ "scale": 1,
483
+ "offset": -125,
484
+ "unit": "A",
485
+ },
486
+ },
487
+ 0xFEE5: { # Engine Hours, Revolutions
488
+ "total_engine_hours": {
489
+ "byte": 0,
490
+ "bit": 0,
491
+ "length": 32,
492
+ "scale": 0.05,
493
+ "offset": 0,
494
+ "unit": "hours",
495
+ },
496
+ "total_engine_revolutions": {
497
+ "byte": 4,
498
+ "bit": 0,
499
+ "length": 32,
500
+ "scale": 1000,
501
+ "offset": 0,
502
+ "unit": "revolutions",
503
+ },
504
+ },
505
+ 0xFECA: { # DM1 - Active Diagnostic Trouble Codes
506
+ "lamp_status_malfunction_indicator": {
507
+ "byte": 0,
508
+ "bit": 0,
509
+ "length": 2,
510
+ "scale": 1,
511
+ "offset": 0,
512
+ "unit": "",
513
+ },
514
+ "lamp_status_red_stop": {
515
+ "byte": 0,
516
+ "bit": 2,
517
+ "length": 2,
518
+ "scale": 1,
519
+ "offset": 0,
520
+ "unit": "",
521
+ },
522
+ "lamp_status_amber_warning": {
523
+ "byte": 0,
524
+ "bit": 4,
525
+ "length": 2,
526
+ "scale": 1,
527
+ "offset": 0,
528
+ "unit": "",
529
+ },
530
+ "lamp_status_protect": {
531
+ "byte": 0,
532
+ "bit": 6,
533
+ "length": 2,
534
+ "scale": 1,
535
+ "offset": 0,
536
+ "unit": "",
537
+ },
538
+ },
539
+ 0xFEC1: { # High Resolution Vehicle Distance
540
+ "high_resolution_total_distance": {
541
+ "byte": 0,
542
+ "bit": 0,
543
+ "length": 32,
544
+ "scale": 5,
545
+ "offset": 0,
546
+ "unit": "m",
547
+ },
548
+ "high_resolution_trip_distance": {
549
+ "byte": 4,
550
+ "bit": 0,
551
+ "length": 32,
552
+ "scale": 5,
553
+ "offset": 0,
554
+ "unit": "m",
555
+ },
556
+ },
557
+ 0xFEBF: { # Wheel Speed Information
558
+ "front_axle_speed": {
559
+ "byte": 0,
560
+ "bit": 0,
561
+ "length": 16,
562
+ "scale": 1 / 256,
563
+ "offset": 0,
564
+ "unit": "km/h",
565
+ },
566
+ "rear_axle_1_speed": {
567
+ "byte": 2,
568
+ "bit": 0,
569
+ "length": 16,
570
+ "scale": 1 / 256,
571
+ "offset": 0,
572
+ "unit": "km/h",
573
+ },
574
+ },
575
+ }
576
+
577
+ @staticmethod
578
+ def is_j1939(message: CANMessage) -> bool:
579
+ """Check if message uses J1939 protocol.
580
+
581
+ Args:
582
+ message: CAN message to check.
583
+
584
+ Returns:
585
+ True if message appears to be J1939 (extended ID).
586
+ """
587
+ return message.is_extended
588
+
589
+ @staticmethod
590
+ def decode(message: CANMessage) -> J1939Message:
591
+ """Decode J1939 message from CAN frame.
592
+
593
+ Args:
594
+ message: CAN message with extended ID.
595
+
596
+ Returns:
597
+ J1939Message with decoded components.
598
+
599
+ Raises:
600
+ ValueError: If message is not extended frame.
601
+ """
602
+ if not message.is_extended:
603
+ raise ValueError("J1939 requires extended (29-bit) CAN ID")
604
+
605
+ pgn, priority, dest_addr, src_addr = extract_pgn(message.arbitration_id)
606
+
607
+ return J1939Message(
608
+ pgn=pgn,
609
+ priority=priority,
610
+ source_address=src_addr,
611
+ destination_address=dest_addr,
612
+ data=message.data,
613
+ timestamp=message.timestamp,
614
+ )
615
+
616
+ @staticmethod
617
+ def get_pgn_name(pgn: int) -> str:
618
+ """Get name for PGN if known.
619
+
620
+ Args:
621
+ pgn: Parameter Group Number.
622
+
623
+ Returns:
624
+ PGN name or hex string if unknown.
625
+ """
626
+ return J1939Decoder.PGN_NAMES.get(pgn, f"PGN_0x{pgn:05X}")
627
+
628
+ @staticmethod
629
+ def extract_signal(data: bytes, byte_pos: int, bit_pos: int, length_bits: int) -> int:
630
+ """Extract signal value from message data.
631
+
632
+ Args:
633
+ data: Message data bytes.
634
+ byte_pos: Starting byte position (0-indexed).
635
+ bit_pos: Starting bit position within byte (0-7).
636
+ length_bits: Number of bits to extract.
637
+
638
+ Returns:
639
+ Raw signal value as integer.
640
+ """
641
+ if byte_pos >= len(data):
642
+ return 0
643
+
644
+ value = 0
645
+ bits_read = 0
646
+
647
+ # Read bits across multiple bytes if needed
648
+ current_byte = byte_pos
649
+ current_bit = bit_pos
650
+
651
+ while bits_read < length_bits and current_byte < len(data):
652
+ # How many bits to read from current byte
653
+ bits_available = 8 - current_bit
654
+ bits_to_read = min(length_bits - bits_read, bits_available)
655
+
656
+ # Extract bits from current byte
657
+ mask = ((1 << bits_to_read) - 1) << current_bit
658
+ byte_value = (data[current_byte] & mask) >> current_bit
659
+
660
+ # Add to result
661
+ value |= byte_value << bits_read
662
+
663
+ # Move to next byte
664
+ bits_read += bits_to_read
665
+ current_byte += 1
666
+ current_bit = 0
667
+
668
+ return value
669
+
670
+ @staticmethod
671
+ def decode_signal(message: J1939Message, signal_name: str) -> dict[str, float | str | None]:
672
+ """Decode a specific signal from a J1939 message.
673
+
674
+ Args:
675
+ message: Decoded J1939 message.
676
+ signal_name: Name of signal to decode.
677
+
678
+ Returns:
679
+ Dictionary with 'value', 'unit', and 'raw' keys.
680
+ Returns None values if signal or PGN not found.
681
+ """
682
+ # Check if PGN has signal definitions
683
+ if message.pgn not in J1939Decoder.PGN_SIGNALS:
684
+ return {"value": None, "unit": None, "raw": None}
685
+
686
+ signals = J1939Decoder.PGN_SIGNALS[message.pgn]
687
+
688
+ if signal_name not in signals:
689
+ return {"value": None, "unit": None, "raw": None}
690
+
691
+ sig_def = signals[signal_name]
692
+
693
+ # Extract raw value
694
+ byte_val = sig_def["byte"]
695
+ bit_val = sig_def["bit"]
696
+ length_val = sig_def["length"]
697
+ scale_val = sig_def["scale"]
698
+ offset_val = sig_def["offset"]
699
+
700
+ if (
701
+ not isinstance(byte_val, int)
702
+ or not isinstance(bit_val, int)
703
+ or not isinstance(length_val, int)
704
+ ):
705
+ return {"value": None, "unit": None, "raw": None}
706
+ if not isinstance(scale_val, (int, float)) or not isinstance(offset_val, (int, float)):
707
+ return {"value": None, "unit": None, "raw": None}
708
+
709
+ raw_value = J1939Decoder.extract_signal(
710
+ message.data,
711
+ byte_val,
712
+ bit_val,
713
+ length_val,
714
+ )
715
+
716
+ # Apply scaling and offset
717
+ scaled_value = raw_value * scale_val + offset_val
718
+
719
+ return {
720
+ "value": scaled_value,
721
+ "unit": sig_def["unit"],
722
+ "raw": raw_value,
723
+ }
724
+
725
+ @staticmethod
726
+ def decode_all_signals(message: J1939Message) -> dict[str, dict[str, float | str | None]]:
727
+ """Decode all signals from a J1939 message.
728
+
729
+ Args:
730
+ message: Decoded J1939 message.
731
+
732
+ Returns:
733
+ Dictionary mapping signal names to their decoded values.
734
+ Empty dict if PGN has no signal definitions.
735
+ """
736
+ if message.pgn not in J1939Decoder.PGN_SIGNALS:
737
+ return {}
738
+
739
+ signals = J1939Decoder.PGN_SIGNALS[message.pgn]
740
+ result = {}
741
+
742
+ for signal_name in signals:
743
+ result[signal_name] = J1939Decoder.decode_signal(message, signal_name)
744
+
745
+ return result