Skip to content

Commit 5ad5093

Browse files
authored
Merge pull request #49 from Nakakiyo092/feature/refactor
Refactoring
2 parents e2ec881 + 2eabd63 commit 5ad5093

1 file changed

Lines changed: 106 additions & 113 deletions

File tree

src/reader.py

Lines changed: 106 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@
103103
}
104104

105105

106-
def get_argparser():
106+
def _get_argparser():
107107
"""Get the command line argument parser."""
108108

109109
parser = argparse.ArgumentParser(
@@ -128,7 +128,7 @@ def get_argparser():
128128
return parser
129129

130130

131-
def create_bus(args):
131+
def _create_bus(args):
132132
"""Create and return a CAN bus, or None if initialization fails."""
133133
try:
134134
if args.devicename == "virtual":
@@ -158,94 +158,98 @@ def create_bus(args):
158158
return None
159159

160160

161-
def read_all_dids(args, bus, notifier):
162-
"""Read all EDR DIDs via 11bits functional, 11bits physical, and 29bits addresses."""
163-
164-
# Abbreviated name
165-
func = isotp.TargetAddressType.Functional
166-
phys = isotp.TargetAddressType.Physical
167-
168-
# Read with 11bits functional address (See GB39732-2020 for the address values)
169-
# rxid=0x700 is a dummy; the functional broadcast (txid=0x7DF) does not listen on a fixed ID.
170-
tx_addr = isotp.Address(isotp.AddressingMode.Normal_11bits, txid=_TX_FUNC_11BIT, rxid=0x700)
171-
rx_addrs = []
172-
# Pre-allocate one receive stack per plausible physical CAN ID pair in the 0x700–0x7FF range.
173-
# GB39732-2020 physical pairs follow the convention: ECU TX = tester TX + 8
161+
def _build_11func(bus, notifier, params):
162+
"""11bits functional: emit-only tx_stack on 0x7DF + per-ECU rx_stacks (0x700-0x7FF)."""
163+
# rxid=0x700 is a placeholder to satisfy Address validation; no ECU transmits on it.
164+
tx_addr = isotp.Address(
165+
isotp.AddressingMode.Normal_11bits, txid=_TX_FUNC_11BIT, rxid=0x700
166+
)
167+
tx_stack = isotp.NotifierBasedCanStack(
168+
bus=bus, notifier=notifier, address=tx_addr, params=params
169+
)
170+
# Pre-allocate one rx stack per plausible per-ECU physical pair (DESIGN.md library-gap),
171+
# so the FF and the subsequent FC/CFs are received on the responder's own pair.
172+
rx_stacks = []
174173
for i in range(0x100 - 0x8):
175-
rx_addrs.append(isotp.Address(
176-
isotp.AddressingMode.Normal_11bits, txid=0x700 + i, rxid=0x700 + i + 8))
174+
rx_addr = isotp.Address(
175+
isotp.AddressingMode.Normal_11bits, txid=0x700 + i, rxid=0x700 + i + 8
176+
)
177+
rx_stacks.append(isotp.NotifierBasedCanStack(
178+
bus=bus, notifier=notifier, address=rx_addr, params=params
179+
))
180+
return tx_stack, rx_stacks, isotp.TargetAddressType.Functional, "11bits functional address"
177181

178-
try:
179-
for did in _EDR_DID_LIST:
180-
payload = read_did(did, bus, notifier, tx_addr, rx_addrs, func, _ISOTP_PARAMS, args.timeout)
181-
output_data(payload)
182-
except Exception as err:
183-
print(err)
184182

185-
# Read with 11bits physical address (See GB39732-2020 for the address values)
186-
tx_addr = isotp.Address(isotp.AddressingMode.Normal_11bits, txid=_TX_PHYS_11BIT, rxid=_RX_PHYS_11BIT)
187-
rx_addrs = []
183+
def _build_11phys(bus, notifier, params):
184+
"""11bits physical: single symmetric stack used for both send and receive."""
185+
addr = isotp.Address(
186+
isotp.AddressingMode.Normal_11bits, txid=_TX_PHYS_11BIT, rxid=_RX_PHYS_11BIT
187+
)
188+
stack = isotp.NotifierBasedCanStack(
189+
bus=bus, notifier=notifier, address=addr, params=params
190+
)
191+
return stack, [stack], isotp.TargetAddressType.Physical, "11bits physical address"
188192

189-
try:
190-
for did in _EDR_DID_LIST:
191-
payload = read_did(did, bus, notifier, tx_addr, rx_addrs, phys, _ISOTP_PARAMS, args.timeout)
192-
output_data(payload)
193-
except Exception as err:
194-
print(err)
195193

196-
# Read with 29bits address (See GB39732-2020 for the address values)
194+
def _build_29bit(bus, notifier, params):
195+
"""29bits NormalFixed: emit-only broadcast tx_stack + per-ECU rx_stacks."""
197196
tx_addr = isotp.Address(
198197
isotp.AddressingMode.NormalFixed_29bits,
199198
target_address=_BROADCAST_29BIT,
200-
source_address=_TESTER_ADDR
199+
source_address=_TESTER_ADDR,
200+
)
201+
tx_stack = isotp.NotifierBasedCanStack(
202+
bus=bus, notifier=notifier, address=tx_addr, params=params
201203
)
202-
rx_addrs = []
204+
# Cover every plausible responder address (excluding the OBD functional broadcast).
205+
rx_stacks = []
203206
for i in range(0xF0):
204-
if i != _OBD_FUNC_ADDR:
205-
rx_addrs.append(isotp.Address(
206-
isotp.AddressingMode.NormalFixed_29bits,
207-
target_address=i,
208-
source_address=_TESTER_ADDR
209-
))
210-
211-
try:
212-
for did in _EDR_DID_LIST:
213-
payload = read_did(did, bus, notifier, tx_addr, rx_addrs, func, _ISOTP_PARAMS, args.timeout)
214-
output_data(payload)
215-
except Exception as err:
216-
print(err)
207+
if i == _OBD_FUNC_ADDR:
208+
continue
209+
rx_addr = isotp.Address(
210+
isotp.AddressingMode.NormalFixed_29bits,
211+
target_address=i,
212+
source_address=_TESTER_ADDR,
213+
)
214+
rx_stacks.append(isotp.NotifierBasedCanStack(
215+
bus=bus, notifier=notifier, address=rx_addr, params=params
216+
))
217+
return tx_stack, rx_stacks, isotp.TargetAddressType.Functional, "29bits address"
217218

218219

219-
def read_did(did, bus, notifier, tx_addr, rx_addrs, addr_type, isotp_params,
220-
timeout=_DEFAULT_TIMEOUT_S) -> bytearray | None:
220+
def _read_all_dids(args, bus, notifier):
221+
"""Read all EDR DIDs via 11bits functional, 11bits physical, and 29bits addresses."""
222+
for builder in (_build_11func, _build_11phys, _build_29bit):
223+
try:
224+
tx_stack, rx_stacks, addr_type, mode_label = builder(
225+
bus, notifier, _ISOTP_PARAMS
226+
)
227+
# The 11bits physical builder returns the same instance as tx_stack and
228+
# rx_stacks[0]; set() dedupes so start() / stop() run once per stack.
229+
# python-can-isotp's TransportLayer is designed for long-lived stacks:
230+
# construct once per scheme, reuse across DIDs, tear down at the end.
231+
all_stacks = {tx_stack, *rx_stacks}
232+
for s in all_stacks:
233+
s.start()
234+
try:
235+
for did in _EDR_DID_LIST:
236+
payload = _read_did(
237+
did, tx_stack, rx_stacks, addr_type, mode_label, args.timeout
238+
)
239+
_output_data(payload)
240+
finally:
241+
for s in all_stacks:
242+
s.stop()
243+
except Exception as err:
244+
print(err)
245+
246+
247+
def _read_did(did, tx_stack, rx_stacks, addr_type, mode_label,
248+
timeout=_DEFAULT_TIMEOUT_S) -> bytearray | None:
221249
"""Read one data by identifier (DID) from the target ECU."""
222250

223251
print("")
224-
if tx_addr.is_tx_29bits():
225-
print("Reading data id", hex(did), "with 29bits address.")
226-
elif addr_type == isotp.TargetAddressType.Functional:
227-
print("Reading data id", hex(did), "with 11bits functional address.")
228-
else:
229-
print("Reading data id", hex(did), "with 11bits physical address.")
230-
231-
# Setup ISOTP stacks
232-
tx_stack = isotp.NotifierBasedCanStack(
233-
bus=bus,
234-
notifier=notifier,
235-
address=tx_addr,
236-
params=isotp_params
237-
)
238-
rx_stacks = []
239-
# In Physical addressing, request and response share the same address pair (tx_addr),
240-
# so rx_addrs can be void. tx_stack is included in rx_stacks to receive the ECU's response.
241-
rx_stacks.append(tx_stack)
242-
for rx_addr in rx_addrs:
243-
rx_stack = isotp.NotifierBasedCanStack(
244-
bus=bus, notifier=notifier,
245-
address=rx_addr,
246-
params=isotp_params
247-
)
248-
rx_stacks.append(rx_stack)
252+
print(f"Reading data id {hex(did)} with {mode_label}.")
249253

250254
# Build the UDS ReadDataByIdentifier request payload.
251255
# didconfig maps DID to a string codec; udsoncan requires it even though we
@@ -262,46 +266,35 @@ def read_did(did, bus, notifier, tx_addr, rx_addrs, addr_type, isotp_params,
262266
data=bytes([(did >> 8) & 0xFF, did & 0xFF]) # DID encoded as big-endian 2-byte
263267
)
264268

265-
# Start stacks
266-
for rx_stack in rx_stacks:
267-
rx_stack.start()
268-
269-
# Send request
269+
# Send request. Stacks are started/stopped by the caller (_read_all_dids)
270+
# once per addressing scheme, not per DID.
270271
tx_stack.send(request.get_payload(), addr_type)
271272

272273
try:
273-
# Wait for response
274-
waiting = True
275-
start_time = time.time()
276-
while waiting:
277-
# Response timeout
278-
if time.time() - start_time > timeout:
279-
payload = None
280-
break
281-
282-
# Check response for all stacks
274+
# Wait for response.
275+
# Non-blocking recv(): a sweep over hundreds of rx_stacks takes microseconds
276+
# when empty, so the user-supplied timeout is honored within ~1 ms granularity.
277+
# monotonic() is immune to wall-clock adjustments mid-wait.
278+
payload = None
279+
deadline = time.monotonic() + timeout
280+
while payload is None and time.monotonic() < deadline:
283281
for rx_stack in rx_stacks:
284-
# TODO: #42
285-
payload = rx_stack.recv(block=True, timeout=0.01)
286-
if payload is not None:
287-
# Compare only the header portion of the received payload against
288-
# the expected positive-response bytes. The remainder is data.
289-
if payload[:len(response)] == response.get_payload():
290-
# Positive response
291-
waiting = False
292-
break
293-
else:
294-
# No Negative response handling. See the DESIGN.md.
295-
pass
296-
282+
received = rx_stack.recv(block=False)
283+
if received is None:
284+
continue
285+
# Compare only the header portion of the received payload against
286+
# the expected positive-response bytes. The remainder is data.
287+
if received[:len(response)] == response.get_payload():
288+
# Positive response
289+
payload = received
290+
break
291+
# Non-matching payload (e.g., negative response). See DESIGN.md.
292+
if payload is None:
293+
time.sleep(0.001)
297294
except Exception as err:
298295
print(err)
299296
return None
300297

301-
# Stop stacks
302-
for rx_stack in rx_stacks:
303-
rx_stack.stop()
304-
305298
if payload is not None:
306299
print(len(payload), "bytes of data received.")
307300
else:
@@ -310,7 +303,7 @@ def read_did(did, bus, notifier, tx_addr, rx_addrs, addr_type, isotp_params,
310303
return payload
311304

312305

313-
def output_data(payload) -> None:
306+
def _output_data(payload) -> None:
314307
"""Output the data to a CSV file according to the format defined in the 'format' folder."""
315308

316309
# Get target did from payload
@@ -379,7 +372,7 @@ def output_data(payload) -> None:
379372
return
380373

381374

382-
def copy_readme():
375+
def _copy_readme():
383376
"""Copy the README file from the format folder to the result folder."""
384377
try:
385378
shutil.copy("format/README.md", "result/README.md")
@@ -397,11 +390,11 @@ def main():
397390
"""Main process."""
398391

399392
# Parse command line arguments
400-
argparser = get_argparser()
393+
argparser = _get_argparser()
401394
args = argparser.parse_args()
402395

403396
# Setup and start a CAN bus
404-
bus = create_bus(args)
397+
bus = _create_bus(args)
405398
if bus is None:
406399
return
407400

@@ -413,14 +406,14 @@ def main():
413406

414407
try:
415408
# Read all EDR DIDs
416-
read_all_dids(args, bus, notifier)
409+
_read_all_dids(args, bus, notifier)
417410
finally:
418411
# Shutdown the CAN bus
419412
notifier.stop()
420413
bus.shutdown()
421414

422415
# Copy the README file
423-
copy_readme()
416+
_copy_readme()
424417

425418

426419
if __name__ == "__main__":

0 commit comments

Comments
 (0)