oscura 0.6.0__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +1 -1
- oscura/automotive/__init__.py +1 -1
- oscura/automotive/dtc/data.json +17 -102
- oscura/core/schemas/device_mapping.json +2 -8
- oscura/core/schemas/packet_format.json +4 -24
- oscura/core/schemas/protocol_definition.json +2 -12
- oscura/loaders/__init__.py +12 -4
- oscura/loaders/tss.py +456 -0
- {oscura-0.6.0.dist-info → oscura-0.7.0.dist-info}/METADATA +37 -19
- {oscura-0.6.0.dist-info → oscura-0.7.0.dist-info}/RECORD +13 -12
- {oscura-0.6.0.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
- {oscura-0.6.0.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.6.0.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
oscura/__init__.py
CHANGED
oscura/automotive/__init__.py
CHANGED
oscura/automotive/dtc/data.json
CHANGED
|
@@ -266,12 +266,7 @@
|
|
|
266
266
|
"category": "Powertrain",
|
|
267
267
|
"severity": "High",
|
|
268
268
|
"system": "Throttle Control",
|
|
269
|
-
"possible_causes": [
|
|
270
|
-
"TPS circuit shorted to voltage",
|
|
271
|
-
"Faulty TPS sensor",
|
|
272
|
-
"Wiring harness open",
|
|
273
|
-
"ECM problem"
|
|
274
|
-
]
|
|
269
|
+
"possible_causes": ["TPS circuit shorted to voltage", "Faulty TPS sensor", "Wiring harness open", "ECM problem"]
|
|
275
270
|
},
|
|
276
271
|
"P0125": {
|
|
277
272
|
"code": "P0125",
|
|
@@ -865,12 +860,7 @@
|
|
|
865
860
|
"category": "Powertrain",
|
|
866
861
|
"severity": "Medium",
|
|
867
862
|
"system": "Emissions Control",
|
|
868
|
-
"possible_causes": [
|
|
869
|
-
"EGR valve stuck closed",
|
|
870
|
-
"EGR passages clogged",
|
|
871
|
-
"Faulty EGR valve",
|
|
872
|
-
"Vacuum leak"
|
|
873
|
-
]
|
|
863
|
+
"possible_causes": ["EGR valve stuck closed", "EGR passages clogged", "Faulty EGR valve", "Vacuum leak"]
|
|
874
864
|
},
|
|
875
865
|
"P0401": {
|
|
876
866
|
"code": "P0401",
|
|
@@ -891,12 +881,7 @@
|
|
|
891
881
|
"category": "Powertrain",
|
|
892
882
|
"severity": "Medium",
|
|
893
883
|
"system": "Emissions Control",
|
|
894
|
-
"possible_causes": [
|
|
895
|
-
"EGR valve stuck open",
|
|
896
|
-
"Faulty EGR valve",
|
|
897
|
-
"EGR vacuum solenoid fault",
|
|
898
|
-
"ECM problem"
|
|
899
|
-
]
|
|
884
|
+
"possible_causes": ["EGR valve stuck open", "Faulty EGR valve", "EGR vacuum solenoid fault", "ECM problem"]
|
|
900
885
|
},
|
|
901
886
|
"P0403": {
|
|
902
887
|
"code": "P0403",
|
|
@@ -958,12 +943,7 @@
|
|
|
958
943
|
"category": "Powertrain",
|
|
959
944
|
"severity": "Low",
|
|
960
945
|
"system": "Emissions Control",
|
|
961
|
-
"possible_causes": [
|
|
962
|
-
"Loose or missing fuel cap",
|
|
963
|
-
"EVAP system leak",
|
|
964
|
-
"Faulty purge valve",
|
|
965
|
-
"Faulty vent valve"
|
|
966
|
-
]
|
|
946
|
+
"possible_causes": ["Loose or missing fuel cap", "EVAP system leak", "Faulty purge valve", "Faulty vent valve"]
|
|
967
947
|
},
|
|
968
948
|
"P0441": {
|
|
969
949
|
"code": "P0441",
|
|
@@ -1075,12 +1055,7 @@
|
|
|
1075
1055
|
"category": "Powertrain",
|
|
1076
1056
|
"severity": "Low",
|
|
1077
1057
|
"system": "Idle Control",
|
|
1078
|
-
"possible_causes": [
|
|
1079
|
-
"Vacuum leak",
|
|
1080
|
-
"IAC valve fault",
|
|
1081
|
-
"Dirty throttle body",
|
|
1082
|
-
"PCV valve problem"
|
|
1083
|
-
]
|
|
1058
|
+
"possible_causes": ["Vacuum leak", "IAC valve fault", "Dirty throttle body", "PCV valve problem"]
|
|
1084
1059
|
},
|
|
1085
1060
|
"P0507": {
|
|
1086
1061
|
"code": "P0507",
|
|
@@ -1088,12 +1063,7 @@
|
|
|
1088
1063
|
"category": "Powertrain",
|
|
1089
1064
|
"severity": "Low",
|
|
1090
1065
|
"system": "Idle Control",
|
|
1091
|
-
"possible_causes": [
|
|
1092
|
-
"Vacuum leak",
|
|
1093
|
-
"IAC valve stuck open",
|
|
1094
|
-
"PCV valve stuck open",
|
|
1095
|
-
"EVAP purge valve leaking"
|
|
1096
|
-
]
|
|
1066
|
+
"possible_causes": ["Vacuum leak", "IAC valve stuck open", "PCV valve stuck open", "EVAP purge valve leaking"]
|
|
1097
1067
|
},
|
|
1098
1068
|
"P0600": {
|
|
1099
1069
|
"code": "P0600",
|
|
@@ -1127,12 +1097,7 @@
|
|
|
1127
1097
|
"category": "Powertrain",
|
|
1128
1098
|
"severity": "Critical",
|
|
1129
1099
|
"system": "Engine Control Module",
|
|
1130
|
-
"possible_causes": [
|
|
1131
|
-
"ECM not programmed",
|
|
1132
|
-
"ECM programming incomplete",
|
|
1133
|
-
"Wrong software version",
|
|
1134
|
-
"ECM fault"
|
|
1135
|
-
]
|
|
1100
|
+
"possible_causes": ["ECM not programmed", "ECM programming incomplete", "Wrong software version", "ECM fault"]
|
|
1136
1101
|
},
|
|
1137
1102
|
"P0603": {
|
|
1138
1103
|
"code": "P0603",
|
|
@@ -1205,12 +1170,7 @@
|
|
|
1205
1170
|
"category": "Powertrain",
|
|
1206
1171
|
"severity": "Medium",
|
|
1207
1172
|
"system": "Charging System",
|
|
1208
|
-
"possible_causes": [
|
|
1209
|
-
"Faulty alternator",
|
|
1210
|
-
"Wiring harness problem",
|
|
1211
|
-
"Poor electrical connection",
|
|
1212
|
-
"ECM fault"
|
|
1213
|
-
]
|
|
1173
|
+
"possible_causes": ["Faulty alternator", "Wiring harness problem", "Poor electrical connection", "ECM fault"]
|
|
1214
1174
|
},
|
|
1215
1175
|
"P0625": {
|
|
1216
1176
|
"code": "P0625",
|
|
@@ -1283,12 +1243,7 @@
|
|
|
1283
1243
|
"category": "Powertrain",
|
|
1284
1244
|
"severity": "High",
|
|
1285
1245
|
"system": "Transmission",
|
|
1286
|
-
"possible_causes": [
|
|
1287
|
-
"Faulty input speed sensor",
|
|
1288
|
-
"Wiring harness problem",
|
|
1289
|
-
"Sensor reluctor damaged",
|
|
1290
|
-
"TCM fault"
|
|
1291
|
-
]
|
|
1246
|
+
"possible_causes": ["Faulty input speed sensor", "Wiring harness problem", "Sensor reluctor damaged", "TCM fault"]
|
|
1292
1247
|
},
|
|
1293
1248
|
"P0720": {
|
|
1294
1249
|
"code": "P0720",
|
|
@@ -1491,12 +1446,7 @@
|
|
|
1491
1446
|
"category": "Chassis",
|
|
1492
1447
|
"severity": "High",
|
|
1493
1448
|
"system": "ABS",
|
|
1494
|
-
"possible_causes": [
|
|
1495
|
-
"Faulty valve relay",
|
|
1496
|
-
"Relay circuit problem",
|
|
1497
|
-
"ABS module fault",
|
|
1498
|
-
"Wiring harness issue"
|
|
1499
|
-
]
|
|
1449
|
+
"possible_causes": ["Faulty valve relay", "Relay circuit problem", "ABS module fault", "Wiring harness issue"]
|
|
1500
1450
|
},
|
|
1501
1451
|
"C0161": {
|
|
1502
1452
|
"code": "C0161",
|
|
@@ -2206,12 +2156,7 @@
|
|
|
2206
2156
|
"category": "Body",
|
|
2207
2157
|
"severity": "Low",
|
|
2208
2158
|
"system": "Lighting System",
|
|
2209
|
-
"possible_causes": [
|
|
2210
|
-
"Burned out bulb",
|
|
2211
|
-
"Wiring harness problem",
|
|
2212
|
-
"Lamp socket corrosion",
|
|
2213
|
-
"BCM fault"
|
|
2214
|
-
]
|
|
2159
|
+
"possible_causes": ["Burned out bulb", "Wiring harness problem", "Lamp socket corrosion", "BCM fault"]
|
|
2215
2160
|
},
|
|
2216
2161
|
"B0601": {
|
|
2217
2162
|
"code": "B0601",
|
|
@@ -2232,12 +2177,7 @@
|
|
|
2232
2177
|
"category": "Body",
|
|
2233
2178
|
"severity": "Low",
|
|
2234
2179
|
"system": "Lighting System",
|
|
2235
|
-
"possible_causes": [
|
|
2236
|
-
"Burned out turn signal bulb",
|
|
2237
|
-
"Wiring harness problem",
|
|
2238
|
-
"Flasher relay fault",
|
|
2239
|
-
"BCM fault"
|
|
2240
|
-
]
|
|
2180
|
+
"possible_causes": ["Burned out turn signal bulb", "Wiring harness problem", "Flasher relay fault", "BCM fault"]
|
|
2241
2181
|
},
|
|
2242
2182
|
"B0603": {
|
|
2243
2183
|
"code": "B0603",
|
|
@@ -2245,12 +2185,7 @@
|
|
|
2245
2185
|
"category": "Body",
|
|
2246
2186
|
"severity": "Low",
|
|
2247
2187
|
"system": "Lighting System",
|
|
2248
|
-
"possible_causes": [
|
|
2249
|
-
"Burned out turn signal bulb",
|
|
2250
|
-
"Wiring harness problem",
|
|
2251
|
-
"Flasher relay fault",
|
|
2252
|
-
"BCM fault"
|
|
2253
|
-
]
|
|
2188
|
+
"possible_causes": ["Burned out turn signal bulb", "Wiring harness problem", "Flasher relay fault", "BCM fault"]
|
|
2254
2189
|
},
|
|
2255
2190
|
"B0604": {
|
|
2256
2191
|
"code": "B0604",
|
|
@@ -2362,12 +2297,7 @@
|
|
|
2362
2297
|
"category": "Body",
|
|
2363
2298
|
"severity": "Low",
|
|
2364
2299
|
"system": "Keyless Entry",
|
|
2365
|
-
"possible_causes": [
|
|
2366
|
-
"Key fob battery weak",
|
|
2367
|
-
"Key fob not synchronized",
|
|
2368
|
-
"BCM fault",
|
|
2369
|
-
"Receiver antenna fault"
|
|
2370
|
-
]
|
|
2300
|
+
"possible_causes": ["Key fob battery weak", "Key fob not synchronized", "BCM fault", "Receiver antenna fault"]
|
|
2371
2301
|
},
|
|
2372
2302
|
"B1300": {
|
|
2373
2303
|
"code": "B1300",
|
|
@@ -2466,12 +2396,7 @@
|
|
|
2466
2396
|
"category": "Network",
|
|
2467
2397
|
"severity": "Critical",
|
|
2468
2398
|
"system": "CAN Bus",
|
|
2469
|
-
"possible_causes": [
|
|
2470
|
-
"TCM not powered",
|
|
2471
|
-
"CAN bus wiring problem",
|
|
2472
|
-
"TCM internal fault",
|
|
2473
|
-
"CAN bus short circuit"
|
|
2474
|
-
]
|
|
2399
|
+
"possible_causes": ["TCM not powered", "CAN bus wiring problem", "TCM internal fault", "CAN bus short circuit"]
|
|
2475
2400
|
},
|
|
2476
2401
|
"U0102": {
|
|
2477
2402
|
"code": "U0102",
|
|
@@ -2544,12 +2469,7 @@
|
|
|
2544
2469
|
"category": "Network",
|
|
2545
2470
|
"severity": "High",
|
|
2546
2471
|
"system": "CAN Bus",
|
|
2547
|
-
"possible_causes": [
|
|
2548
|
-
"BCM not powered",
|
|
2549
|
-
"CAN bus wiring problem",
|
|
2550
|
-
"BCM internal fault",
|
|
2551
|
-
"Ground connection issue"
|
|
2552
|
-
]
|
|
2472
|
+
"possible_causes": ["BCM not powered", "CAN bus wiring problem", "BCM internal fault", "Ground connection issue"]
|
|
2553
2473
|
},
|
|
2554
2474
|
"U0141": {
|
|
2555
2475
|
"code": "U0141",
|
|
@@ -2557,12 +2477,7 @@
|
|
|
2557
2477
|
"category": "Network",
|
|
2558
2478
|
"severity": "High",
|
|
2559
2479
|
"system": "CAN Bus",
|
|
2560
|
-
"possible_causes": [
|
|
2561
|
-
"BCM not powered",
|
|
2562
|
-
"CAN bus wiring problem",
|
|
2563
|
-
"Module internal fault",
|
|
2564
|
-
"Connector problem"
|
|
2565
|
-
]
|
|
2480
|
+
"possible_causes": ["BCM not powered", "CAN bus wiring problem", "Module internal fault", "Connector problem"]
|
|
2566
2481
|
},
|
|
2567
2482
|
"U0151": {
|
|
2568
2483
|
"code": "U0151",
|
|
@@ -149,20 +149,14 @@
|
|
|
149
149
|
"type": "array",
|
|
150
150
|
"description": "Device IDs to include (whitelist)",
|
|
151
151
|
"items": {
|
|
152
|
-
"oneOf": [
|
|
153
|
-
{ "type": "integer" },
|
|
154
|
-
{ "type": "string", "pattern": "^0[xX][0-9A-Fa-f]+$" }
|
|
155
|
-
]
|
|
152
|
+
"oneOf": [{ "type": "integer" }, { "type": "string", "pattern": "^0[xX][0-9A-Fa-f]+$" }]
|
|
156
153
|
}
|
|
157
154
|
},
|
|
158
155
|
"exclude_devices": {
|
|
159
156
|
"type": "array",
|
|
160
157
|
"description": "Device IDs to exclude (blacklist)",
|
|
161
158
|
"items": {
|
|
162
|
-
"oneOf": [
|
|
163
|
-
{ "type": "integer" },
|
|
164
|
-
{ "type": "string", "pattern": "^0[xX][0-9A-Fa-f]+$" }
|
|
165
|
-
]
|
|
159
|
+
"oneOf": [{ "type": "integer" }, { "type": "string", "pattern": "^0[xX][0-9A-Fa-f]+$" }]
|
|
166
160
|
}
|
|
167
161
|
},
|
|
168
162
|
"include_categories": {
|
|
@@ -118,10 +118,7 @@
|
|
|
118
118
|
},
|
|
119
119
|
"value": {
|
|
120
120
|
"description": "Expected constant value for validation",
|
|
121
|
-
"oneOf": [
|
|
122
|
-
{ "type": "integer" },
|
|
123
|
-
{ "type": "array", "items": { "type": "integer" } }
|
|
124
|
-
]
|
|
121
|
+
"oneOf": [{ "type": "integer" }, { "type": "array", "items": { "type": "integer" } }]
|
|
125
122
|
},
|
|
126
123
|
"description": {
|
|
127
124
|
"type": "string",
|
|
@@ -188,18 +185,7 @@
|
|
|
188
185
|
},
|
|
189
186
|
"type": {
|
|
190
187
|
"type": "string",
|
|
191
|
-
"enum": [
|
|
192
|
-
"uint8",
|
|
193
|
-
"uint16",
|
|
194
|
-
"uint32",
|
|
195
|
-
"uint64",
|
|
196
|
-
"int8",
|
|
197
|
-
"int16",
|
|
198
|
-
"int32",
|
|
199
|
-
"int64",
|
|
200
|
-
"float32",
|
|
201
|
-
"float64"
|
|
202
|
-
],
|
|
188
|
+
"enum": ["uint8", "uint16", "uint32", "uint64", "int8", "int16", "int32", "int64", "float32", "float64"],
|
|
203
189
|
"description": "Sample data type"
|
|
204
190
|
},
|
|
205
191
|
"endian": {
|
|
@@ -303,10 +289,7 @@
|
|
|
303
289
|
},
|
|
304
290
|
"expected": {
|
|
305
291
|
"description": "Expected value",
|
|
306
|
-
"oneOf": [
|
|
307
|
-
{ "type": "integer" },
|
|
308
|
-
{ "type": "array", "items": { "type": "integer" } }
|
|
309
|
-
]
|
|
292
|
+
"oneOf": [{ "type": "integer" }, { "type": "array", "items": { "type": "integer" } }]
|
|
310
293
|
},
|
|
311
294
|
"on_failure": {
|
|
312
295
|
"type": "string",
|
|
@@ -379,10 +362,7 @@
|
|
|
379
362
|
},
|
|
380
363
|
"pattern": {
|
|
381
364
|
"description": "Idle pattern to detect",
|
|
382
|
-
"oneOf": [
|
|
383
|
-
{ "type": "string", "enum": ["auto", "zeros", "ones"] },
|
|
384
|
-
{ "type": "integer" }
|
|
385
|
-
]
|
|
365
|
+
"oneOf": [{ "type": "string", "enum": ["auto", "zeros", "ones"] }, { "type": "integer" }]
|
|
386
366
|
},
|
|
387
367
|
"min_duration": {
|
|
388
368
|
"type": "integer",
|
|
@@ -241,12 +241,7 @@
|
|
|
241
241
|
},
|
|
242
242
|
"value": {
|
|
243
243
|
"description": "Expected constant value for validation",
|
|
244
|
-
"oneOf": [
|
|
245
|
-
{ "type": "integer" },
|
|
246
|
-
{ "type": "number" },
|
|
247
|
-
{ "type": "string" },
|
|
248
|
-
{ "type": "array" }
|
|
249
|
-
]
|
|
244
|
+
"oneOf": [{ "type": "integer" }, { "type": "number" }, { "type": "string" }, { "type": "array" }]
|
|
250
245
|
},
|
|
251
246
|
"condition": {
|
|
252
247
|
"type": "string",
|
|
@@ -331,12 +326,7 @@
|
|
|
331
326
|
},
|
|
332
327
|
"expected": {
|
|
333
328
|
"description": "Expected value",
|
|
334
|
-
"oneOf": [
|
|
335
|
-
{ "type": "integer" },
|
|
336
|
-
{ "type": "number" },
|
|
337
|
-
{ "type": "string" },
|
|
338
|
-
{ "type": "array" }
|
|
339
|
-
]
|
|
329
|
+
"oneOf": [{ "type": "integer" }, { "type": "number" }, { "type": "string" }, { "type": "array" }]
|
|
340
330
|
},
|
|
341
331
|
"on_mismatch": {
|
|
342
332
|
"type": "string",
|
oscura/loaders/__init__.py
CHANGED
|
@@ -29,6 +29,7 @@ from oscura.core.types import DigitalTrace, IQTrace, WaveformTrace
|
|
|
29
29
|
_LOADER_REGISTRY: dict[str, tuple[str, str]] = {
|
|
30
30
|
"tektronix": ("oscura.loaders.tektronix", "load_tektronix_wfm"),
|
|
31
31
|
"tek": ("oscura.loaders.tektronix", "load_tektronix_wfm"),
|
|
32
|
+
"tss": ("oscura.loaders.tss", "load_tss"),
|
|
32
33
|
"rigol": ("oscura.loaders.rigol", "load_rigol_wfm"),
|
|
33
34
|
"numpy": ("oscura.loaders.numpy_loader", "load_npz"),
|
|
34
35
|
"csv": ("oscura.loaders.csv_loader", "load_csv"),
|
|
@@ -180,6 +181,7 @@ logger = logging.getLogger(__name__)
|
|
|
180
181
|
# Supported format extensions mapped to loader names
|
|
181
182
|
SUPPORTED_FORMATS: dict[str, str] = {
|
|
182
183
|
".wfm": "auto_wfm", # Auto-detect Tektronix vs Rigol
|
|
184
|
+
".tss": "tss", # Tektronix session files
|
|
183
185
|
".npz": "numpy",
|
|
184
186
|
".csv": "csv",
|
|
185
187
|
".h5": "hdf5",
|
|
@@ -392,8 +394,8 @@ def load_all_channels(
|
|
|
392
394
|
)
|
|
393
395
|
loader_name = SUPPORTED_FORMATS[ext]
|
|
394
396
|
|
|
395
|
-
# Currently only supports Tektronix WFM for multi-channel loading
|
|
396
|
-
if loader_name in ("auto_wfm", "tektronix", "tek"):
|
|
397
|
+
# Currently only supports Tektronix WFM and TSS for multi-channel loading
|
|
398
|
+
if loader_name in ("auto_wfm", "tektronix", "tek", "tss"):
|
|
397
399
|
return _load_all_channels_tektronix(path)
|
|
398
400
|
else:
|
|
399
401
|
# For other formats, try loading as single channel
|
|
@@ -405,10 +407,10 @@ def load_all_channels(
|
|
|
405
407
|
def _load_all_channels_tektronix(
|
|
406
408
|
path: Path,
|
|
407
409
|
) -> dict[str, WaveformTrace | DigitalTrace | IQTrace]:
|
|
408
|
-
"""Load all channels from a Tektronix WFM file.
|
|
410
|
+
"""Load all channels from a Tektronix WFM or TSS file.
|
|
409
411
|
|
|
410
412
|
Args:
|
|
411
|
-
path: Path to the Tektronix .wfm file.
|
|
413
|
+
path: Path to the Tektronix .wfm or .tss file.
|
|
412
414
|
|
|
413
415
|
Returns:
|
|
414
416
|
Dictionary mapping channel names to traces.
|
|
@@ -416,6 +418,12 @@ def _load_all_channels_tektronix(
|
|
|
416
418
|
Raises:
|
|
417
419
|
LoaderError: If the file cannot be read or parsed.
|
|
418
420
|
"""
|
|
421
|
+
# Check if this is a .tss session file
|
|
422
|
+
if path.suffix.lower() == ".tss":
|
|
423
|
+
from oscura.loaders.tss import load_all_channels_tss
|
|
424
|
+
|
|
425
|
+
return load_all_channels_tss(path)
|
|
426
|
+
|
|
419
427
|
wfm = _read_tektronix_file(path)
|
|
420
428
|
channels: dict[str, WaveformTrace | DigitalTrace | IQTrace] = {}
|
|
421
429
|
|
oscura/loaders/tss.py
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
"""Tektronix Session File (.tss) Loader.
|
|
2
|
+
|
|
3
|
+
This module provides loading functionality for Tektronix session files (.tss),
|
|
4
|
+
which are ZIP archives containing multiple waveform captures, instrument
|
|
5
|
+
configuration, measurements, and annotations.
|
|
6
|
+
|
|
7
|
+
A .tss session file typically contains:
|
|
8
|
+
- Multiple .wfm waveform files (one per channel/capture)
|
|
9
|
+
- session.json: Instrument configuration and setup
|
|
10
|
+
- measurements.json: Stored measurement results (optional)
|
|
11
|
+
- annotations.json: User annotations and markers (optional)
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
>>> import oscura as osc
|
|
15
|
+
>>> trace = osc.load("oscilloscope_session.tss")
|
|
16
|
+
>>> print(f"Channel: {trace.metadata.channel_name}")
|
|
17
|
+
>>> print(f"Sample rate: {trace.metadata.sample_rate} Hz")
|
|
18
|
+
|
|
19
|
+
>>> # Load specific channel
|
|
20
|
+
>>> trace = osc.load("session.tss", channel="CH2")
|
|
21
|
+
|
|
22
|
+
>>> # Load all channels
|
|
23
|
+
>>> channels = osc.load_all_channels("session.tss")
|
|
24
|
+
>>> for name, trace in channels.items():
|
|
25
|
+
... print(f"{name}: {len(trace.data)} samples")
|
|
26
|
+
|
|
27
|
+
References:
|
|
28
|
+
Tektronix Programming Manual for Session Files
|
|
29
|
+
TekScope PC Analysis Software Documentation
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import json
|
|
35
|
+
import tempfile
|
|
36
|
+
import zipfile
|
|
37
|
+
from os import PathLike
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
from typing import Any
|
|
40
|
+
|
|
41
|
+
from oscura.core.exceptions import FormatError, LoaderError
|
|
42
|
+
from oscura.core.types import DigitalTrace, IQTrace, WaveformTrace
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def load_tss(
|
|
46
|
+
path: str | PathLike[str],
|
|
47
|
+
*,
|
|
48
|
+
channel: str | int | None = None,
|
|
49
|
+
) -> WaveformTrace | DigitalTrace | IQTrace:
|
|
50
|
+
"""Load a Tektronix session file (.tss).
|
|
51
|
+
|
|
52
|
+
Tektronix session files are ZIP archives containing multiple waveform
|
|
53
|
+
captures along with instrument configuration and analysis results.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
path: Path to the Tektronix .tss session file.
|
|
57
|
+
channel: Optional channel name or index to load. If None,
|
|
58
|
+
loads the first waveform found (alphabetically).
|
|
59
|
+
String names are case-insensitive (e.g., "ch1", "CH1").
|
|
60
|
+
Integer index is 0-based.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
WaveformTrace, DigitalTrace, or IQTrace containing the channel data.
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
LoaderError: If the file cannot be loaded or doesn't exist.
|
|
67
|
+
FormatError: If the file is not a valid Tektronix session.
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
>>> # Load first channel (default)
|
|
71
|
+
>>> trace = load_tss("session.tss")
|
|
72
|
+
|
|
73
|
+
>>> # Load specific channel by name
|
|
74
|
+
>>> trace = load_tss("session.tss", channel="CH2")
|
|
75
|
+
|
|
76
|
+
>>> # Load by index
|
|
77
|
+
>>> trace = load_tss("session.tss", channel=1) # Second channel
|
|
78
|
+
"""
|
|
79
|
+
path = Path(path)
|
|
80
|
+
|
|
81
|
+
# Validate file
|
|
82
|
+
_validate_tss_file(path)
|
|
83
|
+
|
|
84
|
+
# Load all waveforms and select requested channel
|
|
85
|
+
waveforms = _load_all_waveforms(path)
|
|
86
|
+
|
|
87
|
+
if not waveforms:
|
|
88
|
+
raise FormatError(
|
|
89
|
+
"No waveforms found in session file",
|
|
90
|
+
file_path=str(path),
|
|
91
|
+
expected="At least one .wfm file in the archive",
|
|
92
|
+
got="Empty session or no waveform files",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Select channel
|
|
96
|
+
trace, channel_name = _select_channel(waveforms, channel, path)
|
|
97
|
+
|
|
98
|
+
# Enrich metadata with session information
|
|
99
|
+
try:
|
|
100
|
+
with zipfile.ZipFile(path, "r") as zf:
|
|
101
|
+
session_metadata = _parse_session_metadata(zf, path)
|
|
102
|
+
trace = _enrich_metadata_from_session(trace, session_metadata, str(path))
|
|
103
|
+
except Exception:
|
|
104
|
+
# Session metadata is optional, continue with waveform metadata
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
return trace
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def load_all_channels_tss(
|
|
111
|
+
path: Path,
|
|
112
|
+
) -> dict[str, WaveformTrace | DigitalTrace | IQTrace]:
|
|
113
|
+
"""Load all channels from a Tektronix session file.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
path: Path to the .tss session file.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Dictionary mapping channel names to traces.
|
|
120
|
+
Channel names are derived from .wfm filenames (e.g., "ch1", "ch2").
|
|
121
|
+
|
|
122
|
+
Raises:
|
|
123
|
+
LoaderError: If the file cannot be loaded.
|
|
124
|
+
FormatError: If no waveforms found in session.
|
|
125
|
+
|
|
126
|
+
Example:
|
|
127
|
+
>>> channels = load_all_channels_tss(Path("session.tss"))
|
|
128
|
+
>>> for name, trace in channels.items():
|
|
129
|
+
... print(f"{name}: {trace.metadata.sample_rate} Hz")
|
|
130
|
+
"""
|
|
131
|
+
# Validate file
|
|
132
|
+
_validate_tss_file(path)
|
|
133
|
+
|
|
134
|
+
# Load all waveforms
|
|
135
|
+
waveforms = _load_all_waveforms(path)
|
|
136
|
+
|
|
137
|
+
if not waveforms:
|
|
138
|
+
raise FormatError(
|
|
139
|
+
"No waveforms found in session file",
|
|
140
|
+
file_path=str(path),
|
|
141
|
+
expected="At least one .wfm file",
|
|
142
|
+
got="Empty archive",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Enrich all traces with session metadata
|
|
146
|
+
try:
|
|
147
|
+
with zipfile.ZipFile(path, "r") as zf:
|
|
148
|
+
session_metadata = _parse_session_metadata(zf, path)
|
|
149
|
+
for name in waveforms:
|
|
150
|
+
waveforms[name] = _enrich_metadata_from_session(
|
|
151
|
+
waveforms[name], session_metadata, str(path)
|
|
152
|
+
)
|
|
153
|
+
except Exception:
|
|
154
|
+
# Session metadata is optional
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
return waveforms
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _validate_tss_file(path: Path) -> None:
|
|
161
|
+
"""Validate that file exists and is a valid ZIP archive.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
path: Path to validate.
|
|
165
|
+
|
|
166
|
+
Raises:
|
|
167
|
+
LoaderError: If file doesn't exist or can't be read.
|
|
168
|
+
FormatError: If file is not a ZIP archive.
|
|
169
|
+
"""
|
|
170
|
+
if not path.exists():
|
|
171
|
+
raise LoaderError(
|
|
172
|
+
"File not found",
|
|
173
|
+
file_path=str(path),
|
|
174
|
+
fix_hint="Ensure the file path is correct and the file exists.",
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if not zipfile.is_zipfile(path):
|
|
178
|
+
raise FormatError(
|
|
179
|
+
"Not a valid ZIP archive",
|
|
180
|
+
file_path=str(path),
|
|
181
|
+
expected="Tektronix session file (.tss) is a ZIP archive",
|
|
182
|
+
got="File is not a ZIP file",
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
with zipfile.ZipFile(path, "r") as zf:
|
|
187
|
+
# Test archive integrity
|
|
188
|
+
bad_file = zf.testzip()
|
|
189
|
+
if bad_file is not None:
|
|
190
|
+
raise FormatError(
|
|
191
|
+
f"Corrupted ZIP archive: {bad_file}",
|
|
192
|
+
file_path=str(path),
|
|
193
|
+
expected="Valid ZIP archive",
|
|
194
|
+
got="Corrupted file detected",
|
|
195
|
+
)
|
|
196
|
+
except zipfile.BadZipFile as e:
|
|
197
|
+
raise FormatError(
|
|
198
|
+
"Corrupted or invalid ZIP archive",
|
|
199
|
+
file_path=str(path),
|
|
200
|
+
details=str(e),
|
|
201
|
+
) from e
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _parse_session_metadata(zf: zipfile.ZipFile, path: Path) -> dict[str, Any]:
|
|
205
|
+
"""Parse session.json metadata from the archive.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
zf: Open ZipFile object.
|
|
209
|
+
path: Path to session file (for error messages).
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Dictionary containing session metadata.
|
|
213
|
+
Returns empty dict if session.json not found (non-fatal).
|
|
214
|
+
"""
|
|
215
|
+
# Look for session metadata files
|
|
216
|
+
metadata_files = [
|
|
217
|
+
"session.json",
|
|
218
|
+
"Session.json",
|
|
219
|
+
"metadata.json",
|
|
220
|
+
"Metadata.json",
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
for metadata_file in metadata_files:
|
|
224
|
+
try:
|
|
225
|
+
with zf.open(metadata_file) as f:
|
|
226
|
+
data: dict[str, Any] = json.load(f)
|
|
227
|
+
return data
|
|
228
|
+
except KeyError:
|
|
229
|
+
continue
|
|
230
|
+
except json.JSONDecodeError as e:
|
|
231
|
+
# Non-fatal: log warning and continue
|
|
232
|
+
import warnings
|
|
233
|
+
|
|
234
|
+
warnings.warn(
|
|
235
|
+
f"Failed to parse {metadata_file} in {path.name}: {e}",
|
|
236
|
+
stacklevel=2,
|
|
237
|
+
)
|
|
238
|
+
return {}
|
|
239
|
+
|
|
240
|
+
# Session metadata is optional
|
|
241
|
+
return {}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _extract_waveform_list(zf: zipfile.ZipFile) -> list[str]:
|
|
245
|
+
"""Extract list of .wfm files in the session.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
zf: Open ZipFile object.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
List of .wfm file paths within the archive.
|
|
252
|
+
Excludes macOS metadata files (__MACOSX).
|
|
253
|
+
"""
|
|
254
|
+
return [
|
|
255
|
+
name
|
|
256
|
+
for name in zf.namelist()
|
|
257
|
+
if name.lower().endswith(".wfm") and not name.startswith("__MACOSX")
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _load_wfm_from_archive(
|
|
262
|
+
zf: zipfile.ZipFile,
|
|
263
|
+
wfm_name: str,
|
|
264
|
+
path: Path,
|
|
265
|
+
) -> WaveformTrace | DigitalTrace | IQTrace:
|
|
266
|
+
"""Extract and load a .wfm file from the archive.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
zf: Open ZipFile object.
|
|
270
|
+
wfm_name: Name of .wfm file within archive.
|
|
271
|
+
path: Path to session file (for error messages).
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Loaded trace from the waveform file.
|
|
275
|
+
|
|
276
|
+
Raises:
|
|
277
|
+
LoaderError: If waveform cannot be loaded.
|
|
278
|
+
"""
|
|
279
|
+
try:
|
|
280
|
+
# Extract waveform to temporary file
|
|
281
|
+
# (load_tektronix_wfm expects file path, not bytes)
|
|
282
|
+
wfm_bytes = zf.read(wfm_name)
|
|
283
|
+
|
|
284
|
+
with tempfile.NamedTemporaryFile(suffix=".wfm", delete=True) as tmp:
|
|
285
|
+
tmp.write(wfm_bytes)
|
|
286
|
+
tmp.flush()
|
|
287
|
+
|
|
288
|
+
# Use existing Tektronix loader
|
|
289
|
+
from oscura.loaders.tektronix import load_tektronix_wfm
|
|
290
|
+
|
|
291
|
+
trace = load_tektronix_wfm(tmp.name)
|
|
292
|
+
|
|
293
|
+
return trace
|
|
294
|
+
|
|
295
|
+
except Exception as e:
|
|
296
|
+
raise LoaderError(
|
|
297
|
+
f"Failed to load waveform from session: {wfm_name}",
|
|
298
|
+
file_path=str(path),
|
|
299
|
+
details=str(e),
|
|
300
|
+
fix_hint="Waveform file may be corrupted or incompatible.",
|
|
301
|
+
) from e
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _load_all_waveforms(path: Path) -> dict[str, WaveformTrace | DigitalTrace | IQTrace]:
|
|
305
|
+
"""Load all waveforms from the session file.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
path: Path to .tss session file.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Dictionary mapping channel names to traces.
|
|
312
|
+
"""
|
|
313
|
+
waveforms: dict[str, WaveformTrace | DigitalTrace | IQTrace] = {}
|
|
314
|
+
|
|
315
|
+
with zipfile.ZipFile(path, "r") as zf:
|
|
316
|
+
wfm_files = _extract_waveform_list(zf)
|
|
317
|
+
|
|
318
|
+
for wfm_name in sorted(wfm_files): # Sort for consistent ordering
|
|
319
|
+
# Derive channel name from filename
|
|
320
|
+
channel_name = _derive_channel_name(wfm_name)
|
|
321
|
+
|
|
322
|
+
# Load waveform
|
|
323
|
+
trace = _load_wfm_from_archive(zf, wfm_name, path)
|
|
324
|
+
|
|
325
|
+
# Store with normalized channel name
|
|
326
|
+
waveforms[channel_name] = trace
|
|
327
|
+
|
|
328
|
+
return waveforms
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _derive_channel_name(wfm_filename: str) -> str:
|
|
332
|
+
"""Derive channel name from .wfm filename.
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
wfm_filename: Filename like "CH1.wfm", "CH2_Voltage.wfm", etc.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
Normalized channel name (lowercase, e.g., "ch1", "ch2", "d0").
|
|
339
|
+
|
|
340
|
+
Examples:
|
|
341
|
+
>>> _derive_channel_name("CH1.wfm")
|
|
342
|
+
'ch1'
|
|
343
|
+
>>> _derive_channel_name("subdir/CH2_Voltage.wfm")
|
|
344
|
+
'ch2'
|
|
345
|
+
>>> _derive_channel_name("D0.wfm")
|
|
346
|
+
'd0'
|
|
347
|
+
>>> _derive_channel_name("MATH1.wfm")
|
|
348
|
+
'math1'
|
|
349
|
+
"""
|
|
350
|
+
# Get base filename without path
|
|
351
|
+
basename = Path(wfm_filename).stem # Remove extension
|
|
352
|
+
|
|
353
|
+
# Remove path components if nested
|
|
354
|
+
basename = basename.split("/")[-1].split("\\")[-1]
|
|
355
|
+
|
|
356
|
+
# Extract channel identifier (first part before underscore)
|
|
357
|
+
channel_id = basename.split("_")[0]
|
|
358
|
+
|
|
359
|
+
# Normalize to lowercase
|
|
360
|
+
return channel_id.lower()
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _select_channel(
|
|
364
|
+
waveforms: dict[str, WaveformTrace | DigitalTrace | IQTrace],
|
|
365
|
+
channel: str | int | None,
|
|
366
|
+
path: Path,
|
|
367
|
+
) -> tuple[WaveformTrace | DigitalTrace | IQTrace, str]:
|
|
368
|
+
"""Select specific channel from waveforms dictionary.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
waveforms: Dictionary of channel name to trace.
|
|
372
|
+
channel: Channel selector (name, index, or None for first).
|
|
373
|
+
path: Path to session file (for error messages).
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
Tuple of (selected_trace, channel_name).
|
|
377
|
+
|
|
378
|
+
Raises:
|
|
379
|
+
LoaderError: If channel not found or index out of range.
|
|
380
|
+
"""
|
|
381
|
+
if channel is None:
|
|
382
|
+
# Default: first channel (alphabetically sorted)
|
|
383
|
+
channel_name = sorted(waveforms.keys())[0]
|
|
384
|
+
return waveforms[channel_name], channel_name
|
|
385
|
+
|
|
386
|
+
if isinstance(channel, int):
|
|
387
|
+
# Select by index
|
|
388
|
+
channel_names = sorted(waveforms.keys())
|
|
389
|
+
if channel < 0 or channel >= len(channel_names):
|
|
390
|
+
raise LoaderError(
|
|
391
|
+
f"Channel index {channel} out of range",
|
|
392
|
+
file_path=str(path),
|
|
393
|
+
fix_hint=f"Available channels: {', '.join(channel_names)} (indices 0-{len(channel_names) - 1})",
|
|
394
|
+
)
|
|
395
|
+
channel_name = channel_names[channel]
|
|
396
|
+
return waveforms[channel_name], channel_name
|
|
397
|
+
|
|
398
|
+
# Select by name (case-insensitive)
|
|
399
|
+
channel_lower = channel.lower()
|
|
400
|
+
for name, trace in waveforms.items():
|
|
401
|
+
if name.lower() == channel_lower:
|
|
402
|
+
return trace, name
|
|
403
|
+
|
|
404
|
+
# Channel not found
|
|
405
|
+
available = ", ".join(sorted(waveforms.keys()))
|
|
406
|
+
raise LoaderError(
|
|
407
|
+
f"Channel '{channel}' not found in session",
|
|
408
|
+
file_path=str(path),
|
|
409
|
+
fix_hint=f"Available channels: {available}",
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _enrich_metadata_from_session(
|
|
414
|
+
trace: WaveformTrace | DigitalTrace | IQTrace,
|
|
415
|
+
session_metadata: dict[str, Any],
|
|
416
|
+
source_file: str,
|
|
417
|
+
) -> WaveformTrace | DigitalTrace | IQTrace:
|
|
418
|
+
"""Enrich waveform metadata with session-level information.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
trace: Original trace from .wfm file.
|
|
422
|
+
session_metadata: Session metadata from session.json.
|
|
423
|
+
source_file: Path to .tss file (for source_file metadata).
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
Trace with enriched metadata.
|
|
427
|
+
"""
|
|
428
|
+
# Create new metadata with session information
|
|
429
|
+
from dataclasses import replace
|
|
430
|
+
|
|
431
|
+
metadata = trace.metadata
|
|
432
|
+
|
|
433
|
+
# Update source file to point to .tss instead of temp .wfm
|
|
434
|
+
metadata = replace(metadata, source_file=source_file)
|
|
435
|
+
|
|
436
|
+
# Add trigger info from session if available
|
|
437
|
+
if "trigger" in session_metadata and metadata.trigger_info is None:
|
|
438
|
+
metadata = replace(metadata, trigger_info=session_metadata["trigger"])
|
|
439
|
+
|
|
440
|
+
# Return trace with updated metadata
|
|
441
|
+
if isinstance(trace, WaveformTrace):
|
|
442
|
+
return WaveformTrace(data=trace.data, metadata=metadata)
|
|
443
|
+
if isinstance(trace, DigitalTrace):
|
|
444
|
+
return DigitalTrace(data=trace.data, metadata=metadata, edges=trace.edges)
|
|
445
|
+
# IQTrace
|
|
446
|
+
return IQTrace(
|
|
447
|
+
i_data=trace.i_data,
|
|
448
|
+
q_data=trace.q_data,
|
|
449
|
+
metadata=metadata,
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
__all__ = [
|
|
454
|
+
"load_all_channels_tss",
|
|
455
|
+
"load_tss",
|
|
456
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oscura
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Unified hardware reverse engineering framework. Extract all information from any system through signals and data. Unknown protocol discovery, state machine extraction, CRC recovery, security analysis. 16+ protocols, IEEE-compliant measurements.
|
|
5
5
|
Project-URL: Homepage, https://github.com/oscura-re/oscura
|
|
6
6
|
Project-URL: Documentation, https://github.com/oscura-re/oscura/tree/main/docs
|
|
@@ -292,7 +292,7 @@ result = osc.auto_decode(trace)
|
|
|
292
292
|
print(f"Detected {result.protocol}: {len(result.frames)} frames decoded")
|
|
293
293
|
```
|
|
294
294
|
|
|
295
|
-
[
|
|
295
|
+
**[See demos/README.md](demos/README.md)** for 105+ comprehensive demonstrations organized by skill level.
|
|
296
296
|
|
|
297
297
|
---
|
|
298
298
|
|
|
@@ -439,32 +439,50 @@ Built for extensibility:
|
|
|
439
439
|
|
|
440
440
|
## Learn By Doing
|
|
441
441
|
|
|
442
|
-
### Working
|
|
442
|
+
### Working Demonstrations
|
|
443
443
|
|
|
444
|
-
|
|
444
|
+
**105+ comprehensive demos** organized into 12 categories covering:
|
|
445
445
|
|
|
446
|
-
-
|
|
447
|
-
-
|
|
448
|
-
-
|
|
449
|
-
-
|
|
450
|
-
-
|
|
451
|
-
-
|
|
446
|
+
- **Data Loading** - All file format loaders (oscilloscopes, logic analyzers, automotive, scientific)
|
|
447
|
+
- **Basic Analysis** - Waveform measurements, digital analysis, spectral analysis, filtering
|
|
448
|
+
- **Protocol Decoding** - UART, SPI, I2C, CAN, LIN, FlexRay, JTAG, SWD, I2S, USB
|
|
449
|
+
- **Advanced Analysis** - Jitter, eye diagrams, power analysis, signal integrity, TDR
|
|
450
|
+
- **Domain Specific** - Automotive diagnostics, EMC compliance, side-channel analysis, IEEE 181 timing
|
|
451
|
+
- **Reverse Engineering** - CRC recovery, state machines, Wireshark dissectors, ML classification
|
|
452
|
+
- **Advanced Features** - Lazy loading, memory management, performance optimization, batch processing
|
|
453
|
+
- **Extensibility** - Custom analyzers, plugins, templates
|
|
454
|
+
- **Integration** - CI/CD, hardware, external tools, web dashboards
|
|
455
|
+
- **Export & Visualization** - All export formats, plotting, reporting
|
|
456
|
+
- **Complete Workflows** - End-to-end production pipelines
|
|
457
|
+
- **Standards Compliance** - IEEE 181/1241/1459/2414, automotive standards
|
|
452
458
|
|
|
453
|
-
###
|
|
459
|
+
### Comprehensive Demonstrations
|
|
460
|
+
|
|
461
|
+
**33+ in-depth demos** organized by skill level and domain:
|
|
462
|
+
|
|
463
|
+
- **[Getting Started](demos/README.md#beginner-path-2-4-hours)** - File loading, basic measurements, format export (Beginner, 2-4 hours)
|
|
464
|
+
- **[Protocol Decoding](demos/README.md#intermediate-path-6-10-hours)** - UART, SPI, I2C, Manchester, JTAG, USB, PCAP (Intermediate, 6-10 hours)
|
|
465
|
+
- **[Reverse Engineering](demos/README.md#advanced-path-12-20-hours)** - CRC recovery, state machines, Wireshark dissectors, automotive protocols (Advanced, 12-20 hours)
|
|
466
|
+
- **[Standards Compliance](demos/README.md#advanced-path-12-20-hours)** - IEEE 181/1241/1459/2414, CISPR 32, IEC 61000 (Advanced/Expert)
|
|
467
|
+
- **[Complete Workflows](demos/README.md#expert-path-20-40-hours)** - End-to-end production pipelines with ML inference (Expert, 20-40 hours)
|
|
468
|
+
|
|
469
|
+
**Categories**: Waveform Analysis | File I/O | Custom DAQ | Serial Protocols | Protocol Decoding | UDP Analysis | Protocol Inference | Automotive | Timing | Mixed Signal | Spectral | Jitter | Power | Signal Integrity | EMC | Signal RE | Advanced Inference | Complete Workflows
|
|
470
|
+
|
|
471
|
+
[**See full demo catalog with learning paths**](demos/README.md)
|
|
472
|
+
|
|
473
|
+
### Run Your First Demo
|
|
454
474
|
|
|
455
475
|
```bash
|
|
456
476
|
# Install development dependencies
|
|
457
477
|
./scripts/setup.sh
|
|
458
478
|
|
|
459
|
-
#
|
|
460
|
-
python
|
|
479
|
+
# Run your first demo
|
|
480
|
+
python demos/00_getting_started/00_hello_world.py
|
|
461
481
|
|
|
462
|
-
#
|
|
463
|
-
python
|
|
482
|
+
# Or try a specific topic
|
|
483
|
+
python demos/05_domain_specific/05_side_channel_basics.py
|
|
464
484
|
```
|
|
465
485
|
|
|
466
|
-
[**Browse all examples**](examples/)
|
|
467
|
-
|
|
468
486
|
---
|
|
469
487
|
|
|
470
488
|
## Command-Line Interface
|
|
@@ -556,7 +574,7 @@ python3 .claude/hooks/validate_all.py # Must show 5/5 passing
|
|
|
556
574
|
| **Real-World Validation** | Test on your captures, report issues | Ensure reliability across use cases |
|
|
557
575
|
| **Documentation & Case Studies** | Tutorials, sanitized RE workflows, academic papers using Oscura | Lower entry barrier, demonstrate capabilities |
|
|
558
576
|
|
|
559
|
-
[**Contributing Guide**](CONTRIBUTING.md) | [Architecture Documentation](docs/architecture
|
|
577
|
+
[**Contributing Guide**](CONTRIBUTING.md) | [Architecture Documentation](docs/developer-guide/architecture.md)
|
|
560
578
|
|
|
561
579
|
### Community
|
|
562
580
|
|
|
@@ -584,7 +602,7 @@ python3 .claude/hooks/validate_all.py # Must show 5/5 passing
|
|
|
584
602
|
|
|
585
603
|
### Development
|
|
586
604
|
|
|
587
|
-
- [Architecture](docs/architecture
|
|
605
|
+
- [Architecture](docs/developer-guide/architecture.md) - Design principles and patterns
|
|
588
606
|
- [Testing Guide](docs/testing/) - Test suite architecture
|
|
589
607
|
- [CHANGELOG](CHANGELOG.md) - Version history and migration guides
|
|
590
608
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
oscura/__init__.py,sha256=
|
|
1
|
+
oscura/__init__.py,sha256=cB_SbWzKA60qDD2BQHesVvh_GOgWI7XOGfOOwd42YyY,19022
|
|
2
2
|
oscura/__main__.py,sha256=l1rnaD-tpI1W3cp1VHGhSdq9NDqw6Gxjf_gnqXf-zzE,11786
|
|
3
3
|
oscura/convenience.py,sha256=tAABQMwgKjftSNUJLlYnchAwVSO-no0rBfSZteXfZnI,16361
|
|
4
4
|
oscura/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -159,7 +159,7 @@ oscura/api/server/templates/reports.html,sha256=18xoAZPIg7eQANs9k-tmGzVs_-tmS5m6
|
|
|
159
159
|
oscura/api/server/templates/session_detail.html,sha256=32HnJ0o8ptlZEHQVO1c96j6PoxS0856kPjW2MVn0D8o,3693
|
|
160
160
|
oscura/api/server/templates/sessions.html,sha256=kVcpLOlXAx0PizP7Vc1NlhsYg5swTj77fKCZxoTTCYU,3146
|
|
161
161
|
oscura/api/server/templates/waveforms.html,sha256=5XfpHkgRpmV0MeFb9pBkVDa63phT8zGWnrmA0UlUwZE,2269
|
|
162
|
-
oscura/automotive/__init__.py,sha256=
|
|
162
|
+
oscura/automotive/__init__.py,sha256=PCTYjEm5mVPrBiOmYyhPul9utHa955ASz-Oq7h_nYy8,2767
|
|
163
163
|
oscura/automotive/visualization.py,sha256=LD6Gia3kDqoB_jQkOeVmyRVLv1BPuTUVj6lJMVtbQWg,10664
|
|
164
164
|
oscura/automotive/can/__init__.py,sha256=ZYxC8tMMi9Drm9adkH5yklypBTfIbf3D7BYA2SUp9fE,1491
|
|
165
165
|
oscura/automotive/can/analysis.py,sha256=0z6MycxuJGusqDQhYEpHnGRmDw3OMf5yDbS8zI6GJO0,11251
|
|
@@ -177,7 +177,7 @@ oscura/automotive/dbc/__init__.py,sha256=HZlM2WAaaglpX4C9tJVkF7uZiaHW4iUPQAbhfJY
|
|
|
177
177
|
oscura/automotive/dbc/generator.py,sha256=Dxg6fFHYNxtqExKxf0S63MeZsHriisA6R-Ii7WwsfPY,6724
|
|
178
178
|
oscura/automotive/dbc/parser.py,sha256=C5SL1bfJFZp0CYsw-ff8ysJ_57Us9dBoHZ1t6LVLGbo,4421
|
|
179
179
|
oscura/automotive/dtc/__init__.py,sha256=09CgvnClJTQIvzgDOQ22CbcjGAg_zOhPlbm1-k-gl30,895
|
|
180
|
-
oscura/automotive/dtc/data.json,sha256=
|
|
180
|
+
oscura/automotive/dtc/data.json,sha256=5pddTolfBJjwimwp5epiMjUqSngZ1dur9LQlcHfdjrA,79331
|
|
181
181
|
oscura/automotive/dtc/database.py,sha256=Ee1Jd4CbP5T4LON0Y2PdOkgdISHa8jf8c6znb0S_L00,9582
|
|
182
182
|
oscura/automotive/flexray/__init__.py,sha256=2RuW8TR4M3tSDEfcj-pJQpGGSxGaw5GdyGSvjMnU8Lk,938
|
|
183
183
|
oscura/automotive/flexray/analyzer.py,sha256=UG3e9tJSHGV7aR3WKwfVCrNDSVlT7ZgFm7vgCcw0qOo,16727
|
|
@@ -284,9 +284,9 @@ oscura/core/plugins/registry.py,sha256=XNtq0rBuppC1Z5V5RH74afW-LBnWSLc33lCE6BDZK
|
|
|
284
284
|
oscura/core/plugins/versioning.py,sha256=d18sXMbgeWy_dVNaM7JWvCVIF6VgpnjIbjP8gdFfgSs,10785
|
|
285
285
|
oscura/core/schemas/__init__.py,sha256=W33kQp0A8v9qZevqjm3T-1Fjrnou5cz0WCAbLRkwbgY,4327
|
|
286
286
|
oscura/core/schemas/bus_configuration.json,sha256=gpcDsg04760KCaLeIDuWvP6RzRUcPZuQplJbe7xpc8E,9562
|
|
287
|
-
oscura/core/schemas/device_mapping.json,sha256=
|
|
288
|
-
oscura/core/schemas/packet_format.json,sha256=
|
|
289
|
-
oscura/core/schemas/protocol_definition.json,sha256=
|
|
287
|
+
oscura/core/schemas/device_mapping.json,sha256=I87-MWnXldVWIXRC1H-WgzNLszymRdnaxwE3B1z8qys,5415
|
|
288
|
+
oscura/core/schemas/packet_format.json,sha256=NVa4RtwSye9QgD1pBi67nuCDRsVljudCpBBGjmZsMqk,12567
|
|
289
|
+
oscura/core/schemas/protocol_definition.json,sha256=lThVXTWzh7GxN-XNb3h8Bh20l1NZUigivWBb04xvogs,11116
|
|
290
290
|
oscura/correlation/__init__.py,sha256=4H5lCJqBBG3_10oIPsdIOyCij8FlKtMxLp3SAYvSsRg,1537
|
|
291
291
|
oscura/correlation/multi_protocol.py,sha256=X3sTdAc6etsY_9GIbXISCeNMNWM8imkGzHn-UhK7rcw,28288
|
|
292
292
|
oscura/discovery/__init__.py,sha256=ytYa_s4PfKxRRopOdUGsmzUIDPevH25ze_VytBEX2Cw,1232
|
|
@@ -376,7 +376,7 @@ oscura/jupyter/exploratory/unknown.py,sha256=KaxpoP9tW9E7Ld16_3VaXaKszfcU2uw62Id
|
|
|
376
376
|
oscura/jupyter/ui/__init__.py,sha256=XQJh89b_xRe1CB8jssjIdbVgQeLCplFzSkCiXQYKXFU,935
|
|
377
377
|
oscura/jupyter/ui/formatters.py,sha256=roXZhaNLgypb30T6EGF805cDkrqsAh_RQ978iGLtlAU,13580
|
|
378
378
|
oscura/jupyter/ui/progressive_display.py,sha256=Ew-xXek_f6-XkR0CZymyzYbYkbXn8qCZS6Usk2n86dQ,11993
|
|
379
|
-
oscura/loaders/__init__.py,sha256=
|
|
379
|
+
oscura/loaders/__init__.py,sha256=f1yakezyiv6IjrsK_L7H1jFCwQUAGNCvjv5DIKHDgzo,20237
|
|
380
380
|
oscura/loaders/binary.py,sha256=USQTTdWjbrn3b-Ly4v-E5eLhga6kM73QlNKxnLm1YUw,5062
|
|
381
381
|
oscura/loaders/chipwhisperer.py,sha256=gkxgakx0YXxJ4dKKkSmlOZYa-VKdWFldAbz3PPAvWyw,12756
|
|
382
382
|
oscura/loaders/configurable.py,sha256=QyyqN_hMBKafx1wyMCcQrykRFY_TfWFU8XLYXztjnFY,44483
|
|
@@ -394,6 +394,7 @@ oscura/loaders/sigrok.py,sha256=BliW7KuLIKpyRkupaZTGlSczVoHWbK0-HBs2n4r5oVM,1302
|
|
|
394
394
|
oscura/loaders/tdms.py,sha256=J1qDLiQMDs_7ayR04g9hsP39hs7LkYy7jJEc57BUPzw,11114
|
|
395
395
|
oscura/loaders/tektronix.py,sha256=V-ns3yCqsXSkeZ1zNXbOZZuViMzYagsiiHr12WeKc4s,26189
|
|
396
396
|
oscura/loaders/touchstone.py,sha256=Cj4KlQQMcSEZuf5M0hg3lFvXvnuPZkcLa8-X-qKEObA,8898
|
|
397
|
+
oscura/loaders/tss.py,sha256=c0xL2h0x09u765qFAxOKpFE8ggOGpc9DwUyRi6r41Y0,13874
|
|
397
398
|
oscura/loaders/validation.py,sha256=JA_vVYQJB4gC9KFKa2lsGV5D-_agmIjP9kE_dXpv4Wk,17835
|
|
398
399
|
oscura/loaders/vcd.py,sha256=y1mFZXTEqJEFfvMf3MYgGk5e16qG7mNlyAQgVLZNwfU,17722
|
|
399
400
|
oscura/loaders/wav.py,sha256=xv6eELWMYflP7OormysAe6-yAiYNzWMInzC6cuUZZmo,9391
|
|
@@ -583,8 +584,8 @@ oscura/workflows/batch/logging.py,sha256=6YkTrOA2OSiRjYHmtRn-lL2wyNpya0sggVkgxsz
|
|
|
583
584
|
oscura/workflows/batch/metrics.py,sha256=InbxaZmFQrANpp_RCcx5F3va55UaiQjhFJ-_SEKAS20,17322
|
|
584
585
|
oscura/workflows/legacy/__init__.py,sha256=PqHw8AIWfP4-1lJEjCDC4c_O8GXYOcWodK9ciOnGJaU,409
|
|
585
586
|
oscura/workflows/legacy/dag.py,sha256=tEhiWmOSdcEYKmrwYUETC0P5yN-_Cy9fEysPwTDVZ3A,12337
|
|
586
|
-
oscura-0.
|
|
587
|
-
oscura-0.
|
|
588
|
-
oscura-0.
|
|
589
|
-
oscura-0.
|
|
590
|
-
oscura-0.
|
|
587
|
+
oscura-0.7.0.dist-info/METADATA,sha256=s6BHYA3LswuBulGUa6yeH-08oWJKWE-gwZkp9n19aIw,34518
|
|
588
|
+
oscura-0.7.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
589
|
+
oscura-0.7.0.dist-info/entry_points.txt,sha256=QLBxd-iTjBQ5HidaVSkLBwvUsqxSG1ZTJ6i-0juu960,48
|
|
590
|
+
oscura-0.7.0.dist-info/licenses/LICENSE,sha256=p1_oEK-oqWDXMFSv5mKbyYkgW-CPbCnFUvdICu490aY,1077
|
|
591
|
+
oscura-0.7.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|