Skip to content

Instantly share code, notes, and snippets.

@Teraskull
Created June 2, 2024 02:07
Show Gist options
  • Save Teraskull/2bbb2edd1a4f51d5bfcf8bc2ecc45d9b to your computer and use it in GitHub Desktop.
Save Teraskull/2bbb2edd1a4f51d5bfcf8bc2ecc45d9b to your computer and use it in GitHub Desktop.
Python version for Philips Sonicare NFC Password generation.
"""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