The Internal driver provides virtual addresses for storing values within the gateway. Unlike other drivers that communicate with external devices, the Internal driver operates entirely in memory — it has no physical connection and no protocol overhead. It is one of the most versatile tools in the gateway, used for:
Create a new gateway and select the Internal driver. The gateway configuration is minimal — only a Name and optional Description are needed. No connection parameters are required.
Once the gateway is started (it connects instantly), you can start inserting addresses.
Insert a new address in the Internal gateway. Addresses are prefixed with $ (similar to variables in programming languages):
$temperature — a simple variable$room1/temperature — organized hierarchically with /$config/threshold — a configuration valueThe address tree displays all internal addresses in a hierarchical structure.
By default, internal addresses store values in memory only — values are lost on reboot. If a value must survive a gateway restart (e.g. a configuration parameter, a counter, a setpoint), enable Store value (set to always) on the address. This persists the value to the SD card.
Tip: Only enable Store value when necessary. Frequent writes to the SD card (e.g. a value changing every second) can reduce the SD card lifespan. For values that change often and don't need persistence, leave Store value on
never.
The Internal gateway has minimal configuration:
| Label | JSON Key | Description |
|---|---|---|
| Name | name | A descriptive name for this Internal gateway instance. |
| Description | description | Optional description. |
No cluster, no connection parameters. The gateway runs in-process (no separate process) to save memory.
Internal addresses use the $ prefix:
$myVariable$sensors/average$config/alertThresholdAddresses can be organized hierarchically using / and are displayed as a tree in the UI. You can also drag and drop addresses from other drivers onto the Internal gateway — the address name is automatically converted (e.g. %mbus:1/0/value becomes $1/0/value).
| Label | JSON Key | Description |
|---|---|---|
| Name | name | The address name, prefixed with $. |
| Description | description | Optional description. |
| Datatype | datatype | Value type constraint: any (default, accepts any value), number (rejects non-numeric values), string (rejects non-string values), or extend (deep-merges JSON objects instead of replacing them). |
| Log | log | Logging mode: never, on update, always, or a duration in milliseconds. |
| Store value | remanent | Value persistence: never (value lost on reboot, default), always (persisted to SD card), or a duration in milliseconds. |
| Read only | readOnly | If enabled, the address value cannot be modified through the UI grid (it can still be written programmatically via routes or the API). |
| Datatype | Behavior |
|---|---|
any |
Accepts any value: numbers, strings, JSON objects, arrays, etc. |
number |
Only accepts numeric values. Non-numeric writes are rejected with an error. |
string |
Only accepts string values. Non-string writes are rejected with an error. |
extend |
When a JSON object is written, it is deep-merged with the existing value instead of replacing it. Useful for incrementally updating complex objects. |
{
"datatype": "number",
"remanent": true,
"readOnly": false
}
A common pattern is to use an internal address as an intermediate variable between two routes. For example, to convert a Modbus temperature value from Fahrenheit to Celsius before sending it to BACnet:
%M1 → $tempCelsius with a routing function (value - 32) * 5/9$tempCelsius → %bac:local/analogValue:1 (direct value routing)This separates the conversion logic from the protocol routing and makes the intermediate value visible and loggable.
When creating a route, there are two ways to define custom routing logic:
Inline function — write the JavaScript function directly in the route's function field. Simple but the function is embedded in the route and not reusable.
Internal address function — store the JavaScript function as the value of an internal address (with Store value enabled), then reference that address in the route. The function is reusable across multiple routes and can be updated without modifying the routes themselves.
For method 2, create an internal address (e.g. $fn/convertTemp), enable Store value, and write a JavaScript function as its value. Then in the route, reference this address as the routing function.
Internal addresses with Store value enabled are ideal for storing configuration values that should persist across reboots:
$config/alertThreshold — a temperature threshold (e.g. 30)$config/alertEmail — an email address for notifications$config/enabled — an on/off flagThese values can be used in route functions or the logic module to make the system behavior configurable without modifying routes.
Internal addresses can be used to aggregate data from multiple protocols into a unified namespace. For example:
%M1 (Modbus temperature) → $building/floor1/temperature%bac:local/analogInput:1 (BACnet humidity) → $building/floor1/humidity%mbus:1/0/value (M-Bus energy) → $building/floor1/energyThe $building/... addresses provide a protocol-agnostic view of the data that can then be routed to MQTT, the cloud, or any other destination.
| Property | Value |
|---|---|
| Readable | Yes |
| Writable | Yes (unless Read only is enabled) |
| Routable | Yes |
| Loggable | Yes |
| Auto-creation | Yes — writing to a non-existing address creates it automatically |
| Property | Value |
|---|---|
| Address regex | $ prefix (any string starting with $) |
| Readable | Yes — returns current cached value |
| Writable | Yes — validated by datatype |
| Internal | Yes (runs in-process, no separate process) |
| Auto-creation | Yes — writing to a non-existing address creates it with readOnly: false, remanent: false |
// datatype 'number' — rejects non-numeric, coerces strings
writeValue('$temp', 22.5) // OK
writeValue('$temp', 'hello') // Error: "not a number"
// datatype 'string' — rejects non-strings
writeValue('$name', 'Room 1') // OK
writeValue('$name', 42) // Error: "not a string"
// datatype 'extend' — deep-merges objects
writeValue('$config', { a: 1 }) // value = { a: 1 }
writeValue('$config', { b: 2 }) // value = { a: 1, b: 2 }
// datatype '' (any) — accepts anything
writeValue('$data', { complex: [1, 2, 3] }) // OK
DATADIR/<gateway_id>/<address_id>