139 lines
4 KiB
Python
139 lines
4 KiB
Python
import datetime
|
|
import typing
|
|
import imb_table
|
|
|
|
|
|
def get_first_serial() -> int:
|
|
"""
|
|
Generate a 6-digit serial number for an intelligent mail barcode.
|
|
The first 2 digits are the last 2 digits of the julian date, and the next 4
|
|
are serial and the next number to use is stored in a temporary file.
|
|
This will break if over 10000 serials are requested in a day
|
|
"""
|
|
# Last 2 digits of the ordinal day
|
|
date = datetime.datetime.utcnow().timetuple().tm_yday % 100
|
|
try:
|
|
serial_file = open("next_serial.txt")
|
|
next_serial = int(serial_file.read().strip())
|
|
if next_serial // 10000 == date:
|
|
first_idx = next_serial % 10000
|
|
else:
|
|
first_idx = 0
|
|
except (ValueError, IndexError, FileNotFoundError):
|
|
first_idx = 0
|
|
|
|
return date * 10000 + first_idx
|
|
|
|
|
|
def write_current_serial(current_serial: int):
|
|
serial_file = open("next_serial.txt", "w")
|
|
serial_file.write(format(current_serial, "06d"))
|
|
|
|
|
|
def _format_routing(routing: str) -> int:
|
|
if len(routing) == 0:
|
|
return 0
|
|
elif len(routing) == 5:
|
|
return int(routing) + 1
|
|
elif len(routing) == 9:
|
|
return int(routing) + 100000 + 1
|
|
elif len(routing) == 11:
|
|
return int(routing) + 1000000000 + 100000 + 1
|
|
else:
|
|
raise ValueError("Routing code must be 0, 5, 9, or 11 characters")
|
|
|
|
|
|
def _generate_crc(data_int: int) -> int:
|
|
"""
|
|
Do the weird USPS CRC11 which requires precisely 102 bits in
|
|
This is done by copying USPS code which is not very optimal
|
|
"""
|
|
data = data_int.to_bytes(13, "big")
|
|
poly = 0xF35
|
|
fcs = 0x7FF
|
|
|
|
current = data[0] << 5
|
|
for _ in range(6):
|
|
if (fcs ^ current) & 0x400:
|
|
fcs = (fcs << 1) ^ poly
|
|
else:
|
|
fcs = fcs << 1
|
|
fcs &= 0x7FF
|
|
current <<= 1
|
|
|
|
for current in data[1:]:
|
|
current <<= 3
|
|
for _ in range(8):
|
|
if (fcs ^ current) & 0x400:
|
|
fcs = (fcs << 1) ^ poly
|
|
else:
|
|
fcs = fcs << 1
|
|
fcs &= 0x7FF
|
|
current <<= 1
|
|
|
|
return fcs
|
|
|
|
|
|
def _generate_codewords(data: int, crc: int) -> typing.List[int]:
|
|
codewords = []
|
|
codewords.append(data % 636)
|
|
data //= 636
|
|
for i in range(8):
|
|
codewords.append(data % 1365)
|
|
data //= 1365
|
|
codewords.append(data)
|
|
|
|
codewords.reverse()
|
|
codewords[0] += ((crc >> 10) & 1) * 659
|
|
codewords[9] *= 2
|
|
return codewords
|
|
|
|
|
|
def _generate_characters(codewords: typing.List[int], crc: int) -> typing.List[int]:
|
|
characters = []
|
|
for idx, codeword in enumerate(codewords):
|
|
xor = ((crc >> idx) & 1) * 0x1FFF
|
|
characters.append(imb_table.CHARACTER_TABLE[codeword] ^ xor)
|
|
|
|
return characters
|
|
|
|
|
|
def _get_bit(characters: typing.List[int], character: int, bit: int) -> int:
|
|
return (characters[character] >> bit) & 1
|
|
|
|
|
|
def _generate_bars(characters: typing.List[int]) -> str:
|
|
s = ""
|
|
for bar in range(65):
|
|
descender = _get_bit(characters, *imb_table.BAR_TABLE[bar * 2])
|
|
ascender = _get_bit(characters, *imb_table.BAR_TABLE[bar * 2 + 1])
|
|
s = s + "TADF"[descender * 2 + ascender]
|
|
return s
|
|
|
|
|
|
def from_payload(data: int) -> str:
|
|
crc = _generate_crc(data)
|
|
codewords = _generate_codewords(data, crc)
|
|
characters = _generate_characters(codewords, crc)
|
|
return _generate_bars(characters)
|
|
|
|
|
|
def _generate_payload(bi: int, stid: int, mailer: int, serial: int, raw_routing: str):
|
|
if mailer >= 1000000:
|
|
decimal_tracking = int(f"{stid:03d}{mailer:09d}{serial:06d}")
|
|
else:
|
|
# Why are you using this program?
|
|
decimal_tracking = int(f"{stid:03d}{mailer:06d}{serial:09d}")
|
|
|
|
payload = _format_routing(raw_routing)
|
|
|
|
# ... what the fuck usps
|
|
payload = payload * 10 + (bi // 10)
|
|
payload = payload * 5 + (bi % 10)
|
|
payload = payload * 10**18 + decimal_tracking
|
|
return payload
|
|
|
|
|
|
def generate(bi: int, stid: int, mailer: int, serial: int, raw_routing: str):
|
|
return from_payload(_generate_payload(bi, stid, mailer, serial, raw_routing))
|