Captures everything needed to redeploy the two-display clock (hour on I2C 0x61, minute on I2C 0x63) on a fresh Pi: - Both systemd units (matrix0x61.service, matrix0x63.service) - Deployed Pimoroni script tree, including the local %I (12-hour) clock customization - Vendored upstream sources (ltp305-python, breakout-garden) so restore is fully offline-capable - Boot config snippet enabling I2C - install.sh that wires it all back up idempotently - Inventory doc cross-referencing every live-system path Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
124 lines
3.5 KiB
Python
Executable File
124 lines
3.5 KiB
Python
Executable File
#!/usr/bin/env python
|
|
import smbus
|
|
import sys
|
|
|
|
I2C_BUS = 1
|
|
DEBUG = False
|
|
|
|
install_mode = False
|
|
|
|
if len(sys.argv) > 1:
|
|
if "--install" in sys.argv:
|
|
install_mode = True
|
|
|
|
try:
|
|
bus = smbus.SMBus(I2C_BUS)
|
|
except IOError:
|
|
print("Unable to access /dev/i2c-{}, please ensure i2c is enabled!".format(I2C_BUS))
|
|
sys.exit()
|
|
|
|
|
|
def check_chip_id(i2c_addr, chip_ids):
|
|
if len(chip_ids) == 0:
|
|
return True
|
|
|
|
for register in chip_ids:
|
|
value, size, negate_match = chip_ids[register]
|
|
reg, reg_size = register
|
|
if reg_size == 1:
|
|
if size == 1:
|
|
read = bus.read_byte_data(i2c_addr, reg)
|
|
elif size == 2:
|
|
read = bus.read_word_data(i2c_addr, reg)
|
|
else:
|
|
raise RuntimeError("Unsupported Chip ID size: {} byte(s)".format(size))
|
|
|
|
if negate_match and value != read:
|
|
return True
|
|
elif value == read:
|
|
return True
|
|
|
|
else:
|
|
# TODO: Support 16-bit registers
|
|
raise RuntimeError("Unsupported register size: {} byte(s)".format(size))
|
|
|
|
return False
|
|
|
|
|
|
def get_device(line):
|
|
parts=[x.strip() for x in line.split(":")]
|
|
|
|
i2c_addr = int(parts[0][0:4], 16)
|
|
|
|
chip_ids = {}
|
|
|
|
if(len(parts[0]) > 4):
|
|
register_map = parts[0][5:-1].split(',')
|
|
for mapping in register_map:
|
|
if '!=' in mapping:
|
|
register, value = [(int(x, 16), (len(x) - 2) // 2) for x in mapping.split('!=')]
|
|
chip_ids[register] = value[0], value[1], True
|
|
else:
|
|
register, value = [(int(x, 16), (len(x) - 2) // 2) for x in mapping.split('=')]
|
|
chip_ids[register] = value[0], value[1], False
|
|
|
|
return i2c_addr, parts[1], parts[2], parts[3], chip_ids
|
|
|
|
devices = [get_device(line) for line in open("breakouts.config").read().strip().split("\n")]
|
|
|
|
addresses = set([device[0] for device in devices])
|
|
|
|
|
|
def identify(find_i2c_addr):
|
|
try:
|
|
bus.read_byte_data(find_i2c_addr, 0x00)
|
|
except IOError as e:
|
|
pass
|
|
|
|
for i2c_addr, library, module, name, chip_ids in devices:
|
|
if i2c_addr == find_i2c_addr and check_chip_id(i2c_addr, chip_ids):
|
|
installed = True
|
|
try:
|
|
__import__(module)
|
|
except ImportError:
|
|
installed = False
|
|
return installed, library, name
|
|
|
|
return None, None, None
|
|
|
|
|
|
found_addr = []
|
|
found_devices = {}
|
|
|
|
for i2c_addr in addresses:
|
|
try:
|
|
bus.read_byte_data(i2c_addr, 0x00)
|
|
if DEBUG: print("Found device on: {:02x}".format(i2c_addr))
|
|
found_addr.append(i2c_addr)
|
|
installed, library, name = identify(i2c_addr)
|
|
if installed is None:
|
|
continue
|
|
if name not in found_devices:
|
|
found_devices[name] = [installed, library, [i2c_addr]]
|
|
else:
|
|
found_devices[name][2].append(i2c_addr)
|
|
|
|
except IOError as e:
|
|
if DEBUG: print("IOError reading: {:02x}".format(i2c_addr))
|
|
continue
|
|
|
|
for name in found_devices:
|
|
installed, library, i2c_addresses = found_devices[name]
|
|
format_string = ""
|
|
if install_mode:
|
|
format_string = "{name}|{library}|{installed}"
|
|
else:
|
|
format_string = "{i2c_addresses}: {name} ({library} {installed})"
|
|
|
|
print(format_string.format(
|
|
i2c_addresses = ",".join(["0x{:02x}".format(i2c_addr) for i2c_addr in i2c_addresses]),
|
|
name = name,
|
|
library = library,
|
|
installed = "installed" if installed else "required"
|
|
))
|