Created
June 2, 2024 02:07
-
-
Save Teraskull/2bbb2edd1a4f51d5bfcf8bc2ecc45d9b to your computer and use it in GitHub Desktop.
Python version for Philips Sonicare NFC Password generation.
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
"""Python version for Philips Sonicare NFC Password generation.""" | |
# /// script | |
# requires-python = ">=3.8" | |
# dependencies = [ | |
# "segno", | |
# ] | |
# /// | |
# Made possible with the amazing work of: | |
# @atc1441 at https://gist.github.com/atc1441/41af75048e4c22af1f5f0d4c1d94bb56 | |
# and Cyrill Künzi at https://kuenzi.dev/toothbrush/ | |
# | |
# Datasheet for the NTAG: https://www.nxp.com/docs/en/data-sheet/NTAG213_215_216.pdf | |
# Segno is used to generate a QR code of the NFC command. | |
try: | |
import segno | |
except ImportError: | |
pass | |
# USER VARIABLES. | |
# NTAG UID. Called "Serial Number" when using the "NFC Tools" app. | |
# Found at first three bytes of memory location 0x00 and the whole 0x01, as seen in section 8.5.1 of the datasheet. | |
uid = [0x04, 0x1A, 0xF8, 0xFA, 0x0E, 0x12, 0x91] | |
# Head MFG string, printed at the bottom of the Head. | |
# Found at memory locations from 0x21 to 0x23 without the leading "`T". Notice the space on the 7th character. | |
nfc_second = "230503 81M" | |
# The time to reset the Head to, in seconds. Cannot be larger than 65535 (18 hrs, 12 min, 15 sec), will roll over to 0. | |
# Anything above 21600 will blink the LED to notify you to change the head. | |
time_in_seconds = 0 | |
# Verbose mode. True = output all details, False = output only NFC command and QR code (if Segno is installed). | |
verbose = False | |
# Show QR code of the NFC command. True = show QR code, False = do not show QR code. | |
show_qr = True | |
# DO NOT CHANGE ANYTHING BELOW THIS LINE. | |
def crc16(crc: int, buffer: bytes) -> int: | |
"""Implement the default CRC16 Algo.""" | |
for byte in buffer: | |
crc ^= byte << 8 | |
for _ in range(8): | |
if crc & 0x8000: | |
crc = (crc << 1) ^ 0x1021 | |
else: | |
crc <<= 1 | |
crc &= 0xFFFF | |
return crc | |
def compute_password(uid: bytes, nfc_second: str) -> int: | |
"""Compute the password for Philips Sonicare based on the NTAG UID and MFG string.""" | |
crc_calc = crc16(0x49A3, uid) # Calculate the NTAG UID CRC. | |
nfc_second_bytes = nfc_second.encode("utf-8") # Convert nfc_second to bytes. | |
crc_calc |= crc16(crc_calc, nfc_second_bytes) << 16 # Calculate the MFG CRC and combine with NTAG UID CRC. | |
return ((crc_calc >> 8) & 0x00FF00FF) | ((crc_calc << 8) & 0xFF00FF00) # Rotate the bytes. | |
def format_hex(hex_value: int) -> str: | |
"""Format an integer as a hexadecimal string with colons.""" | |
return ":".join(f"{(hex_value >> i) & 0xFF:02X}" for i in (24, 16, 8, 0)) | |
def calculate_seconds(bytes_: list[int]) -> int: | |
"""Calculate the time in seconds from a list of bytes. | |
Helper function, not used here. | |
""" | |
return (bytes_[1] << 8) + bytes_[0] | |
def calculate_bytes(seconds: int) -> str: | |
"""Calculate the bytes representation of time from seconds.""" | |
return f"{(seconds & 0xFF):02X}:{((seconds >> 8) & 0xFF):02X}:02:00" | |
if __name__ == "__main__": | |
crc_actual = compute_password(uid, nfc_second) | |
formatted_value = format_hex(crc_actual) | |
time_in_hex = calculate_bytes(time_in_seconds) | |
if verbose: | |
print(f"UID: {uid}") | |
print(f"Serial Number: {nfc_second}") | |
print(f"Password: {crc_actual} (0x{crc_actual:08X})") | |
print(f"Full command to reset the Head to {time_in_seconds} seconds ({time_in_seconds // 60} minutes):\n") | |
print(f"1B:{formatted_value},A2:24:{time_in_hex}") | |
if show_qr: | |
print() | |
# Generate QR code if segno is installed. | |
try: | |
qr_code = segno.make_qr(f"1B:{formatted_value},A2:24:{time_in_hex}") | |
qr_code.terminal(border=3, compact=True) | |
except NameError: | |
print("Segno not installed. Cannot show QR code.") | |
print("Install with: pip install segno") | |
# EOF |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment