#!/usr/bin/env python3
"""Configure ACR1552U RF power and field pulsing from the command line.

    --power {0,20,40,60,80,100}      RF output power, percent (0 = hardware default)
    --pulse {0,250,500,1000,2500}    field RF-off interval in ms; 0 = continuous field

Pass one or both. What the two args write:
    power   -> Set RF Power     (E0 00 00 50 01 XX)  -- self-resets the reader
    pulse   -> Polling/ATR      (E0 00 00 23 01 XX): 0 clears the RF-Off-Interval bit
               (bit1) for a continuous field; a duration sets bit1 and selects the
               interval via bits 4-5 (250->00  500->01  1000->10  2500->11)

TL;DR: --power applies immediately (reader self-resets); --pulse takes effect only
after a manual unplug/replug.

Examples:
    acr1552u_rf_config.py --power 100 --pulse 0          # full power, field always on
    acr1552u_rf_config.py --power 40  --pulse 500        # 40%, pulse off 500 ms/poll
    acr1552u_rf_config.py --pulse 250                    # just set pulse, leave power
"""

import time
import argparse
from smartcard.System import readers
from smartcard.scard import SCARD_CTL_CODE, SCARD_SHARE_DIRECT

ESCAPE = SCARD_CTL_CODE(3500)

POWER_BYTE = {0: 0x00, 20: 0x01, 40: 0x02, 60: 0x03, 80: 0x04, 100: 0x05}
BYTE_POWER = {v: k for k, v in POWER_BYTE.items()}
MS_SEL = {250: 0, 500: 1, 1000: 2, 2500: 3}   # duration -> bits4-5 value
SEL_MS = {v: k for k, v in MS_SEL.items()}

# Polling/ATR base: bit0 Enable Polling, bit3 extra-MIFARE ATR, bit7 Part4 ATR.
POLL_BASE = 0x89


def SET_POWER(byte):
    return [0xE0, 0x00, 0x00, 0x50, 0x01, byte]


def SET_POLLING(reg):
    return [0xE0, 0x00, 0x00, 0x23, 0x01, reg]


def polling_reg(pulse):
    """0 clears the RF-Off-Interval bit (continuous); a ms value sets bit1 + interval."""
    if pulse == 0:
        return POLL_BASE                       # bit1 clear -> field continuous
    return POLL_BASE | 0x02 | (MS_SEL[pulse] << 4)   # bit1 set + interval bits


def decode_polling(v):
    polling = "on" if v & 0x01 else "off"
    if v & 0x02:
        return polling, "on, %d ms" % SEL_MS[(v >> 4) & 0x03]
    return polling, "off (continuous)"


# --- PC/SC ------------------------------------------------------------------
def _connect_direct(timeout=15.0):
    deadline = time.time() + timeout
    last = None
    while time.time() < deadline:
        try:
            for r in readers():
                if "PICC" in str(r):
                    conn = r.createConnection()
                    conn.connect(mode=SCARD_SHARE_DIRECT)
                    return conn
        except Exception as e:
            last = e
        time.sleep(0.25)
    raise RuntimeError("ACR1552U PICC reader unavailable (resetting?): %s" % last)


def escape(apdu):
    conn = _connect_direct()
    try:
        return conn.control(ESCAPE, apdu)
    except Exception:
        return None
    finally:
        try:
            conn.disconnect()
        except Exception:
            pass


def get_power_byte():
    r = escape([0xE0, 0x00, 0x00, 0x50, 0x00])
    return r[-1] if r else None


def get_polling_byte():
    r = escape([0xE0, 0x00, 0x00, 0x23, 0x00])
    return r[-1] if r else None


# --- main -------------------------------------------------------------------
def parse():
    p = argparse.ArgumentParser(
        description="Configure ACR1552U RF power and field pulsing.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=__doc__)
    p.add_argument("--power", type=int, choices=[0, 20, 40, 60, 80, 100],
                   help="RF output power (percent)")
    p.add_argument("--pulse", type=int, choices=[0, 250, 500, 1000, 2500],
                   help="field RF-off interval in ms; 0 = continuous")
    args = p.parse_args()
    if args.power is None and args.pulse is None:
        p.error("give at least one of --power / --pulse")
    return args


def main():
    args = parse()

    # pulse/duration -> Polling/ATR (no self-reset; needs a manual replug)
    if args.pulse is not None:
        escape(SET_POLLING(polling_reg(args.pulse)))

    # power -> Set RF Power (self-resets the reader once; applies on its own)
    if args.power is not None:
        escape(SET_POWER(POWER_BYTE[args.power]))

    # read back and report actual reader state (NV values)
    pwr, pol = get_power_byte(), get_polling_byte()
    pwr_s = ("%d%%" % BYTE_POWER[pwr]) if pwr in BYTE_POWER else "?"
    polling, pulse = decode_polling(pol) if pol is not None else ("?", "?")
    print("power = %-5s (0x%02X) | polling = %-3s | pulse = %-14s (reg 0x%02X)"
          % (pwr_s, pwr or 0, polling, pulse, pol or 0))

    if args.pulse is not None:
        print("Pulse/duration written to NV -- unplug and replug the reader for it "
              "to take effect.")


if __name__ == "__main__":
    main()
