Flashing morse code on a LED implant

Here’s yet another version of the script: this one can talk to an ACR122U through the PS/SC, or directly to any PN53x reader through USB (including an ACR122U):

#!/usr/bin/python3
"""Small program to flash a morse code message on an NFC LED implant using
either a ACR122U reader through PC/SC, or a PN53x-based USB reader directly
(including a ACR122U)

The pyscard module is required for operation with PC/SC
The nfcpy module is required for direct USB operation with a PN53x-based reader
"""

### Parameters
default_interface = "pn53x_usb"
default_wpm       = 10 #words per minute
msg_start_pause   = "   "
msg_end_pause     = "   "



### Modules
import sys
import argparse
from time import sleep
from datetime import datetime



### Defines
# Morse code dictionary
morsecode = {
	"A":     ".-",
	"B":     "-...",
	"C":     "-.-.",
	"D":     "-..",
	"E":     ".",
	"F":     "..-.",
	"G":     "--.",
	"H":     "....",
	"I":     "..",
	"J":     ".---",
	"K":     "-.-",
	"L":     ".-..",
	"M":     "--",
	"N":     "-.",
	"O":     "---",
	"P":     ".--.",
	"Q":     "--.-",
	"R":     ".-.",
	"S":     "...",
	"T":     "-",
	"U":     "..-",
	"V":     "...-",
	"W":     ".--",
	"X":     "-..-",
	"Y":     "-.--",
	"Z":     "--..",
	"1":     ".----",
	"2":     "..---",
	"3":     "...--",
	"4":     "....-",
	"5":     ".....",
	"6":     "-....",
	"7":     "--...",
	"8":     "---..",
	"9":     "----.",
	"0":     "-----",
	"=":     "-...-",
	"/":     "-..-.",
	"?":     "..--..",
	",":     "--..--",
	".":     ".-.-.-",
	":":     "---...",
	"'":     ".----.",
	'"':     ".-..-.",
	"_":     "..--.-",
	"(":     "-.--.",
	")":     "-.--.-",
	"#":     "-.---",
	"-":     "-....-",
	"|":     "...-..",
	"\\":    "-.....",
	"*":     "-----.",
	";":     "-.-.-.",
	"@":     ".--.-.",
	"^":     "....--.-.",
	"$":     "...-..-",
	"!":     "....-.",
	">":     "....---.",
	"]":     "....-....",
	"[":     "....-..",
	"<":     "....-.-..",
	"&":     "....--.",
	"%":     "....-.--.",
	"~":     "....--",
	"+":     ".-.-.",
	"{":     "....-.--",
	"}":     "....--..-",
	"[AR]":  ".-.-.",
	"[AS]":  ".-...",
	"[BK]":  "-...-.-",
	"[BT]":  "-...-",
	"[KA]":  "-.-.-",
	"[CL]":  "-.-..-..",
	"[KN]":  "-.--.",
	"[VA]":  "...-.-",
	"[VE]":  "...-.",
	"[GR]":  "--..-.",
	"[HM]":  "....--",
	"[IX]":  "..-..-",
	"[IMI]": "..--..",
	"[INT]": "..-.-",
	"[SOS]": "...---..."}

# CCID escape command
ioctl_ccid_escape_code = 1	# 1 for PCSC-Lite, 3500 for Windows

# ACR122U pseudo-APDU commands
cmd_get_fw_revision = [0xff, 0x00, 0x48, 0x00, 0x00]
cmd_disable_polling = [0xff, 0x00, 0x51, 0x00, 0x00]
cmd_enable_polling  = [0xff, 0x00, 0x51, 0xff, 0x00]

# PN53x commands, wrapped in ACR122U direct transmit pseudo-APDUs
cmd_rf_field_off    = [0xff, 0x00, 0x00, 0x00, 0x04, 0xd4, 0x32, 0x01, 0x00]
cmd_rf_field_on     = [0xff, 0x00, 0x00, 0x00, 0x04, 0xd4, 0x32, 0x01, 0x01]



### Routines
def send_acr122u_control_command(cmd, hcard):
  """Send the ACR122U a control command, return the raw result and raise an
  exception in case of error
  """

  hresult, response = SCardControl(hcard, ioctl_ccid_escape, cmd)
  if hresult != SCARD_S_SUCCESS:
     raise SystemError("Failure to control: {}".format(
			SCardGetErrorMessage(hresult)))
  return(response)



