This document aims to show the user how to set up and run their own instance of the MediSCARA Master Control Unit.
To see the main features of the MMCU see this page.
You will need Docker to run the neccessary containers. Information about the installation can be found here.
Python is necessary to run the MMCU, you can install it from here.
In the above image we can see the necessary components for the setup. In the next section we will install and configure every one of the components.
A brief overview of the flow of a command can be seen on the next image:
To install the application, navigate to the root of the directory you've downloaded the source to and run pip install .
.
This will install the necessary files and packages.
This example configuration provides a docker-compose.yaml file that can be used to set up the necessary software stack.
Navigate to the file and run docker compose up -d
to start the containers.
Use docker compose down -v
to stop and completely remove the installed containers.
To make sure that everything runs in order, run the docker ps
command.
The output should look something like this:
7846e1cfe3c8
a38f3c63da92
01be3f96ff79
These are the IDs of the running containers. You should see (at least) three of them.
Now that we have our containers running, we need to prepare the IoT Agent. The steps included in this guide can be found in further detail at the official tutorial.
First we need to provision a service group.
You can find this and all of the following requests in the Postman collection provided here.
This can be done with the Create Service Group
request:
curl --location --request POST 'localhost:4041/iot/services' \
--header 'fiware-service: mediscara' \
--header 'fiware-servicepath: /' \
--header 'Content-Type: application/json' \
--data-raw '{
"services": [
{
"apikey": "exampleApiKey",
"entity_type": "Thing",
"resource": "/iot/json"
}
]
}'
The values of apikey
, fiware-service
, fiware-servicepath
and resource
will be used later, so dont forget about them.
The response should be 201 Created
.
Next, we need to add the MCU itself, as an IoT Device.
This can be done with the Create MCU
request:
curl --location --request POST 'localhost:4041/iot/devices' \
--header 'fiware-service: mediscara' \
--header 'fiware-servicepath: /' \
--header 'Content-Type: application/json' \
--data-raw '{
"devices": [
{
"device_id": "MCUTest",
"entity_name": "MCU:Test",
"entity_type": "MCU",
"protocol": "PDI-IoTA-JSON",
"transport": "HTTP",
"endpoint": "http://host.docker.internal:6900/api",
"attributes": [
{
"object_id": "e",
"name": "error",
"type": "Text"
}
],
"commands": [
{
"name": "start_laser_cut",
"type": "command"
},
{
"name": "home",
"type": "command"
}
]
}
]
}'
The endpoint should point to the IP address of the MCU (the host.docker.internal host functions as an alias for the localhost in this case). In this request, we can define attributes and commands for the MCU. These command values will be the keywords for the MCU Commands.
The response should be 201 Created
here as well.
After these steps we are ready to configure the MCU itself.
First, we need to configure the environment variables for the MMCU. This can be done via the .env file.
The API_KEY
, FIWARE_SERVICE
and FIWARE_SERVICEPATH
varables need to have the same values as the parameters we sent when we provisioned the service group.
The IOTA_PATH
needs to be set to the value of resource
from the same request.
The MCU_ID
is the same as the device_id
parameter from the previous request (here "MCUTest").
The IOTA_URL
is the URL of the IoT Agent (localhost:7896). The host is the ip address of the MCU and the port is the port of the MCU (localhost:6900 as you can see in the previous request).
After this, we can run the MCU in a terminal with the mcu
command.
You should see the following:
* Serving Flask app 'mcu.mcu'
* Debug mode: off
This means the MCU has started successfully.
Now we have a working MCU unit but it is not worth much without commands that can be executed.
To create a new command, make a python file in the /src/mcu/external directory. In this guide, we will call it example.py.
Then create a class that inherits from the mcu.user_defined.Command
class. The file should look like this:
"""This is an example module demonstrating the usage of the MMCU"""
# import the command class
from mcu.models.user_defined import Command
# import the necessary methods for registering embedded communication protocols
from mcu.config import add_serial_server, add_tcp_server
class ExampleCommand(Command):
"""This is the example command class that is run by the MCU
It should always inherit from the Command class imported above
This class sends a message via TCP socket and serial communication whenever the incoming command
contains the 'exampleKeyword' key.
The command is sent from an IoT Agent and it uses the JSON protocol as its payload.
"""
Now we have to implement the __init__
method:
def __init__(self) -> None:
# register the superclass with the keyword
# whenever a new FIWARE command comes in and matches with the given keyword, the class' 'target' method will be executed
super().__init__(keywords=['start_laser_cut'])
# request a tcp socket server from the runtime
self.tcp = add_tcp_server('localhost', 2000)
# register callback methods for the events regarding the server
self.tcp.register_callbacks(connected=self.on_tcp_connected,
lost=self.on_tcp_connection_lost,
received=self.on_tcp_received
)
There are some errors in the code, so we will need to define the callback methods for the socket communctication.
def on_tcp_connected(self, client: str):
"""This method is called when a new socket client is connected to the server"""
print(f"New connection from {client}")
def on_tcp_connection_lost(self):
"""This method is called when a socket client is disconnected from the server"""
print("A client has disconnected")
def on_tcp_received(self, msg: bytes):
"""This method is called when data is received from a socket client"""
print("Incoming message: ", msg.decode())
if msg == b'OK':
# update the info attribute
self.update_attribute('info', 'OK')
Finally, we will need to define the target
method. This method will be executed when the IoT Agent send a command with the start_laser_cut
key.
def target(self, *args, keyword: str):
"""This method is executed whenever one of the registered keywords match the incoming request"""
# send a message via socket
self.tcp.send("HOME")
# report back to the runtime
# dont update the status as it will come back later through TCP socket
self.result = None
We have provided a simple python script to emulate an embedded device's homing sequence.
To issue a command to the MMCU, you can use the Send Home Command
request:
curl --location --request PATCH 'localhost:1026/v2/entities/MCU:Test/attrs' \
--header 'fiware-service: mediscara' \
--header 'fiware-servicepath: /' \
--header 'Content-Type: application/json' \
--data-raw '{
"home": {
"type": "command",
"value": ""
}
}'
This should return a 204 No Content
response.
After you send the command, the MCU will execute the target
method.
Because we set the result
variable to an empty string, the home_info
attribute will only be updated to RECEIVED.
To view the MCU status use the
Get entities
request:curl --location --request GET 'host.docker.internal:1026/v2/entities/' \ --header 'fiware-service: mediscara' \ --header 'fiware-servicepath: /'
The status of the home command can be seen here:
"home_info": {
"type": "commandResult",
"value": "RECEIVED",
"metadata": {
"TimeInstant": {
"type": "DateTime",
"value": "2023-02-09T13:36:24.851Z"
}
}
}
This means the MCU has received a command, but the result has not yet been determined.
After a delay, the dummy device will send an 'OK' message to the MCU, signaling the end of the homing sequence.
This means that we can update the info attribute using the update_attribute
method.
The result of the home command should look like this:
"home_info": {
"type": "string",
"value": "OK",
"metadata": {
"TimeInstant": {
"type": "DateTime",
"value": "2023-02-09T13:37:12.031Z"
}
}
}