j0h

SDCard_ListDir.py

j0h
Jun 7th, 2026
125
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 5.55 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. import argparse
  3. import re
  4. import time
  5. import serial
  6.  
  7.  
  8. PORT = "/dev/ttyACM0"
  9. BAUD = 9600
  10.  
  11.  
  12. def bcc_xor(data: bytes) -> bytes:
  13. b = 0
  14. for x in data:
  15. b ^= x
  16. return f"{b:02X}".encode("ascii")
  17.  
  18.  
  19. def frame_bcc(body: bytes) -> bytes:
  20. msg = b"\x05" + body
  21. return msg + bcc_xor(msg) + b"\r"
  22.  
  23.  
  24. def frame_no_bcc(body: bytes) -> bytes:
  25. return b"\x05" + body + b"\r"
  26.  
  27.  
  28. def printable(data: bytes) -> str:
  29. out = []
  30. for b in data:
  31. if 32 <= b <= 126:
  32. out.append(chr(b))
  33. elif b == 0x00:
  34. out.append("<NUL>")
  35. elif b == 0x05:
  36. out.append("<ENQ>")
  37. elif b == 0x06:
  38. out.append("<ACK>")
  39. elif b == 0x15:
  40. out.append("<NAK>")
  41. elif b == 0x0D:
  42. out.append("<CR>")
  43. else:
  44. out.append(f"<{b:02X}>")
  45. return "".join(out)
  46.  
  47.  
  48. def recv_until_cr(ser, timeout=2.0, limit=4096) -> bytes:
  49. end = time.time() + timeout
  50. buf = bytearray()
  51.  
  52. while time.time() < end and len(buf) < limit:
  53. b = ser.read(1)
  54. if not b:
  55. continue
  56. buf += b
  57. if b == b"\r":
  58. break
  59.  
  60. return bytes(buf)
  61.  
  62.  
  63. def strip_reply(rx: bytes) -> bytes:
  64. if rx and rx[0] in (0x06, 0x15) and len(rx) >= 6:
  65. return rx[4:-3]
  66. return b""
  67.  
  68.  
  69. def sd_open_body(path: str) -> bytes:
  70. path = "/" + path.strip("/")
  71. path_bytes = path.encode("ascii") + b"\x00"
  72. length = len(path_bytes)
  73. return b"FF1FR" + f"{length:03X}".encode("ascii") + path_bytes
  74.  
  75.  
  76. def parse_entry(payload: bytes):
  77. payload = payload.rstrip(b"\x00")
  78.  
  79. try:
  80. text = payload.decode("ascii")
  81. except UnicodeDecodeError:
  82. return None
  83.  
  84. m = re.match(
  85. r"^([01])([0-9A-Fa-f]{8})([0-9]{8})([0-9]{6})([0-9A-Fa-f]{3})(.*)$",
  86. text,
  87. )
  88.  
  89. if not m:
  90. return None
  91.  
  92. kind, size_hex, date, tm, name_len_hex, name = m.groups()
  93. name_len = int(name_len_hex, 16)
  94. name = name[:name_len]
  95.  
  96. return {
  97. "is_dir": kind == "1",
  98. "size": int(size_hex, 16),
  99. "date": date,
  100. "time": tm,
  101. "name": name,
  102. "raw": text,
  103. }
  104.  
  105.  
  106. def send_packet(ser, pkt: bytes, label="", debug=False) -> bytes:
  107. if debug:
  108. print()
  109. print("TX", label)
  110. print("TX hex :", pkt.hex(" ").upper())
  111. print("TX txt :", printable(pkt))
  112.  
  113. ser.write(pkt)
  114. ser.flush()
  115.  
  116. rx = recv_until_cr(ser)
  117.  
  118. if debug:
  119. print("RX hex :", rx.hex(" ").upper() if rx else "")
  120. print("RX txt :", printable(rx) if rx else "")
  121. print("DATA :", printable(strip_reply(rx)))
  122.  
  123. return rx
  124.  
  125.  
  126. def list_dir(ser, path: str, debug=False):
  127. ser.reset_input_buffer()
  128.  
  129. open_body = sd_open_body(path)
  130. rx = send_packet(ser, frame_bcc(open_body), "open dir", debug)
  131. payload = strip_reply(rx)
  132.  
  133. try:
  134. count = int(payload.decode("ascii"), 16)
  135. except Exception:
  136. print("Could not decode directory count:", printable(payload))
  137. return []
  138.  
  139. print("COUNT:", count)
  140.  
  141. entries = []
  142.  
  143. for i in range(count):
  144. body = b"FF0FR21" if i == count - 1 else b"FF1FR20"
  145. rx = send_packet(ser, frame_no_bcc(body), f"entry {i+1}/{count}", debug)
  146.  
  147. payload = strip_reply(rx)
  148. entry = parse_entry(payload)
  149.  
  150. if entry:
  151. entries.append(entry)
  152. else:
  153. print("Could not parse entry:", printable(payload))
  154.  
  155. time.sleep(0.02)
  156.  
  157. return entries
  158.  
  159.  
  160. def walk(ser, path: str, recursive=False, debug=False, depth=0):
  161. path = "/" + path.strip("/")
  162. entries = list_dir(ser, path, debug=debug)
  163.  
  164. indent = " " * depth
  165. print(f"{indent}{path}/")
  166.  
  167. for e in entries:
  168. suffix = "/" if e["is_dir"] else ""
  169. size = "" if e["is_dir"] else f" {e['size']} bytes"
  170. print(f"{indent} {e['name']}{suffix}{size}")
  171.  
  172. if recursive:
  173. for e in entries:
  174. if e["is_dir"]:
  175. walk(ser, path + "/" + e["name"], recursive=True, debug=debug, depth=depth + 1)
  176.  
  177.  
  178. def main():
  179. ap = argparse.ArgumentParser()
  180. ap.add_argument("port", nargs="?", default=PORT)
  181. ap.add_argument("--baud", type=int, default=BAUD)
  182. ap.add_argument("--path", default="/FCDATA01/DATALOG/1-secLog")
  183. ap.add_argument("-r", "--recursive", action="store_true")
  184. ap.add_argument("--debug", action="store_true")
  185. args = ap.parse_args()
  186.  
  187. with serial.Serial(
  188. args.port,
  189. args.baud,
  190. bytesize=serial.SEVENBITS,
  191. parity=serial.PARITY_EVEN,
  192. stopbits=serial.STOPBITS_ONE,
  193. timeout=0.05,
  194. write_timeout=1.0,
  195. ) as ser:
  196. time.sleep(0.5)
  197. walk(ser, args.path, recursive=args.recursive, debug=args.debug)
  198.  
  199.  
  200. if __name__ == "__main__":
  201. main()
  202.  
  203. '''
  204. Open/list directory:
  205. ENQ FF1FR <path_len_hex_3> <path> NUL BCC CR
  206.  
  207. Reply:
  208. ACK FF0 <count_hex_4> BCC CR
  209.  
  210. Read next entry:
  211. ENQ FF1FR20 CR
  212. (no BCC)
  213.  
  214. Read final entry:
  215. ENQ FF0FR21 CR
  216. (no BCC)
  217.  
  218.  
  219. ## Eaxmple Usage:
  220. $ ./nav2.py /dev/ttyACM0
  221. COUNT: 31
  222. /FCDATA01/DATALOG/1-secLog/
  223. 20260406/
  224. 20260407/
  225. 20260408/
  226. 20260409/
  227. 20260410/
  228. 20260411/
  229. 20260412/
  230. 20260413/
  231. 20260414/
  232. 20260415/
  233. 20260416/
  234. 20260417/
  235. 20260418/
  236. 20260419/
  237. 20260420/
  238. 20260501/
  239. 20260502/
  240. 20260503/
  241. 20260504/
  242. 20260505/
  243. 20260506/
  244. 20260507/
  245. 20260508/
  246. 20260509/
  247. 20260510/
  248. 20260512/
  249. 20260328/
  250. 20260607/
  251. DETZ/
  252. DERP/
  253. LULZ/
  254.  
  255. '''
  256.  
  257.  
Advertisement
Add Comment
Please, Sign In to add comment