def nfc_morse_player(do_pcsc, wpm, msg):
  """Morse code player
  """

  if not msg:
    print("Nothing to do!")
    return(0)

  # Add a pause at the beginning and at the end of the message
  msg = msg_start_pause + msg + msg_end_pause

  # Turn the message into a sequence of field-on durations (positive) and
  # field-off durations (negative)
  msg = msg.upper()
  morsechars = []
  i = 0
  while i < len(msg):

    if msg[i] == "[":
      j = msg.find("]", i + 1)
      if j > 0 and msg[i : j + 1] in morsecode:
        morsechars.append(morsecode[msg[i : j + 1]])
        i = j + 1
        continue

    if msg[i] == " " or msg[i] == "\t":
      morsechars.append(" ")

    elif msg[i] in morsecode:
      morsechars.append(morsecode[msg[i]])

    else:
      print("Untranslatable in morse code: {} - dropped".format(msg[i]))

    i += 1

  ditlen = 1.2 / wpm 
      
  morseseq = []
  for mc in morsechars:

    if mc == " ":
      if morseseq:
        morseseq[-1] = -ditlen * 7
      else:
        morseseq.append(-ditlen * 7)
      continue

    for c in mc:
      if c == ".":
        morseseq.append(ditlen)
      else:
        morseseq.append(ditlen * 3)
      morseseq.append(-ditlen)

    morseseq[-1] = -ditlen * 3



  # Set things up using the PC/SC interface
  if do_pcsc:

    # Establish context and connect to the reader
    try:
      hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
      hresult, hcard, dwActiveProtocol = SCardConnect(hcontext,
					'ACS ACR122U PICC Interface 00 00',
					SCARD_SHARE_DIRECT, SCARD_PROTOCOL_T0)
    except:
      print("Cannot connect to ACR122U")
      return(-1)

    # Get the ACR122U's firmware revision number, to test that CCID escape has
    # been enabled and to double-check that the reader is indeed an ACR122U
    try:
      fwrev = "".join([chr(v) for v in send_acr122u_control_command(
		cmd_get_fw_revision, hcard)])
    except:
      print("Error getting ACR122U firmware revision number.")
      print("Is the CCID exchange command allowed in /etc/libccid_Info.plist")
      return(-2)
    
    if fwrev[:7].upper() != "ACR122U":
      print('Error: "{}" does not appear to be an ACR122U'.format(fwrev))
      return(-3)

    # Disable polling so the reader won't try to read the implant when the field
    # is turned on
    try:
      send_acr122u_control_command(cmd_disable_polling, hcard)
    except:
      print("Error disabling polling")
      return(-4)

  # Set things up using the direct PN53x USB interface
  else:

    # Open the PN53x USB reader
    try:
      clf = ContactlessFrontend()
      if not clf.open("usb"):
        raise SystemError
    except:
      print("Error opening the PN53x USB reader")
      return(-1)



  # Play the morse code sequence
  warn_if_too_fast = True
  for d in morseseq:

    # Flip the RF field on or off
    cmd_start_tstamp = datetime.now().timestamp()
    try:
      if do_pcsc:	# Use the PC/SC interface
        send_acr122u_control_command(cmd_rf_field_off if d < 0 \
					else cmd_rf_field_on, hcard)
      else:		# Use the direct PN53x USb interface
        clf.device.chipset.rf_configuration(0x01, b"\00" if d < 0 else b"\01")
    except:
      print("Error switching the RF field {}".format("off" if d < 0 else "on"))
      return(-5)
    cmd_duration = datetime.now().timestamp() - cmd_start_tstamp

    # Wait for however long it takes to match the duration in the sequence
    remaining_wait = abs(d) - cmd_duration
    if remaining_wait > 0:
      sleep(remaining_wait)
    else:
      if warn_if_too_fast:
        print("Warning: {} WPM is too fast for the ACR122U and/or your " \
		"computer.".format(wpm))
        print("Morse code timing may appear incorrect")
        warn_if_too_fast = False        



  # Clean things up using the PC/SC interface
  if do_pcsc:

    # Re-enable the RF field and re-enable polling so the reader can work again
    # as a regular reader
    try:
      send_acr122u_control_command(cmd_rf_field_on, hcard)
    except:
      print("Error re-enabling the RF field")
      return(-5)

    try:
      send_acr122u_control_command(cmd_enable_polling, hcard)
    except:
      print("Error re-enabling polling")
      return(-6)

  # Clean things up using the direct PN53x USb interface
  else:

    clf.close()

      

### Main routine
if __name__ == "__main__":

  # Read the command line arguments
  argparser = argparse.ArgumentParser()
  argparser.add_argument(
	  "-i", "--interface",
	  type = str,
	  help = "Reader interface (default: {})".format(default_interface),
	  choices = ("pcsc", "pn53x_usb"),
	  default = default_interface,
          required = False 
	)
  argparser.add_argument(
	  "-w", "--words-per-minute",
	  type = int,
	  help = "Rate of the morse code (default: {})".format(default_wpm),
	  default = default_wpm,
          required = False
	)
  argparser.add_argument(
	  "message",
	  type = str,
	  help = "Text to output in morse code on LED implant",
	)
  args = argparser.parse_args()

  do_pcsc = args.interface == "pcsc"

  # Conditionally import the relevant module here, so the user doesn't have to
  # install the other if they don't plan on using that particular interface
  if do_pcsc:
    from smartcard.scard import *	# From the pyscard package

    # We can only set this variable here
    ioctl_ccid_escape = SCARD_CTL_CODE(ioctl_ccid_escape_code)

  else:
    from nfc import *			# From the nfcpy package

  sys.exit(nfc_morse_player(do_pcsc, args.words_per_minute, args.message))

Essentially, any USB PN53x reader that works with libnfc will work with the direct USB mode. The reason I added it is because I wanted to use my DL533N-XL long-range reader to drive my doNExT’s LEDs, and the DL533N-XL isn’t supported by PCSC-Lite.

And look how gloriously it works: my arm is about half an inch off the reader and it lights up like a Christmas tree!

https://www.dailymotion.com/embed/video/x7w68qk

1 Like