Created
May 23, 2024 02:50
-
-
Save queengooborg/25252b607df2e2afd85127800756aaca to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- 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