|
|
# RFID reader
|
|
|
|
|
|
As part of the seconde follow up a RFID reader has been added to the demonstration stand.
|
|
|
|
|
|
## Setup
|
|
|
|
|
|
Only one RFID reader is added and due to this it simply is connected to the Raspberry Pi running the Basyx implementation.
|
|
|
|
|
|
Use `docker-compose.rfid.yml` to build and run a Docker container. The docker compose file uses `Rfid.Dockerfile`. The component could be run without Docker.
|
|
|
|
|
|
## Wiring
|
|
|
|
|
|
The used RFID reader can be connected to a Raspberry Pi like this:
|
|
|
|
|
|
| Module pin | Raspberry Pi pin |
|
|
|
|-----|-----|
|
|
|
| SDA (SS) | GPIO8 [pin 24] |
|
|
|
| SCK | GPIO11 [pin 23] |
|
|
|
| MOSI | GPIO10 [pin 19] |
|
|
|
| MISO | GPIO9 [pin 21] |
|
|
|
| IRQ | GPIO24 [pin 18] |
|
|
|
| GND | GND [pin 25] |
|
|
|
| RST | GPIO25 [pin 22] |
|
|
|
| 3.3V | 3V3 [pin 1 or 17] |
|
|
|
|
|
|
|
|
|
## Implementation Basyx
|
|
|
|
|
|
The concept of the implementation is compareable to the one for the light or sensor implementation. An interface ```IRfidReader``` as well as the class ```RfidReader``` implementig it are used. The `RfidController` creates the displays and uses the `RfidSubmodelProvider` to create, register and upload the AAS as well as host and register the submodules. Additionally, `RfidMqttCallback` is used to react to received MQTT messages.
|
|
|
|
|
|
### General
|
|
|
|
|
|
While the Basyx part is written in Java, python scripts are used to access the RFID reader. The reader is directly connected to the Raspberry Pi running the Basyx implementation. But it is possible to use another device to access the display and run the python scripts. In this case one Rasperry Pi could control multiple displays instead of one. The communication between the controller and the reader is realized with MQTT.
|
|
|
|
|
|
### IRfidReader
|
|
|
|
|
|
The file can be found here. The interface includes these methods:
|
|
|
|
|
|
| Method | Purpose |
|
|
|
|-----|-----------|
|
|
|
| `readTag()` | Method to read at most one tag. |
|
|
|
| `writeTag(String text)` | Write text to at most one tag. |
|
|
|
| `startReadTagLoop()` | Start RFID reader in a mode where every tag is read. |
|
|
|
| `subscribeToMqtt(String broker, String topic)` | Subscribe to a MQTT topic and assign the callback |
|
|
|
|
|
|
### RfidReader
|
|
|
|
|
|
The file can be found here. The class implements the interface `IRfidReader`.
|
|
|
|
|
|
MQTT is used to start scripts accessing the RFID reader. The topic determins the python script that will be run.
|
|
|
|
|
|
```java
|
|
|
private void publishMqtt(String topic, String msg){
|
|
|
try{
|
|
|
MqttClient mqttClient = new MqttClient(Common.getMQTTBrokerHost(), "DisplayController");
|
|
|
MqttConnectOptions connOpts = new MqttConnectOptions();
|
|
|
connOpts.setCleanSession(true);
|
|
|
connOpts.setAutomaticReconnect(true);
|
|
|
mqttClient.connect(connOpts);
|
|
|
MqttMessage message = new MqttMessage(msg.getBytes());
|
|
|
message.setQos(0);
|
|
|
mqttClient.publish(topic, message);
|
|
|
}
|
|
|
catch(Exception e){
|
|
|
logger.info("MQTT connection failed", e);
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
This method enables to publish MQTT messages. A MQTT client is initialized and the message constructed. QoS level 0 is used.
|
|
|
|
|
|
```java
|
|
|
@Override
|
|
|
public Map<String, Object> readTag() {
|
|
|
Map<String, Object> map = new HashMap<>();
|
|
|
try{
|
|
|
publishMqtt("rfidReader/RfidReader_"+id+"/read", " ");
|
|
|
|
|
|
Map<String, Object> oldResult = lastResult;
|
|
|
long finish = System.currentTimeMillis() + 60000;
|
|
|
while(System.currentTimeMillis() < finish){
|
|
|
if(!lastResult.equals(oldResult)){
|
|
|
return lastResult;
|
|
|
}
|
|
|
Thread.sleep(1000);
|
|
|
}
|
|
|
}
|
|
|
catch(Exception e){
|
|
|
map.put("error", true);
|
|
|
map.put("message", e);
|
|
|
return map;
|
|
|
}
|
|
|
|
|
|
publishMqtt("rfidReader/RfidReader_"+id+"/stop", " ");
|
|
|
|
|
|
map.put("error", true);
|
|
|
map.put("message", "No chip detected.");
|
|
|
return map;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
A MQTT message is published with the corresponding topic. The previous result read by the RFID reader is stored. For 60s it is compared to the actual value every second. If a new chip is detected and the value changes, the new result is returned as map. If something goes wrong or no chip is placed on the reader within the 60 seconds, a map containing the error is returned. To stop the running python script, an additional message is published. This ensures that the result isn't changed after the timeout.
|
|
|
|
|
|
```java
|
|
|
@Override
|
|
|
public boolean writeTag(String text) {
|
|
|
try{
|
|
|
publishMqtt("rfidReader/RfidReader_"+id+"/write", text);
|
|
|
|
|
|
Map<String, Object> oldResult = lastResult;
|
|
|
long finish = System.currentTimeMillis() + 60000;
|
|
|
while(System.currentTimeMillis() < finish){
|
|
|
if(!lastResult.equals(oldResult)){
|
|
|
return true;
|
|
|
}
|
|
|
Thread.sleep(1000);
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
catch (Exception e){
|
|
|
logger.error("Writing failed: ", e);
|
|
|
}
|
|
|
publishMqtt("rfidReader/RfidReader_"+id+"/stop", " ");
|
|
|
return false;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Changing the text of a tag works similar to reading it. But the MQTT message additionally includes the text to be written as payload. Instead of a map a boolean is returned indicating if the operation was successfull. After writing a tag, it is read causing a change of the last result. This can be used to determine if the text was changed.
|
|
|
|
|
|
```java
|
|
|
@Override
|
|
|
public boolean startReadTagLoop() {
|
|
|
try{
|
|
|
publishMqtt("rfidReader/RfidReader_"+id+"/loop", " ");
|
|
|
return true;
|
|
|
}
|
|
|
catch (Exception e){
|
|
|
logger.error("Start of read tag loop failed: ", e);
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
A MQTT message is send to start the corresponding script.
|
|
|
|
|
|
```java
|
|
|
@Override
|
|
|
public void subscribeToMqtt(String broker, String topic) {
|
|
|
MqttClient mqttClient = null;
|
|
|
try {
|
|
|
mqttClient = new MqttClient(broker, "RfidController");
|
|
|
} catch (MqttException e) {
|
|
|
logger.warn("MQTT client could not be created: " + e.getMessage());
|
|
|
}
|
|
|
connOpts = new MqttConnectOptions();
|
|
|
connOpts.setCleanSession(true);
|
|
|
connOpts.setAutomaticReconnect(true);
|
|
|
try {
|
|
|
RfidMqttCallback callback = new RfidMqttCallback(this, displayUrn);
|
|
|
mqttClient.setCallback(callback);
|
|
|
mqttClient.connect(connOpts);
|
|
|
mqttClient.subscribe(topic);
|
|
|
}
|
|
|
catch (NullPointerException | MqttException e) {
|
|
|
logger.warn("MQTT client could not connect to broker: " + e.getMessage());
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
A MQTT client is initialized and the connection options set. [`RfidMqttCallback`](#mqttrfidcallback) is added to the client as callback.
|
|
|
|
|
|
### RfidController
|
|
|
|
|
|
The file can be found here.
|
|
|
|
|
|
```java
|
|
|
public static void main(String[] args) {
|
|
|
final int HOSTPORT = Common.getHostPort();
|
|
|
rfidReaderAmount = getAmount();
|
|
|
boolean inDocker = Common.inDocker();
|
|
|
|
|
|
for (int i = 0; i < rfidReaderAmount; i++) {
|
|
|
RfidReader rfid = new RfidReader(Integer.toString(i), inDocker);
|
|
|
rfid.subscribeToMqtt(Common.getMQTTBrokerHost(), "rfidReader/RfidReader_" + rfid.getID());
|
|
|
RfidSubmodelProvider rsp = new RfidSubmodelProvider("RfidController:RfidReader_" + rfid.getID());
|
|
|
rsp.hostUploadAndRegister(rfid, "RfidReader_" + rfid.getID(), "RfidReader_" + rfid.getID(), HOSTPORT + i);
|
|
|
rfid.startReadTagLoop();
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
The amount of RFID readers is retrieved from the environment variable. The amount determines the number of readers that are initialized. For each created RFID reader, an AAS and a submodel are created, registered and uploaded using the `RfidSubmodelProvider`. For each RFID reader `subscribeToMqtt()` is called to determine the topic to listen to. This topic is used to receive the read results.
|
|
|
|
|
|
### RfidSubmodelProvider
|
|
|
|
|
|
The file can be found here.
|
|
|
|
|
|
Like in the other submodel providers in the project a method for creating a submodel is defined and the operations are added. For reading a tag once or starting the loop simply the function calling the methods of the rfid reader and the operation need to be set.
|
|
|
|
|
|
```java
|
|
|
Property input = new Property("rfidInput");
|
|
|
input.setModelingKind(ModelingKind.TEMPLATE);
|
|
|
input.setValueType(ValueType.String);
|
|
|
|
|
|
Function<Object[], Object> writeTagFunction = (args) -> rfid.writeTag(args[0].toString());
|
|
|
Operation writeTagOperation = new Operation(writeTagFunction);
|
|
|
writeTagOperation.setIdShort("writeRfid");
|
|
|
writeTagOperation.setInputVariables(Arrays.asList(new OperationVariable(input)));
|
|
|
sensorSubmodel.addSubmodelElement(writeTagOperation);
|
|
|
```
|
|
|
|
|
|
For writing a tag the text has to be passed. To do so, a propterty is initialized and its modeling kind and value type are set additionally. The created submodel is returned and used to host, upload and register it in the following step.
|
|
|
|
|
|
```java
|
|
|
public void hostUploadAndRegister(IRfidReader rfid, String AASID, String AASShortID, int port) {
|
|
|
Submodel sensorModel = createRfidModel(rfid);
|
|
|
super.hostUploadAndRegister(sensorModel, AASID, AASShortID, port);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Because the `hostUploadAndRegister()` method is almost the same for all submodelProviders in this project, I moved it into a seperate class called `GeneralSubmodelProvider`. The `RfidSubmodelProvider` extends this class and passes the created submodel for the LCD to the function of its super class.
|
|
|
|
|
|
### MqttRfidCallback
|
|
|
|
|
|
The file can be found here. The class implements `org.eclipse.paho.client.mqttv3.MqttCallback`. Especially overriding the method `messageArrived` is usefull to react to received MQTT messages.
|
|
|
|
|
|
```java
|
|
|
try{
|
|
|
String msg = mqttMessage.toString();
|
|
|
msg = msg.replace("\n", "");
|
|
|
|
|
|
ObjectMapper mapper = new ObjectMapper();
|
|
|
map = mapper.readValue(msg, HashMap.class);
|
|
|
rfid.setLastResult(map);
|
|
|
}
|
|
|
catch(Exception e){
|
|
|
logger.error("Could not update value of lastResult: ", e);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Whenever a message arrives, the last result of the RFID reader class is updated. Furthermore, the text and the id of the RFID chip are displayed on a specified display.
|
|
|
|
|
|
```java
|
|
|
try{
|
|
|
IConnectorFactory connectorFactory = new HTTPConnectorFactory();
|
|
|
this.manager = new ConnectedAssetAdministrationShellManager(registryProxy, connectorFactory);
|
|
|
|
|
|
ModelUrn aasURN = new ModelUrn(displayUrn);
|
|
|
ConnectedAssetAdministrationShell connectedAAS = manager.retrieveAAS(aasURN);
|
|
|
|
|
|
Map<String, ISubmodel> submodels = connectedAAS.getSubmodels();
|
|
|
ISubmodel connectedSM = submodels.get("display");
|
|
|
IOperation operation = connectedSM.getOperations().get("displayText");
|
|
|
|
|
|
String chipText = map.get("chip_text").toString();
|
|
|
chipText = chipText.trim();
|
|
|
if(chipText.isEmpty() || chipText.isBlank()){
|
|
|
chipText = "null";
|
|
|
}
|
|
|
operation.invoke(chipText + "," + map.get("chip_id"));
|
|
|
}
|
|
|
catch(Exception e){
|
|
|
logger.info("Could not display value of lastResult: ", e);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Using the `ConnectedAssetAdministrationShellManager`, the AAS of the display can be get by using the URN. All submodels of the AAS are get and the one with the short ID "display" is extracted from the list. This submodel should contain the necessary operation called "displayText". Before invoking it, the String we want to pass needs to be preprocessed - otherwise some tags with null characters (`\u0000`) as text will cause problems.
|
|
|
|
|
|
## Implementation Python
|
|
|
|
|
|
### mqtt_handler_rfid.py
|
|
|
|
|
|
In this script, a paho MQTT client is implemented. Depending on the received message a corresponding python script for reading or writing a tag is run.
|
|
|
|
|
|
```python
|
|
|
brokerAddress = environ.get('MQTTBROKERHOST')
|
|
|
readerId = environ.get('READERID')
|
|
|
|
|
|
topicLoop = "rfidReader/RfidReader_"+readerId+"/loop"
|
|
|
topicRead = "rfidReader/RfidReader_"+readerId+"/read"
|
|
|
topicWrite = "rfidReader/RfidReader_"+readerId+"/write"
|
|
|
topicStop = "rfidReader/RfidReader_"+readerId+"/stop"
|
|
|
|
|
|
proc = None
|
|
|
```
|
|
|
|
|
|
First, the broker address and the ID are retrieved from the environment variables. The ID is used to set the coressponding topics. Afterwards, an empty variable for the currently running process is defined.
|
|
|
|
|
|
```python
|
|
|
def on_connect(client, userdata, flags, rc):
|
|
|
client.subscribe(topicLoop)
|
|
|
client.subscribe(topicRead)
|
|
|
client.subscribe(topicWrite)
|
|
|
client.subscribe(topicStop)
|
|
|
```
|
|
|
|
|
|
The function is used to subscribe to the necessary topics.
|
|
|
|
|
|
```python
|
|
|
def on_message(client, userdata, message):
|
|
|
payload = None
|
|
|
try:
|
|
|
payload = str(message.payload.decode("utf-8"))
|
|
|
print("message received:", payload)
|
|
|
print("message topic:", message.topic)
|
|
|
except Exception as e:
|
|
|
print("Error. Could not decode payload: ")
|
|
|
print(e)
|
|
|
|
|
|
global proc
|
|
|
|
|
|
try:
|
|
|
proc.kill()
|
|
|
except Exception as e:
|
|
|
print(e)
|
|
|
|
|
|
if(message.topic == topicLoop):
|
|
|
try:
|
|
|
proc = subprocess.Popen([sys.executable, 'read_rfid_loop.py'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
except Exception as e:
|
|
|
print(e)
|
|
|
|
|
|
if(message.topic == topicRead):
|
|
|
try:
|
|
|
proc = subprocess.Popen([sys.executable, 'read_rfid.py'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
except Exception as e:
|
|
|
print(e)
|
|
|
|
|
|
if(message.topic == topicWrite):
|
|
|
try:
|
|
|
text = payload
|
|
|
proc = subprocess.Popen([sys.executable, 'write_rfid.py', text], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
|
except Exception as e:
|
|
|
print(e)
|
|
|
```
|
|
|
|
|
|
The payload of the given message is decoded. Depending on the topic different processes are started. If one of the scripts is already running, the process is killed. Because of this no further steps are necessary if a message with the topic for stopping is received. Otherwise, either the loop or a script for reading a tag once can be started. To change the text of a tag, the message payload is passed to the python script for writing as argument.
|
|
|
|
|
|
```python
|
|
|
client = mqtt.Client("rfidReader")
|
|
|
client.on_connect = on_connect
|
|
|
client.on_message = on_message
|
|
|
client.connect(brokerAddress)
|
|
|
client.loop_forever()
|
|
|
```
|
|
|
|
|
|
A MQTT client is initialized and the previous functions are assigned as on_connect and on_message. `on_connect` is executed when the client connects to the broker and `on_message` every time a message is received. The client connects to the broker and `client.loop_forever()` enables to run the script indefinitly. Furthermore, the function handles automatic reconnects.
|
|
|
|
|
|
### read_rfid.py
|
|
|
|
|
|
The script can be found here. For using the RFID reader, `mfrc522` is used.
|
|
|
|
|
|
```python
|
|
|
brokerAddress = environ.get('MQTTBROKERHOST')
|
|
|
readerId = environ.get('READERID')
|
|
|
```
|
|
|
|
|
|
The address of the MQTT broker as well as the id of the RFID reader are retrieved from the environment variables.
|
|
|
|
|
|
```python
|
|
|
reader = SimpleMFRC522()
|
|
|
client = mqtt.Client("rfid-rc522")
|
|
|
client.connect(brokerAddress)
|
|
|
```
|
|
|
|
|
|
A reader and a MQTT client are initilized.
|
|
|
|
|
|
```python
|
|
|
try:
|
|
|
chipId, chipText = reader.read()
|
|
|
timestamp = datetime.now()
|
|
|
createDict()
|
|
|
finally:
|
|
|
GPIO.cleanup()
|
|
|
```
|
|
|
|
|
|
When a chip is put on the reader, the text and id are read and the current time is stored. `createDict()` is called, which will be explained in the following. After reading exactly one tag, the script terminates.
|
|
|
|
|
|
```python
|
|
|
def createDict():
|
|
|
rfidDict = {'reader_id': 'RfidReader_' + str(readerId),
|
|
|
'model': 'AZ-Delivery RFID RC522 Reader',
|
|
|
'chip_id': chipId,
|
|
|
'chip_text': chipText.strip(),
|
|
|
'timestamp': timestamp
|
|
|
}
|
|
|
jsonString = json.dumps(rfidDict, indent=4, default=str)
|
|
|
client.publish("rfidReader/RfidReader_"+str(readerId),jsonString)
|
|
|
```
|
|
|
|
|
|
A dictionary containing the data is created and converted into a string. This string is published using MQTT.
|
|
|
|
|
|
### read_rfid_loop.py
|
|
|
|
|
|
The script works like `read_rfid.py`. But instead of terminating after one tag was read, it runs in an infinit loop. Every time a chip is detected, the MQTT message is published. A 3 second delay is set to avoid instantly reading the same chip again.
|
|
|
|
|
|
|
|
|
### write_rfid.py
|
|
|
|
|
|
The script can be found here. It works like `read_rfid.py` as well.
|
|
|
|
|
|
```python
|
|
|
try:
|
|
|
text = sys.argv[1]
|
|
|
reader.write(text)
|
|
|
chipId, chipText = reader.read()
|
|
|
timestamp = datetime.now()
|
|
|
createDict()
|
|
|
finally:
|
|
|
GPIO.cleanup()
|
|
|
```
|
|
|
|
|
|
The text to write needs to be passed as argument.
|
|
|
|
|
|
|
|
|
|