Skip to content

Instantly share code, notes, and snippets.

@queengooborg
Created May 23, 2024 02:50
Show Gist options
  • Save queengooborg/25252b607df2e2afd85127800756aaca to your computer and use it in GitHub Desktop.
Save queengooborg/25252b607df2e2afd85127800756aaca to your computer and use it in GitHub Desktop.
# -*- coding: utf-8 -*-
# Python script to parse Bambu Lab RFID tag data
# Created for https://github.com/Bambu-Research-Group/RFID-Tag-Guide
# Written by Vinyl Da.i'gyu-Kazotetsu (www.queengoob.org)
import sys
from datetime import datetime
COMPARISON_BLOCKS = [1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14]
IMPORTANT_BLOCKS = [0] + COMPARISON_BLOCKS
BYTES_PER_BLOCK = 16
BLOCKS_PER_TAG = 64
TOTAL_BYTES = BLOCKS_PER_TAG * BYTES_PER_BLOCK
def chunkstring(string, length):
return (string[0+i:length+i] for i in range(0, len(string), length))
def process_date(data):
string = bytes_to_string(data)
parts = string.split("_")
if len(parts) < 5:
return string # Not a date we can process, if it's a date at all
return datetime(
year=int(parts[0]),
month=int(parts[1]),
day=int(parts[2]),
hour=int(parts[3]),
minute=int(parts[4])
)
def bytes_to_string(data):
return data.decode('ascii').replace('\x00', ' ').strip()
def bytes_to_hex(data, chunkify = False):
output = data.hex().upper()
return " ".join(chunkstring(output, 2)) if chunkify else output
def bytes_to_int(data):
return int.from_bytes(data, 'little')
class TagLengthMismatchError(TypeError):
def __init__(self, actual_length):
super().__init__(f"The data does not appear to be a valid MIFARE 1K RFID tag (received {actual_length} bytes / {int(actual_length / BYTES_PER_BLOCK)} blocks, expected {TOTAL_BYTES} bytes / {BLOCKS_PER_TAG} blocks).")
class Tag():
def __init__(self, filename, data):
# Check to make sure the data is 1KB
if len(data) != TOTAL_BYTES:
raise TagLengthMismatchError(len(data))
# Store the raw data
self.filename = filename
self.blocks = list(data[0+i:BYTES_PER_BLOCK+i] for i in range(0, len(data), BYTES_PER_BLOCK))
# Parse the data
self.data = {
"uid": bytes_to_hex(self.blocks[0][0:4]),
"filament_type": bytes_to_string(self.blocks[2]),
"detailed_filament_type": bytes_to_string(self.blocks[4]),
"color": "#" + bytes_to_hex(self.blocks[5][0:4]),
"weight_in_grams": bytes_to_int(self.blocks[5][4:6]),
"material_id": bytes_to_string(self.blocks[1][8:16]),
"temperatures": {
"min_hotend": bytes_to_int(self.blocks[6][10:12]),
"max_hotend": bytes_to_int(self.blocks[6][8:10]),
"bed_temp": bytes_to_int(self.blocks[6][6:8]),
"bed_temp_type": bytes_to_int(self.blocks[6][4:6]),
"drying_time": bytes_to_int(self.blocks[6][2:4]),
"drying_temp": bytes_to_int(self.blocks[6][0:2]),
},
"x_cam_info": self.blocks[8][0:12],
"tray_uid": self.blocks[9],
"production_date": process_date(self.blocks[12]),
"unknown_1": self.blocks[5][6:16],
"unknown_2": bytes_to_string(self.blocks[1][0:8]), # Looks like another form of material ID that's slightly more specific
"unknown_3": self.blocks[6][12:16],
"unknown_4": self.blocks[8][12:16],
"unknown_5": self.blocks[10],
"unknown_6": bytes_to_string(self.blocks[13]), # Appears to be some sort of date -- on some tags, this is identical to the production date, but not always
"unknown_7": self.blocks[14],
}
def __str__(self, blocks_to_output = IMPORTANT_BLOCKS):
result = ""
for key in self.data:
result += f"- {key}: {bytes_to_hex(self.data[key]) if type(self.data[key]) == bytes else self.data[key]}\n"
result += '\n'
for b in range(len(self.blocks)):
if b not in blocks_to_output:
continue
result += f"Block {b:02d}: {bytes_to_hex(self.blocks[b], True)} ({self.blocks[b]})\n"
return result[:-1]
def compare(self, other, blocks_to_compare = COMPARISON_BLOCKS):
cmp_result = [[False for i in range(BYTES_PER_BLOCK)] for b in blocks_to_compare]
# Compare all of the blocks
for bi in range(len(blocks_to_compare)):
b = blocks_to_compare[bi]
for i in range(BYTES_PER_BLOCK):
cmp_result[bi][i] = self.blocks[b][i] == other.blocks[b][i]
# Print results
for bi in range(len(cmp_result)):
print("Block {0:02d}: {1}".format(blocks_to_compare[bi], "".join("✅" if i else "❌" for i in cmp_result[bi])))
def load_data(files_to_load, silent = False):
data = []
for filename in files_to_load:
try:
with open(filename, "rb") as f:
newdata = Tag(filename, f.read())
data.append(newdata)
except TagLengthMismatchError:
if not silent: print(f"{filename} not a valid tag, skipping")
return data
def print_data(data, print_comparisons):
for i in range(len(data)):
tag = data[i]
print(tag.filename)
print(tag)
print()
if print_comparisons and i > 0:
tag.compare(data[i-1])
print()
if __name__ == "__main__":
data = load_data(sys.argv[1:])
print_data(data, False)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment