"""Part 12, step 3: report badges and keys on POLL. Needs osdp.py, mfrc522.py and ssd1306.py."""

from machine import Pin, PWM, UART, I2C, unique_id
import time
from mfrc522 import MFRC522
from ssd1306 import SSD1306_I2C
import osdp

ADDRESS = 1
READER_NUMBER = 0
RAW_BITS = 0
IDENTITY = bytes([0x00, 0x00, 0x00, 1, 1]) + unique_id()[-4:] + bytes([0, 1, 0])
CAPABILITIES = bytes([
    3, 1, 0,
    4, 4, 1,
    5, 2, 1,
    6, 1, 1,
    8, 1, 0,
    10, 128, 0,
])
UNKNOWN_COMMAND = bytes([3])
REPLIES = {
    osdp.POLL: (osdp.ACK, b""),
    osdp.ID: (osdp.PDID, IDENTITY),
    osdp.CAP: (osdp.PDCAP, CAPABILITIES),
}

KEYS = [
    ["1", "2", "3", "A"],
    ["4", "5", "6", "B"],
    ["7", "8", "9", "C"],
    ["*", "0", "#", "D"],
]
KEY_SCAN_MS = 20
BADGE_POLL_MS = 100
BADGE_GONE_AFTER_MISSES = 5

uart = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))
transmit_enable = Pin(2, Pin.OUT, value=0)
rows = [Pin(n, Pin.OUT, value=1) for n in (9, 8, 7, 6)]
columns = [Pin(n, Pin.IN, Pin.PULL_UP) for n in (13, 12, 11, 10)]
buzzer = PWM(Pin(0))
buzzer.freq(4000)
reader = MFRC522(spi_id=0, sck=18, mosi=19, miso=16, cs=17, rst=20)
screen = SSD1306_I2C(128, 64, I2C(1, sda=Pin(26), scl=Pin(27)))
screen.write_cmd(0xA0)
screen.write_cmd(0xC0)

def send(frame):
    transmit_enable.value(1)
    uart.write(frame)
    uart.flush()
    transmit_enable.value(0)

def pressed_key():
    found = None
    for row, labels in zip(rows, KEYS):
        row.value(0)
        for column, label in zip(columns, labels):
            if column.value() == 0:
                found = label
        row.value(1)
    return found

def read_badge():
    reader.init()
    status, _ = reader.request(reader.REQIDL)
    if status == reader.OK:
        status, uid = reader.SelectTagSN()
    return uid if status == reader.OK else None

def beep(milliseconds):
    buzzer.duty_u16(32768)
    time.sleep_ms(milliseconds)
    buzzer.duty_u16(0)

def display(*lines):
    screen.fill(0)
    for number, line in enumerate(lines):
        screen.text(line, 0, number * 16)
    screen.show()

pending_badges = []
pending_keys = ""
last_sequence = None
last_reply = b""

def badge_reply(uid):
    bits = len(uid) * 8
    return osdp.RAW, bytes([READER_NUMBER, RAW_BITS, bits & 0xFF, bits >> 8]) + bytes(uid)

def keys_reply(keys):
    return osdp.KEYPAD, bytes([READER_NUMBER, len(keys)]) + keys.encode()

def answer(command):
    global pending_keys
    if command.code == osdp.POLL and pending_badges:
        return badge_reply(pending_badges.pop(0))
    if command.code == osdp.POLL and pending_keys:
        keys, pending_keys = pending_keys, ""
        return keys_reply(keys)
    return REPLIES.get(command.code, (osdp.NAK, UNKNOWN_COMMAND))

def handle(command):
    global last_sequence, last_reply
    repeated = command.sequence == last_sequence and command.sequence != 0
    if not repeated:
        code, data = answer(command)
        last_reply = osdp.reply(ADDRESS, command.sequence, code, data)
    last_sequence = command.sequence
    send(last_reply)

buffer = b""
previous_key = None
previous_badge = None
misses = 0
last_scan = time.ticks_ms()
last_poll = time.ticks_ms()
display("Show badge", "or type PIN")

while True:
    if uart.any():
        buffer += uart.read()
    command, buffer = osdp.parse(buffer)
    if command is not None and command.address == ADDRESS:
        handle(command)

    if time.ticks_diff(time.ticks_ms(), last_scan) >= KEY_SCAN_MS:
        last_scan = time.ticks_ms()
        key = pressed_key()
        if key is not None and key != previous_key:
            beep(40)
            pending_keys += key
        previous_key = key

    if time.ticks_diff(time.ticks_ms(), last_poll) >= BADGE_POLL_MS:
        last_poll = time.ticks_ms()
        uid = read_badge()
        misses = 0 if uid is not None else misses + 1
        if uid is not None and uid != previous_badge:
            pending_badges.append(uid)
            print("Badge queued:", reader.tohexstring(uid))
        if uid is not None or misses > BADGE_GONE_AFTER_MISSES:
            previous_badge = uid

    time.sleep_ms(2)
