The routing table permits to transfer any type of serializable data (numerical values, strings, json objects, etc..) between all building automation and IoT protocols supported by the gateway software. Main standard open protocols are supported: KNX, Bacnet, Modbus, m-bus, MQTT, and more. The routing feature supports plain 1 to 1 routing between 2 addresses, but also more complex routing patterns detailed in this document. Due to the complex nature of universal/multi-protocol routing, advanced use cases often require basic programming skills.
Chapter 1 introduces quickly the gateways and the routing table. It describes how to setup step by step a simple route through the user interface.
Chapter 2 explains in details all the routes parameters.
Chapter 3 concludes with advanced routing examples using JavaScript and regular expressions.
This chapter is a beginner tutorial that shows how to route values between different protocols. It is assumed that the gateways (Modbus and Bacnet in this tutorial) are already configured and properly working. If needed please refer to the user manual for the gateways configuration.
The gateways pannel is the heart of the product, it permits to insert new gateways, many protocols are supported (KNX, MQTT, Modbus, Bacnet, Mbus, OPCUA, ADS, SMTP, ...) and new ones are regulary added into the software updates. The list of all gateways is displayed on the left pannel, and appears in green color if they are active and connected. By clicking on a gateway, it opens the selected gateway tab which lets you see and configure its addresses. There is no fixed limit in the number of gateways or addresses, although they do consume RAM and CPU ressources. At the bottom of the gateway list, there is a button Routes list
which lets you open the routing table.
For beginners it is easy to create new routes by drag & dropping the source address on the destination address. To do so you source gateway tab (here the modbus tab) and your destination gateway tab (here the bacnet tab). It is possible to layout the tabs in two different pannels as shown in the screenshot below, and then to drag your source address (here %M1
) to your destination address (here %bac:local/analogValue:1
).
Once you the drag & drop is released, a popup window containing the route creation menu appears. From this menu, you can configure the different parameters of the route. Once your route is configured, you can click the Save button to validate the creation of the route, or the Cancel button to abort the operation.
After creation, the route appears in the routing table. The routing table can contain any number of routes. In this example, the modbus register is simply routed to the bacnet analogValue object:
%M1 → %bac:local/analogValue:1
route parameters can be updated directly from the routing table (live updates)
From the route context menu (right click), it is possible to delete the route, update the route (it opens the route update popup window), or duplicate the route.
The routes can be edited by selecting multiple routes (ticks on the Name column) and then by opening the Selection menu. It is useful to remove or update multiple routes at once (bulk updates).
There is two main views for the routing table, the default Live table, and the Spreadsheet. The spreadsheet is similar to Microsoft Excel spreadsheets and is useful to update multiple parameters at once, or to copy paste routes.
The import and export supports two file formats : CSV
and JSON
. From the export menu it is possible to export uniquely the selected routes, or all the routes.
Some technicians update their routing file with Microsoft Excel by importing and editing the CSV
file. Once the work is finished, they can exort the file from Excel and then import it back into the device. The screenshot below shows the procedure to import the CSV data into Excel.
The figure below is the JSON
file generated when exporting the routes. This file format can be used for backups or by programmers to process the data in custom scripts.
[
{
"name": "route example",
"type": "value",
"direction": "right",
"disableDelay": "",
"repeat": 0,
"minimumDiff": 0,
"buffer": 0,
"active": true,
"source": {
"address": "%M1",
"gateway": "modbus"
},
"destination": {
"address": "%bac:local/analogValue:1",
"gateway": "bacnet"
},
"id": "cf408b70-4e3d-454c-a16b-6d7b4a7c2fd8",
"index": 0
},
{
"name": "route example copy",
"type": "value",
"direction": "right",
"disableDelay": "",
"repeat": 0,
"minimumDiff": 0,
"buffer": 0,
"active": true,
"source": {
"address": "%M1",
"gateway": "modbus"
},
"destination": {
"address": "%bac:local/analogValue:1",
"gateway": "bacnet"
},
"id": "519fc9b8-b483-4d6b-b7de-8ef1e7edcaa5",
"index": 1
}
]
Routes have several parameters described in this section. Main parameters are the source address, destination address, and direction of the routing. Source addresses listen to data-points change of values, and send those to the corresponding destination address. Routing can be performed from source to destination (default behaviour), reversed (destination to source), or bidirectional. Other parameters can be useful to transform the value before routing it, or to configure hysteresis to limit the traffic induced by frequent value updates.
A name can be configured for each route. It is an optional parameter that is used as a description. It has no functional implications.
There is 4 type of data/values that can be routed from a source address:
online
: means the source value is up to date and readable.offline
: means the driver process is not running, hence the source value is not up to date and readable.pending
: means the driver process is starting, so the address is not ready yet.!timeout
, !ILLEGAL_DATA_VALUE
, !ILLEGAL FUNCTION
, etc…Based on the value, and status, there is 4 types of possible routes:
{
"value" : 33,
"status" : "online"
}
disconnected
, pending
, connecting
, or connected
.For each route, it is possible to precise the source gateway. If a device is running multiple gateways of the same protocol (e.g. 4 Modbus masters connecting to 4 different slaves), the system may end up with 4 occurrences of the same address name. In such cases the source gateway can be specified in the route. So if you have 4 Modbus masters, you would be able to select the correct one. (Note that the routes are not always 1-1 mappings. If the source gateway is left empty, all matching addresses trigger the route. For advanced use cases, instead of selecting a single gateway, or all gateways by leaving the field empty, it is also possible to use a regular expression or a JavaScript function to select a subset of matching gateways.)
The source address parameter defines the address that triggers the routing by listening new value changes on it. It can either be a single fixed address defined by its address name (simple case), or multiple addresses matching a pattern like a regular expression. All possible types of source address definition are listed below:
function isSource(adrName, adrId, adr, info){
// first argument is the name of the address,
// second argument is the numerical id of the address,
// third argument is the full address object.
// last argument is additional info about the route and its context.
// returns to accept an address, or false to reject it.
if(adrName.startsWith("%M300")){
// accepts all modbus register starting with "%M300"
// it could include %M300, %M3000, %M3001, %M30000, etc...
return true
}else{
// rejects the address: it will never trigger the route.
return false
}
}
The source value field has 2 main use cases that can be combined together:
If the field is left empty, then the original value is simply routed without alterations. When defined, it can take the following forms:
JSON
object.%M3
or %bac:local/analogValue:2
).function functionXYZ(newValue, sourceAddress, destinationAddress, info){
// first argument is the new value being routed,
// second argument is the source address object,
// third argument is the destination address object,
// last argument contains additional info about the route and its context.
if(newValue > 0){
// if the routed value is greater than 0
return newValue * 2 //returns the routed value multiplied by 2.
}else{
// if the routed value is smaller or equal to 0
return null // returns nothing => cancels the route execution.
}
}
Note that functions can be stored in a gateway of type internal in addresses starting with $
as such as $functionXYZ
. To ensure that your function is not lost after a device reboot, it is needed to set the address Store Value parameter to always. Once the function is stored inside the address, it can be used in multiple routes by writing the name of the address ($functionXYZ
) in the route source value parameter.
The source value is only used when the routing direction is from source to destination (see next section 2.6). Note that reversed routing (from destination to source) uses the opposite parameter Destination Value (section 2.9).
When the route type (section 2.2) is value, you can choose the route direction. The default direction is from source to destination. The following directions are possible:
When the route type is status or value/status, the route direction is forced to right (→). It is due to the address status being a read-only property.
Same as Source Gateway (section 2.3), but applied to the destination address gateway. As an example, it is useful when routing Modbus register %M100
of a given Modbus gateway modbusGwA, to Modbus register %M100
of another Modbus gateway modbusGwB. In this scenario the source and destination addresses share the same name (%M100
), so specifying which gateway is the source or destination is important. Here the source gateway would be defined in the route as modbusGwA, and the destination gateway as modbusGwB.
The destination address is the data-point to which the routed value is written/sent. It can either be a single fixed address defined by its address name (simple case), or multiple addresses matching a pattern. All possible types of destination address definition are listed below:
function sourceToDestination(srcName, srcId, src, info){
// first argument is the source address name,
// second argument is the source address numerical id,
// third argument is the full source address object,
// last argument contains additional info about the route and its context.
if(srcName == "%M100"){
return "%M200" // maps source %M100 to destination %M200.
}else{
return "%M0" // maps all other sources to destination %M0.
}
}
// same mapping as example above, but implemented using nested functions.
function sourceToDestination2(srcName, srcId, src, info){
// Instead of returning the destination address identifier, it is possible
// to return a boolean function that checks each destination candidates.
return function booleanCheck(destName, destId, dest){
return (srcName == "%M100" && destName == "%M200") || destName == "%M0"
}
}
The destination value parameter is equivalent to the source value (section 2.5) parameter, but used only when the route is executed in the reversed direction, from destination to source. When routing in the normal direction from source to destination, the source value parameter is used instead. Note that the destination value parameter can only apply if the route direction (section 2.6) is left (←) or bidirectional.
The repeat parameter permits to regularly (every x milliseconds) re-trigger / re-execute automatically the route when the source value does not change regularly. It guarentees that the destination address is written at least every x milliseconds. Note that if there is a communication failure on the destination gateway, the protocol driver process is restarted and retries to establish the connection. Once the gateway is connected, routes are automatically refreshed and executed. So it is not necessary to configure the repeat parameter for this use case.
Once a route is triggered and executed, it can be deactivated for a small duration like 100 milliseconds. It means that when the route is executed and the destination address written, potential new values coming from the source will be ignored during 100 ms. This permits to limit excessive traffic (CPU & network usage) and prevent potential routing loops.
The minimum diff parameter permits to define a hysteresis threshold for routing numerical values. This concept is the same as the BACnet change-of-value (COV) increment. Assuming that the minimum difference is set to 0.1, then the source value must vary by an increment superior to 0.1 to be routed to destination. Small routed value variations are ignored. Using this parmater can be can be a good strategy to decrease the CPU and network usage.
The buffer parameter is hidden by default. In case of communication issues with the destination gateway, the routing will fail until reconnection. By specifying a buffer size, it guarentees that the last x messages / values sent by the source address will be kept in memory and transmitted in order to their destination once the communication is re-established.
This chapter focuses on advanced routing examples. Note that simple routes can be easily configured using the web interface by drag & dropping an address on another, as shown in chapter 1.
This section demonstrates how to route multiple Bacnet objects to OPC UA with a single route. To achieve it, a regular expression is used. Regular expression is a simple language/parser which permits to describe, capture, and replace patterns in any string of characters. In this use case it is used to identify matching source addresses, and transform each matching source address to the proper destination address. For a comprehensive description, please refer to Javascript regular expressions.
The following paragraph shows how to configure the route to map all Bacnet analog values properties to an OPC UA tree structure.
Route configuration:
\%bac:local\/analogValue:(\d)\/(.*)
. This regex matches all Bacnet analog values properties. The regex above include 2 capturing groups: (\d)\/(.*)
. The first one captures the Bacnet object instance number (1,2,3, ...) , and the second one the Bacnet property name (presentValue, objectName, description, ...). The capture groups can be used to determine the corresponding destination address. The below address names are matching the regex. A change of value in any of the matching addresses will trigger the route. The figure below shows some addresses that matches the regular expression.%bac:local/analogValue:1/presentValue
%bac:local/analogValue:1/objectName
%bac:local/analogValue:1/description
%bac:local/analogValue:1/…
%bac:local/analogValue:2/…
%bac:local/analogValue:3/…
%opc/analogValue/$1/$2
%opc
of the destination address indicates that it is an OPC address. The $1
pattern refers to the first capturing of the source address regex, which is the bacnet object instance number. The $2
pattern refers to the second capturing group, which is the bacnet property name. Each destination address is computed by the route using the JavaScript String replace function. The figure below shows some mapping examples.%bac:local/analogValue:1/presentValue -> %opc/analogValue/1/presentValue
%bac:local/analogValue:1/objectName -> %opc/analogValue/1/objectName
%bac:local/analogValue:2/presentValue -> %opc/analogValue/2/presentValue
%bac:local/analogValue:[instance]/[prop] -> %opc/analogValue/[instance]/[prop]
This section demonstrate how to create a route able to detect communication issues on a Modbus register and to trigger an email alert (SMTP driver).
For this use case we will have to use a route of type status (section 2.2). The status is interesting to trigger alerts when a given address is not responding or in faulty state. There exists 3 main address statuses: offline
, pending
, and online
. The Modbus driver also emits Modbus specific errors such as !timeout
, !ILLEGAL_FUNCTION
, !ILLEGAL_DATA_VALUE
, ... .These specific errors can be used to detect configuration or communication issues and they start with the exclamation mark !
.
Now let’s assume that the gateway device is acting as a Modbus RTU master. The master is gathering multiple values from different Modbus slaves. Each Modbus slave has a slave id. This id can be specified in the Modbus master address: %M1-100
represents the holding register number 100 of the slave with id 1. %M2-100
represents the same register number, but on the slave 2. If one of the slaves has a problem, its addresses status will emit the error. The route receives the status changes, checks if it is an error, and eventually sends an email alert stating that the concerned Modbus slave is not responding.
The route configuration for such use case is the following:
%M1-100
Modbus register address (Modbus slave 1) is used. Note that a regex could be used to have a single route covering all the slave statuses.function statusToEmail(status, src){
if(status == "online" || status == "pending" || status == "offline"){
//those are normal statuses sent when the modbus master is (re)starting.
return; // returns nothing => cancels the route execution.
}else{
//there is a modbus communication issue such as timeout
return "Modbus communication problem with address " + src.name + "\n"+
"Error : " + status
}
}
The following example will demonstrate how to route a full Bacnet object with some of its properties into MQTT JSON
messages. MQTT messaging protocol is lightweight and often used in IoT applications, like Amazon IoT cloud, Google IoT cloud, and other companies services.
The route will convert a Bacnet analogValue object presentValue, objectName, and units, to the following JSON
format:
{
"properties" : {
"objectName" : "BTU DELTA TEMP",
"unit" : "degreesCelsius"
},
"value" : 3,
"timestamp" : "2023-10-28 18:49:39"
}
Note that a timestamp is added to the JSON
. It corresponds to the date & time of the routing (when the analogValue object presentValue changes and triggers the route).
Route configuration:
%bac:local/analogValue:1
is the source address. It is a single local Bacnet object (the bacnet device is the weble gateway itself) of type analogValue. The value emitted on this address is the bacnet presentValue.%mqtt:analogValue_1
is the single destination MQTT topic. (The MQTT address datatype must be configured as json for serialization.)%bac:local/analogValue:1/objectName
, and %bac:local/analogValue:1/units
. These properties can be accessed in the route using our gateways API: https://api.weble.ch/module-core_gateways.html. The source value transform function is bound to the API (it means that the JavaScript “this” keyword refers to the API object).function bacnetToJson(value, sourceAddress, destinationAddress){
// value here is the bacnet object presentValue
var gwId = sourceAddress.gateway_id //get the bacnet gateway id.
var boa = sourceAddress.name //e.g. %bac:local/analogValue:1
// get the bacnet object properties, those are stored in children
// addresses. Those can be fetched using the gateway api (this).
// https://api.weble.ch/module-core_gateways.html
var objectName = this.getAddress({
gateway_id:gwId,
name : boa + "/objectName
}).value
var objectUnit = this.getAddress({
gateway_id:gwId,
name : boa + "/units
}).value
return {
"properties" : {
"objectName" : objectName,
"unit" : objectUnit
},
"value" : value,
"timestamp" : new Date().toLocaleString() //adds a timestamp
}
}