$SCRDR over UDP

When the SpringCore device is configured for Smart Reader mode + $SCRDR over UDP, it sends $SCRDR Messages to a remote UDP server, and opens a local UDP port where it can receive $SCCMD Messages.

Configuration checklist

  • Define the IPv4 configuration of the device in register 0280. Leave this register empty to get the configuration from DHCP.
  • Set register 02C0 to 44 (UDP server, Smart Reader operation)
  • Set register 02A0 to 00xxxx01xx (SCRDR format over the Network link)
  • Insert the name or the address of the host into register 0294. Leave empty or set to 255.255.255.255 for broadcast.
  • (Optionaly) define the options in register 0295. Check that the $SCCMD Message comes from the expected source to increase the security of the system.
  • (Optionaly) define the Port for UDP and TCP servers in register 0285. Default port is 4000. Host and device must use the same port.

Security considerations

This protocol is intrinsically insecure: it provides neither confidentiality nor integrity protection. It must therefore only be used on a trusted local or private network.

Sample host software

Use the following Python scripts as a reference for using the Smart Reader with configuration $SCRDR over UDP.

Listener

The Listener script is an UDP server, running on the host, that receives and shows any text message coming on port 4000.

NB: since this sample script doesn’t do any processing of the message but only displays it, it is suitable for both $SCRDR and JSON formats.

# libraries import
from threading import Thread, Event
import logging
import socket
import getopt
import sys
import os


# helpers import
from helpers.ctrlccatcher import CtrlCCatcher


# display_help function
def display_help( script_name ):

    print( script_name )
    print( "[--port=<port>]                 UDP port to listen to (default: 4000)" )
    sys.exit()


# main function  
def main( script_name, argv ):
    
    # default settings
    port = 4000

    # options parsing
    try:
        opts, _ = getopt.getopt(argv,"h",[ "help", "port=" ])
    except getopt.GetoptError as e:
        print(e)
        display_help( script_name )

    for opt, arg in opts:
        if opt in ( "-h", "--help"):
            display_help( script_name )
        elif opt == "--port":
            try:
                port = int(arg)
            except ValueError:
                print( "Port must be an integer number!" )
                sys.exit()

    # set logging options
    logging.basicConfig(level=logging.DEBUG)

    # CTRL+C catcher thread (this could be useful on some Windows platforms)
    ctrlccatch = CtrlCCatcher()
    ctrlccatch.start()

    # Create an UDP listening socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.setblocking(0)
    sock.settimeout(0.5)

    # Bind the socket to the port
    server_address = ("0.0.0.0", port)
    logging.info( f"starting up on {server_address} port {port}" )
    sock.bind(server_address)

    # Messages are read from the socket using recvfrom(), which returns the data as well as the address of the client from which it was sent.
    logging.info( "waiting for messages" )
    while ctrlccatch.wait():
        
        try:
            data, address = sock.recvfrom(4096)        
            if data:
                data = data.decode( "ascii" )
                logging.info( data )
        except socket.timeout:
            pass

    # stop all process and exit
    logging.info( "Cleaning..." )
    ctrlccatch.terminate()
    ctrlccatch.join()
    logging.info( "Done." )


# entry point
if __name__ == "__main__":
    # add "this script" folder to the library search path
    # could be useful on Windows platform
    sys.path.append( os.path.dirname( os.path.realpath( __file__ ) ) )
  
    # start our application
    main( os.path.basename( __file__ ), sys.argv[1:] )
    
# EOF

Sender

The Sender script is an UDP client, running on the host, that sends a $SCCMD Message to a target device, or broadcasts the Message.

For demonstration purpose, we send the $SCCMD;SEQ:0E message to play the Wink sequence.

Change message to $SCCMD;SEQ=60 for the Access Granted sequence or $SCCMD;SEQ=61 for Access Denied. For a complete list of sequences, refer to the PLAY SEQUENCE instruction.

# libraries import
from threading import Thread, Event
import socket
import getopt
import sys
import os


# helpers import
from helpers.ctrlccatcher import CtrlCCatcher


# display_help function
def display_help( script_name ):

    print( script_name )
    print( "[--addr=<addr>]                 Address of the target device (default: broadcast)" )
    print( "[--port=<port>]                 UDP port on the target device (default: 4000)" )
    print( "[--message=\"<message>\"]       Message to send to the device (default: WINK Sequence)" )
    sys.exit()


# main function  
def main( script_name, argv ):
    
    # default settings
    message = b"$SCCMD;SEQ=0E"
    addr = '<broadcast>'
    port = 4000

    # options parsing
    try:
        opts, _ = getopt.getopt(argv,"h",[ "help", "addr=", "port=", "message=" ])
    except getopt.GetoptError as e:
        print(e)
        display_help( script_name )

    for opt, arg in opts:
        if opt in ( "-h", "--help"):
            display_help( script_name )
        elif opt == "--message":
            message = bytearray(arg.encode())
        elif opt == "--addr":
            addr = str(arg)
        elif opt == "--port":
            try:
                port = int(arg)
            except ValueError:
                print( "Port must be an integer number!" )
                sys.exit()

    # Create an UDP socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
    # Enable broadcasting mode
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
    # Send the message to the target address and port
    sock.sendto(message, (addr, port))

# entry point
if __name__ == "__main__":
    # add "this script" folder to the library search path
    # could be useful on Windows platform
    sys.path.append( os.path.dirname( os.path.realpath( __file__ ) ) )
  
    # start our application
    main( os.path.basename( __file__ ), sys.argv[1:] )
    
# EOF