Host Interfaces Network MQTT Client Smart Reader
April 13, 2023 at 2:39 AMSmart Reader over MQTT
MQTT (or MQTT/TLS) is the preferred communication channel for Smart Reader operation.
Two MQTT topics are involved:
-
springcard/springcore/{$id}/rdr/evt
for reader to host communication (events) -
springcard/springcore/{$id}/rdr/cmd
for host to reader communication (commands)
The messages preferably use the JSON format. Text messages using the $SCRDR format are supported as well but may be less efficient in term of host-side implementation.
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
64
(MQTT Client, Smart Reader operation) - Set register 02A0 to
00xxxx02xx
(JSON format over the Network link) - Insert the name or the address of the MQTT broker (server) into register 0286
- Enable TLS in register 0288 or leave this register empty for plain mode
- (Optionaly) Confirm the server port in register 0285. If this register is empty, default port 1883 is used for plain mode or default port 8883 for TLS mode
- (Optionaly) Precize the client login register 028B and password register 028C. Leave empty if the broker allows anonymous access or relies on the TLS client certificate.
- (Optionaly) Precize the client options in register 0289. Default values shall be suitable for most MQTT brokers
Sample host software
Use the following Python script as a reference for using the Smart Reader with configuration JSON over MQTT.
Pre-requisites
- Set-up an MQTT broker and allow anonymous access or adapt the script accordingly
- Install Paho MQTT library (command:
python -m pip install paho-mqtt
)
Using the script
- Specify the address or the hostname of the MQTT broker using the
--addr=
parameter, - Provide the
--wink
,--accept
or--deny
parameters if you want the script to send a command to the reader in response to every event.
Source code
# libraries import
from threading import Event
import paho.mqtt.client as mqtt
import logging
import getopt
import tempfile
import json
import time
import ssl
import sys
import os
import re
# helpers import
from helpers.mqtt_events import mqtt_events
from helpers.ctrlccatcher import CtrlCCatcher
# Incoming message callback
def incoming_message( client, mqtt_context, msg ):
# sanity check
if ( client == None ) or ( msg == None ):
return
logging.info( "Incoming message on topic " + msg.topic )
logging.info( str(msg.payload) )
# try load this json content
try:
json_content = json.loads( msg.payload )
except json.JSONDecodeError:
logging.warning( "This is not a json message!" )
return
# json message is correct, check if we can find the TagId field
if mqtt_context[ "reply" ] :
logging.info( "Sending reply: " + str(mqtt_context[ "reply" ]) )
# change for the command topic
reply_topic = re.sub( "/evt$", "/cmd", msg.topic )
logging.info( "Reply topic is " + reply_topic )
client.publish( topic=reply_topic, payload=mqtt_context[ "reply" ] )
# display_help function
def display_help( script_name ):
print( script_name )
print( "-s, --addr=<server> MQTT server (default is localhost)" )
print( "-p, --port=<port> MQTT port (default is 1883 for plain, 8883 for TLS)" )
print( "[--tls] Use secure communication (implicit if CA, certificate or key are set)" )
print( "[--ca=<file>] CA certificate file" )
print( "[--cert=<file>] Client's certificate file" )
print( "[--key=<file>] Client's private key file" )
print( "[--clientid] MQTT Client ID" )
sys.exit()
# main function
def main( script_name, argv ):
# custom settings for AC demo
current_path = os.path.dirname( os.path.realpath( __file__ ) )
addr = "localhost"
port = 0
use_tls = False
keepalive = 60
mqtt_context = {}
client_id = "scrdr_" + next(tempfile._get_candidate_names())
mqtt_context[ "current_path" ] = current_path
mqtt_context[ "reply" ] = False
# options parsing
try:
opts, _ = getopt.getopt(argv,"h",[ "help", "addr=","port=","ca=","cert=","key=","tls","clientid=","wink","accept", "deny" ])
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 == "--addr":
addr = arg
elif opt == "--ca":
use_tls = True
ca = arg
elif opt == "--cert":
use_tls = True
cert = arg
elif opt == "--key":
use_tls = True
key = arg
elif opt == "--tls":
use_tls = True
elif opt == "--clientid":
client_id = arg
elif opt == "--wink":
mqtt_context[ "reply" ] = b"{ \"Sequence\": \"0E\" }"
elif opt == "--accept":
mqtt_context[ "reply" ] = b"{ \"Sequence\": \"60\" }"
elif opt == "--deny":
mqtt_context[ "reply" ] = b"{ \"Sequence\": \"61\" }"
elif opt == "--port":
try:
port = int(arg)
except ValueError:
print( "Port must be an integer number!" )
sys.exit()
# port?
if use_tls and port <= 0:
port = 8883
else:
port = 1883
# set logging options
logging.basicConfig(level=logging.DEBUG)
# SSL options
if use_tls:
try:
#debug print opnessl version
logging.info( f"using {ssl.OPENSSL_VERSION}" )
ssl_context = ssl.create_default_context()
ssl_context.load_verify_locations( cafile=ca )
ssl_context.load_cert_chain( certfile=cert, keyfile=key )
except Exception:
print( "Could not create TLS context!" )
sys.exit()
# CTRL+C catcher thread (this could be useful on some Windows platforms)
ctrlccatch = CtrlCCatcher()
ctrlccatch.start()
# process exit flag
mqtt_context[ "exit_event" ] = Event()
mqtt_context[ "exit_event" ].clear()
# callback declaration
mqtt_context[ "callback" ] = incoming_message
# select on which topics to subscribe (topic, qos)
mqtt_context[ "subscriptions" ] = [
( "springcard/springcore/+/rdr/evt", 0 ),
]
# start our MQTT client process
client = mqtt.Client( client_id=client_id )
if use_tls:
client.tls_set_context( context=ssl_context )
client.user_data_set( mqtt_context )
client.on_connect = mqtt_events.on_connect
client.on_disconnect = mqtt_events.on_disconnect
client.on_message = mqtt_events.on_message
logging.info( f"Trying to connect to {addr} on port {port}" )
logging.info( f"Client ID is {client_id}" )
try:
client.connect( host=addr, port=port, keepalive=keepalive )
except ( TimeoutError, ConnectionRefusedError ):
logging.error( "Unable to contact this server!" )
sys.exit()
# start mqtt processing thread
client.loop_start()
# this is the main loop, it does nothing but wait for a quit indication
while ctrlccatch.wait() and not mqtt_context[ "exit_event" ].is_set():
# no need to eat all cpus
time.sleep(0.5)
# stop the client
client.loop_stop()
# 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