The Serial2MqttGateway functions as a gateway between MQTT and serial devices, which can each be adressed and interfered with via unique topics based on the device's respective device ID.
The application is based on the SerialPortGateway.
Features:
- All of SerialPortGateway's features:
- Auto discovery of new serial devices
- Processing of incoming messages from the devices
- Easy to use message format for communicating with serial devices
- Sending messages/commands to specific devices or all devices at once
- Automatic handling of IO Errors
- Logging of all actions
- Designed as a base class for other applications to be build upon
- Callbacks for easily handling different events (New messages, devices added/deleted )
- Easy to use interface for interfering with the gateway
- Parallelization of all time critical or potentially blocking tasks
- Quick configuration of the gateway via easy config files
- Can be used either dockerized or standalone
- Communication via MQTT with the serial devices
- Messages coming from the devices are send to an MQTT broker.
- Incoming messages are organized hierarchically in topics, based on their device ID and message type.
- Commands can be delivered over MQTT to each device connected.
- Messages coming from the devices are send to an MQTT broker.
- Gateway-Commands, which can control the Serial2MqttGateway over MQTT.
- Files and Folder structure
- Dependencies
- Installation
- Including and compiling Serial2MqttGateway in a project
- Code Documentation
- Requirements for communicating between the Serial2MqttGateway and serial devices
- MQTT topic hierarchy
- Usage
- Notes
- License
config
contains the configuration files- Sample Config file
- Sample hardware ID whitelist
- Sample serial port blacklist
cert
contains a sample cert (from Let's Encrypt, in case you have a service which uses a cert issued by them)
dependencies
is the place where all dependencies get downloaded to (See Installation for further details)src
contains the source codeSerial2MqttGateway
classserial2mqtt-gateway
main application runtime
.env
is an environment file for Docker.gitmodules
contains references to the dependenciesbuild.sh
is a script for building the applicationclearMqttTopics.sh
is a script for clearing all retained MQTT topicsdocker-compose.yml
is a docker-compose for easy deploymentDockerfile
is the Dockerfile which can be used for building an image with all needed dependenciesMakefile
is for compiling the application
If you follow the Installation instructions, some dependencies will be cloned directly into the dependencies folder:
In case you're going to use the Docker image, you're good and don't require anything other than Docker in order to deploy/compile/test the Serial2MqttGateway or any enhancements done by yourself.
In case you want to use the Serial2MqttGateway without Docker, you need the following few things installed:
- gcc, g++, libc-dev, make, curl, wget, git.
- g++ must be version 5 or higher.
- git must be version 2 or higher.
- wjwwood's serial library. This also requires:
- cmake, python, python-pip, catkin
- paho.mqtt.c
- must be version 1.3.9 or higher
- paho.mqtt.cpp
- must be version 1.2.0 or higher
If there's anything not working, try to sync your configuration with that from the Dockerfile (Or drop me an issue if nothing helps).
- Clone the repo
- Change the directory into the repo
- Execute
git submodule update --init --recursive --remote
to download all dependencies needed for using/compiling/executing the gateway - Building:
- Build the Docker image:
make buildDockerImage
ordocker-compose build
- Build the application without Docker:
./build.sh
- Build the Docker image:
C++14 is required for compilation.
In order to use Serial2MqttGateway, <path>/Serial2MqttGateway/src/Serial2MqttGateway.hpp
needs to be included.
In order to compile Serial2MqttGateway, following files need to be compiled and linked:
<path>/Serial2MqttGateway/dependencies/SerialPortGateway/dependencies/Logger/src/Logger.cpp
<path>/Serial2MqttGateway/dependencies/SerialPortGateway/dependencies/Logger/src/LogType.cpp
<path>/Serial2MqttGateway/dependencies/SerialPortGateway/dependencies/Exception/src/Exception.cpp
<path>/Serial2MqttGateway/dependencies/SerialPortGateway/dependencies/Config/src/ConfigExceptions.cpp
<path>/Serial2MqttGateway/dependencies/SerialPortGateway/dependencies/Config/src/Config.cpp
<path>/Serial2MqttGateway/dependencies/SerialPortGateway/src/SerialDevice.cpp
<path>/Serial2MqttGateway/dependencies/SerialPortGateway/src/SerialMessage.cpp
<path>/Serial2MqttGateway/dependencies/SerialPortGateway/src/SerialPortGateway.cpp
(Take a look at the Makefile.)
Besides this document, you can find detailed documentation in the SerialPortGateway.
The sources are also documented where necessary/useful, but everything that's not documented in the SerialPortGateway should be pretty self explanatory.
- Messages from devices need to follow the SerialMessageFormat.
- The command declared via the config key
COMMAND_GETID
needs to be understood by every device connected and result in a validly formatted message from the device to the gateway directly after. This message needs to correspond to the message type declared with the config keyMESSAGE_TYPE_ID
and needs to contain the device's ID. - The message delimiter declared with the config key
MESSAGE_DELIMITER
needs to be in sync with the message delimiter the connected devices are using. - The baud rate declared with the config key
BAUD_RATE
needs also to be used by every device connected for sending and receiving data.
In order to communicate with the MQTT broker in a consistent way, a topic hierarchy had to be defined. This hierarchy allows to access a fixed structure through which all necessary information can be exchanged.
In the following examples and tables there's also defined whether the topic is/must be retained, and on which QoS-Level information should be exchanged.
This is crucial for the gateway working correctly.
The hierarchy generally starts with the gateway ID, and can optionally be supplemented by a prefix (The gateway ID will only be preceeded by a slash (/
) if a prefix is given):
<optionalPrefix>/<gatewayId>
The gateway ID is then followed by a subtopic:
<optionalPrefix>/<gatewayId>/<subtopic>
Those subtopics can be one of the following:
Topic | Purpose | QoS-Level | Retained? |
---|---|---|---|
connected |
Shows whether the gateway is connected or not (0 or 1) | 2 | true |
devices |
Contains all device information & (sub-)topics | 2 | true |
command |
Endpoint on which the gateway listens on commands Messages send to this endpoint should NOT be retained, to prevent unwanted execution of commands |
2 | false |
response |
Topic where possible responses get send to after a gateway command has been invoked | 2 | true |
devicelist |
Topic where the device list gets send to, if requested via gateway command | 2 | true |
In the case of the devices
topic, the topic has a subtopic for each device ID which is or was connected:
<optionalPrefix>/<gatewayId>/devices/<deviceId>
And then again, this topic has a subtopic for each message type the specific device has send a message with:
<optionalPrefix>/<gatewayId>/devices/<deviceId>/<messageType>
Apart from those subtopics based on message types, there are two special subtopics a device can have:
Topic | Purpose | QoS-Level | Retained? |
---|---|---|---|
connected |
Shows whether the device is connected or not (0 or 1) | 2 | true |
command |
Endpoint on which the gateway listens on commands for serial devices Messages send to this endpoint should NOT be retained, to prevent unwanted execution of commands. |
2 | false |
These two subtopics should never be used as a message type by any of the connected devices.
Every topic can be listened on, in order to -for instance- log data to a database or something similar.
This could be easily done with a wildcard subscription: <optionalPrefix>/<gatewayId>/#
Refer to MQTT's documentation in order to get more information on how to subscribe to topics in different ways.
A full topic hierarchy of a gateway could look like this:
gateways/
|--->gateway1/
| |--->connected
| |--->command
| |--->response
| |--->devicelist
| \--->devices/
| |--->device1/
| | |--->connected
| | |--->command
| | |--->id
| | |--->active
| | |--->info
| | \--->status
| \--->device2/
| |--->connected
| |--->command
| |--->id
| |--->active
| |--->info
| \--->status
|--->gateway2/
...
The Serial2MqttGateway can be easily configured with three different config files.
This is the basic config of the gateway. It uses the Config class.
On top of the mandatory configuration keys defined here, the gateway needs following additional keys defined:
Key | Purpose | Value Description | Default |
---|---|---|---|
GATEWAY_ID | - ID of the gateway - Needed to use and distinguish multiple gateways on the same topic level from one another - Is also required to be able to receive gateway commands |
String | testgateway1 |
MQTT_PROTOCOL | Specifies the protocol to use for communication with a broker | String - tcp for unencrypted MQTT/TCP communication- ssl for encrypted MQTT/TCP communication- ws for unencrypted websocket communication- wss for encrypted websocket communication |
tcp |
MQTT_CERTIFICATE_AUTHORITY_PATH | Specifies the location of all trusted CA files, in case you want to use encrypted communication for MQTT | String - none if no CA trust store is used- <path/to/ca/files> if a CA trust store is used |
/etc/ssl/certs/ |
MQTT_SERVER_CERTIFICATE_FILE | Configuration on whether to use a server certificate or not, in case you want to use encrypted communication for MQTT | String - none if no server certificate is used- <path/to/serverCertificateFile> if a server certificate is used |
none |
MQTT_CLIENT_CERTIFICATE_FILE | Configuration on whether to use a client certificate or not, in case you want to use encrypted communication for MQTT | String - none if no client certificate is used- <path/to/clientCertificateFile> if a client certificate is used |
none |
MQTT_CLIENT_KEY_FILE | Configuration on whether to use a client certificate or not, in case you want to use encrypted communication for MQTT | String - none if no client certificate is used- <path/to/clientKeyFile> if a client certificate is used |
none |
MQTT_HOST | MQTT host, where the broker is running | String Hostname or IP |
test.mosquitto.org |
MQTT_PORT | MQTT port, where the broker is listening on | Port number | 1883 |
MQTT_WAIT_UNTIL_RECONNECT | Time to wait until the next reconnect attempt in ms | Integer | 1000 |
MQTT_USERNAME | MQTT username to use when logging in | String Can be empty |
|
MQTT_PASSWORD | MQTT password to use when logging in | String Can be empty |
|
MQTT_TOPIC_PREFIX | MQTT topic prefix which should be used Is set before every topic the Serial2MqttGateway is publishing on |
String Can be empty |
test/gateways |
The hardware ID whitelist lists all allowed hardware IDs;
- If the list is empty, no whitelist-checks will be performed.
- If the list has one or more entries, only devices matching one of those hardware IDs can/will be added to the gateway.
The hardware IDs are formatted in the following way: <VendorID>:<ProductID>
The hardware IDs are separated from each other by a line break. A hardware ID whitelist can look like the following:
1a86:7523
0403:6001
2341:0042
These IDs can be retrieved with Linux tools such as "lsusb" or "udevadm".
The serial port blacklist lists all forbidden serial port paths that should be ignored when scanning for or adding new devices. The port paths are separated from each other by a line break. A port blacklist can look like the following:
/dev/ttyS0
/dev/ttyS1
/dev/ttyS2
/dev/ttyS3
- The container needs to run in priviledged mode in order to gain access to the serial ports.
- In case you want to use the container just as your development environment, do one of the following things:
docker-compose up -d devenv; docker attach serial2mqttgateway_daemon_<number>;
docker run --privileged -it serial2mqttgateway /bin/bash
- For starting the application in a Docker container, simply use the
docker-compose.yml
-File included;
Rundocker-compose up -d daemon
if you want to have theserial2mqtt-gateway
run in the background.\ - In case you didn't build the image already, docker-compose will do this automatically for you.
Take a look at the Notes for some restrictions when using Docker as container runtime in conjunction with serial ports.
In order to send commands to the gateway, you must send a message with the corresponding content to this topic:
<optionalPrefix>/<gatewayId>/command
If the command requires an argument, the argument needs to be separated by a space character:
<command> <argument>
There are following commands currently implemented:
Command | Purpose | Arguments |
---|---|---|
devicelist | Publishs a list of all connected devices on <optionalPrefix>/<gatewayId>/devicelist |
|
serialportlist | Publishs a list of the system's available serial ports on <optionalPrefix>/<gatewayId>/response |
|
mappinglist | Publishs a list of all mappings between connected devices by their ID & their corresponding serial port on <optionalPrefix>/<gatewayId>/response |
|
broadcast | Broadcasts a message/command to all connected serial devices | <message> |
add | Tries to add a serial device on a specific serial port | <path/to/serial/port> |
addnew | Tries to add all new serial devices, which are not already registered/added in the gateway | |
delete | Tries to delete a serial device with a specific device ID | <deviceId> |
deleteall | Tries to delete all currently registered serial devices from the gateway |
In order to send commands to a specific device, you must send a message with the corresponding content to this topic:
<optionalPrefix>/<gatewayId>/devices/<deviceId>/command
- In case you're using Arduinos with the Serial2MqttGateway, you can use the ArduinoStreamCommander.
But this doesn't mean you have to, as long as you follow the requirements for communication. - Docker can -without doing anything else- only provide devices or ports to the container which are already connected when the container is started. In this case this means that all serial devices to be connected must be initially available at least at the start of the container, so that the port numbers are registered in the container. Dis- and reconnects that happen afterwards -and thus the exchange of devices to other ports- are unproblematic, but to register additional devices a reboot is required.
Meaning: If more ports should be used than when the container was initially started, the container must be restarted.
Copyright 2019-2022 Jan-Eric Schober
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.