Skip to content

Instantly share code, notes, and snippets.

@snacsnoc
Forked from Spiritdude/blkenvflash
Last active December 22, 2025 20:04
Show Gist options
  • Select an option

  • Save snacsnoc/427d38c23b7cef10b5e0554efa9c59c6 to your computer and use it in GitHub Desktop.

Select an option

Save snacsnoc/427d38c23b7cef10b5e0554efa9c59c6 to your computer and use it in GitHub Desktop.
LuckFox Pico Pro/Max SD card writer from .env.txt
#!/usr/bin/env python3
# blkenvflash_mac_pwrite.py — fast Luckfox TF writer (macOS), uses pwrite()
# fork of BLKENVFLASH by Rene K. Mueller <spiritdude@gmail.com>
# License: MIT
#
# History:
# 2024/06/18: after the hint by A. Schuetz https://github.com/LuckfoxTECH/luckfox-pico/issues/129 was able to make script functional
# 2024/06/10: start
import os, sys, re, subprocess, atexit
VERSION='0.0.4-mac'
CHUNK=4*1024*1024 # 4 MiB
if len(sys.argv)!=2:
print(f"USAGE {sys.argv[0]} </dev/diskN>"); sys.exit(1)
dev=sys.argv[1]
if not dev.startswith("/dev/"): print("Pass a /dev/diskN"); sys.exit(1)
raw_dev=re.sub(r"^/dev/disk","/dev/rdisk",dev)
print(f"== blkenvflash_mac {VERSION} ==")
def run(cmd):
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def true_size(s):
u={'B':1,'K':1024,'M':1024*1024,'G':1024*1024*1024}
m=re.search(r'(\d+)([BKMG])',s); return int(m.group(1))*u[m.group(2)] if m else 0
def nice(n):
for k,v in (('G',1<<30),('M',1<<20),('K',1<<10),('B',1)):
if n>=v and n%v==0: return f"{n//v:,}{k}"
return f"{n:,}B"
@atexit.register
def _done(): print("done.")
if not os.path.exists(".env.txt"):
print("ERROR: .env.txt missing"); sys.exit(1)
print(f"unmounting {dev} ...")
try: run(["diskutil","unmountDisk",re.sub(r"^/dev/rdisk","/dev/disk",dev)])
except subprocess.CalledProcessError: print("WARN: unmountDisk failed (continuing)")
print(f"writing to {dev} (raw={raw_dev})")
fd=os.open(raw_dev, os.O_WRONLY)
try:
with open(".env.txt") as fh:
for l in fh:
m=re.search(r'(blkdevparts|sd_parts)=(\w+)',l)
if not m: continue
_type,_dev=m.group(1),m.group(2)
if _dev!="mmcblk1": continue
parts=re.sub(r'blkdevparts=\w+:','',l).split(',')
c_off=0
for e in parts:
m=(re.search(r'(\d+[KMG])@(\d+[KMG])\((\w+)\)',e) or
re.search(r'(\d+[KMG])\((\w+)\)',e))
if not m: continue
if len(m.groups())==3:
size=true_size(m.group(1)); _off=true_size(m.group(2)); name=m.group(3)
else:
size=true_size(m.group(1)); _off=0; name=m.group(2)
img=f"{name}.img"
if not os.path.exists(img):
print(f"ERROR: missing {img}"); sys.exit(1)
size_actual=os.path.getsize(img)
print(f" mmcblk1: {name}.img size:{size:,}/{nice(size)} (env off:{_off:,}/{nice(_off)}) imgsize:{size_actual:,} ({nice(size_actual)})")
# stream file -> device at byte offset c_off
written=0
with open(img,'rb', buffering=0) as fi:
while True:
buf=fi.read(CHUNK)
if not buf: break
os.pwrite(fd, buf, c_off+written)
written+=len(buf)
c_off += size # advance by declared partition size
os.fsync(fd)
finally:
os.close(fd)
print(f"\nTip: diskutil eject {re.sub(r'^/dev/rdisk','/dev/disk',dev)} before removing the SD.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment