Skip to main content

BB-Eco template JSON format

This page documents the JSON file format used by bb-eco template export, bb-eco template apply, bb-eco template diff, and the desktop app's Download Template / Apply Template actions on a device's Info tab.

For the concepts behind config as code see Configuration as code with BB-Eco. For the CLI commands that read and write this format, see the bb-eco template reference.

Reference templates (download)​

Two fully-commented reference templates document every key, its label on the device's web page, its factory default, and the values the device accepts:

They are .jsonc files (JSON with comments) so every key can carry its explanation inline. template apply parses strict JSON, so strip the comments before feeding one to the tool:

sed 's|//.*||' ed-template-reference.jsonc > my-template.json
bb-eco template validate my-template.json

Example templates​

Real exports, one per model, exactly as bb-eco template export writes them:

ModelExampleWhat it shows
ED-588ed-588.jsonDigital inputs and outputs, quick stop, serial gateway, I/O tunnelling
ED-549ed-549.jsonAnalogue inputs, % of FSR ASCII data format, hexadecimal Modbus data format
ED-560ed-560.jsonAnalogue outputs with values in the hexadecimal data format
ED-582ed-582.jsonRTD inputs, Kelvin over ASCII and Fahrenheit over Modbus
ED-593ed-593.json, ed-593-millivolts.jsonThermocouple inputs in Fahrenheit, and in millivolts
ES-246es-246.jsonAn RS-232 port with FIFO control
ES-511es-511.jsonA switchable port in RS422/485 half-duplex mode, static addressing
ES-522es-522.jsonTwo switchable ports in RS232 mode

Top-level shape​

{
"version": 6,
"family": "ED",
"compatibleModels": ["ED-588"],
"description": "Production line baseline",
"createdAt": "2026-10-22T14:21:59+00:00",
"identity": { /* read-only, shown by bb-eco config */ },
"device": { /* the device's home page: name, location */ },
"network": { /* the Network page */ },
"security": { /* the Security page */ },
"io": { /* ED: the Protocol and IO Lines pages */ },
"serialPorts": [ /* ES, and ED models with a Serial Port page */ ]
}

Every section and every key is optional. A missing key, a null, or a missing section means don't touch: BB-Eco leaves the corresponding setting on the target device unchanged, and template diff never reports it as drift.

How a template follows the device's page​

The groups and keys follow the device's own web page, so a template and the page can be read side by side:

  • Each section is a page, and inside it each group is a heading on that page: io.protocol.ascii is the Protocol page's ASCII Protocol Settings, serialPorts[0].defaultSettings is Serial Port 1's Default Port Settings.
  • A model's export has only the groups and keys its page shows. An ED-549 has io.analogInputs and no digital groups; an RS-232 port has no Duplex Mode.
  • Choices are written as words taken from the page's own option text, such as activeLow for 0 = ON, fallingEdge, engineeringUnit or halfDuplexAutogating. The option text itself is accepted too. Line states are 1 and 0 (the page's ON and OFF), check boxes true and false.
  • In the desktop app, Configuration Status and the Apply Template preview name each change where the page shows it, for example Serial Port 1 > Default Port Settings > Baud Rate. bb-eco config and bb-eco template diff use the template keys, serialPorts[0].defaultSettings.baudRate.
  • A value a device can't take is refused before anything is written, in the app's preview and by bb-eco template validate, apply and diff. Each error names the values that are accepted and links to the section of this page that explains the setting.

Envelope fields​

Five top-level fields describe the template itself, not the device configuration:

FieldTypeDefaultDescription
versioninteger | null6Template version. Omit it and the current version is assumed. See Versioning.
familystring | nullfrom device on exportTarget device family: "ED", "ES", or "BB". Apply refuses to push a template to a device of a different family.
compatibleModelsstring[] | nullthe exported modelOptional model allowlist. If set, apply refuses a device whose model isn't in the list. Naming the models also lets template validate check each IO line's number offline.
descriptionstring | nullnullHuman-readable description. Shown in the desktop app's template list and in bb-eco template validate output.
createdAtRFC 3339 timestamp | nullexport timeWhen the template was generated. Informational only.

These envelope fields are left out of the bb-eco config <device> listing but written by bb-eco template export.

identity: read-only device identity​

A read-only snapshot of one device. bb-eco template export and the app's Download Template leave it out, so an exported template is portable from the start; bb-eco config <device> shows it as part of the device readout. Never applied by template apply, never compared by template diff. If you add it to a template by hand, it is ignored.

"identity": {
"mac": "00:0A:4F:06:25:4D",
"ip": "192.168.1.101",
"model": "ED-549",
"modelVariant": null,
"firmwareVersion": "8.25",
"firmwareType": "T",
"firmwareDate": "26/06/2023",
"checkConnectivity": false,
"dns": "",
"modbusSlaveId": 255
}
FieldTypeDescription
macstringHardware MAC, colon-separated uppercase
ipstringAddress the device was reachable at
modelstringModel number (e.g. ED-549)
modelVariantstring | nullHardware variant suffix, if any
firmwareVersionstringRunning firmware version
firmwareTypestring | nullChip-class marker (e.g. T)
firmwareDatestringFirmware build date as the device reports it
checkConnectivityboolean | nullThe device's connectivity check setting, as reported
dnsstring | nullED: the DNS server the device reports
modbusSlaveIdint | nullED: the Modbus address the device reports (Modbus TCP does not use it)

device: the device's home page​

"device": {
"name": "Pumphouse1",
"location": "Pumphouse"
}
FieldPage labelTypeNotes
nameDevice Namestring | nullMaximum 10 characters on ED, 40 on ES. BB-Eco rejects longer values before they reach the device
locationLocationstring | nullMaximum 10 characters. ED only: ES devices have no location

On ED devices name and location are always written together as one operation, because the device only accepts them as a pair.

network: the Network page​

"network": {
"dhcp": false,
"subnet": "255.255.255.0",
"gateway": "192.168.127.1",
"webPort": 80,
"upnp": true
}
FieldPage labelTypeNotes
dhcpDHCPboolean | nulltrue = DHCP, false = static. Static addressing wants staticIp + subnet + gateway together; apply fills any omission from the device's current configuration
staticIpIP Addressstring | nullDotted quad. Never exported (see Static IP addresses). Leave it out to keep the device's current address
subnetSubnet Maskstring | nullDotted-quad subnet mask. Exported under static addressing only: under DHCP the DHCP server provides it
gatewayGateway Addressstring | nullDefault gateway IP. 0.0.0.0 = no gateway. Exported like subnet
webPortWeb Server Portinteger | null1–65535 (default 80). Changing it restarts the device's web server
upnpUPnPboolean | nullUPnP/SSDP announcements on/off

network is applied last in the apply order so that an IP change can't strand the rest of the apply.

Static IP addresses​

An exported template never contains the device's IP address. That holds even for a device on a static address: the export records "dhcp": false with its subnet and gateway, and leaves out staticIp. This keeps a template safe to apply to other devices, because two devices given the same address would conflict on the network.

When you apply a template, staticIp works like this:

TemplateResult on the device
"dhcp": false, no staticIpStatic addressing, keeping the address the device has now. A device that was on DHCP keeps its current lease address as its static address
"dhcp": false, "staticIp": "192.168.127.30"Static addressing at 192.168.127.30, with the template's subnet and gateway
"dhcp": trueDHCP. staticIp, subnet and gateway are ignored, and the device keeps its previous static settings

To give a device a specific address, add staticIp to a copy of the template and apply that copy to that one device:

"network": {
"dhcp": false,
"staticIp": "192.168.127.30",
"subnet": "255.255.255.0",
"gateway": "192.168.127.1"
}
bb-eco template apply 192.168.127.24 ed-588-at-30.json --dry-run
bb-eco template apply 192.168.127.24 ed-588-at-30.json

The device moves to its new address at the end of the apply. Run bb-eco discover to find it there.

security: credentials and access control​

Excluded from export by default. Pass --include-security to opt in for usernames, MAC filters, and authentication. Passwords are never written by export; a template only carries one if you type it in (rarely a good idea; see the warning below).

"security": {
"adminUsername": "admin",
"adminPassword": null,
"userUsername": null,
"userPassword": null,
"adminMacFilterEnabled": false,
"adminMacAddress": "000000000000",
"userMacFilterEnabled": null,
"userMacAddresses": ["000A4F112233"],
"authentication": "Basic",
"resetProtection": "Everybody"
}
FieldTypeNotes
adminUsername, userUsernamestring | nullAccount usernames, up to 20 characters. Authentication turns on the moment a username is non-empty
adminPassword, userPasswordstring | nullUp to 20 characters. Never commit these to Git
adminMacFilterEnabled, userMacFilterEnabledboolean | nullMAC-based access control toggles
adminMacAddressstring | nullAdmin MAC as exactly 12 hex digits, no separators. 000000000000 = unset. Must differ from every entry in the user MAC list
userMacAddressesstring[] | nullRead-only via templates. The device manages this list one entry at a time (up to 32 entries), so maintain it through the device web UI
authenticationstring | null"Basic" or "Digest"
resetProtectionstring | null"Everybody" or "AdminOnly": controls who may factory-reset or restart the device

Apply uses read-modify-write: null fields are filled from the device's current values, so a partial security template can never accidentally clear a password.

Don't commit passwords

A template can carry passwords if you add them by hand, but storing passwords in version control is rarely the right answer. Prefer leaving adminPassword and userPassword as null in committed templates and setting them through a separate secure-secrets workflow.

io: the Protocol and IO Lines pages (ED only)​

io holds the Protocol page in io.protocol, then one group per line type: digitalOutputs, digitalInputs, quickStop, analogInputs, analogOutputs, rtdInputs and thermocoupleInputs. Each line-type group holds the settings that act on every line of that type, then its lines. A model has only the groups its page shows:

"io": {
"protocol": {
"current": "ASCII",
"ascii": { "address": 1, "tcpPort": 9500, "idleTimeout": 0, "checksum": false,
"serialGateway": { "baudRate": 9600, "commandTimeout": 200 } },
"modbus": { "respondToServerIds": "0 and 255", "acceptAllAddresses": false,
"tcpPort": 502, "idleTimeout": 0, "maxConnections": 8 }
},
"digitalOutputs": {
"polarity": "activeHigh",
"lines": [ { "line": "DOut 0", "index": 0, "powerOnValue": 0, "safeValue": 0, "quickStopValue": 0 } ]
},
"digitalInputs": {
"polarity": "activeHigh",
"counterUpdateDirection": "fallingEdge",
"counterMode": "16bit",
"lines": [ { "line": "DIn 0", "index": 8, "debounceMs": 0 } ]
},
"quickStop": { "line": "disabled", "edge": "fallingEdge" }
}
note

Applying protocol settings restarts the device, so plan for a brief loss of IO connectivity.

io.protocol: the Protocol page​

FieldPage labelTypeNotes
currentCurrent Protocolstring | null"ASCII" (factory) or "Modbus". Only the current protocol answers; each protocol keeps its own settings

io.protocol.ascii, ASCII Protocol Settings:

FieldPage labelTypeNotes
addressDevice Addressint | null1–255 (factory: 1)
tcpPortTCP Portint | null1–65535 (factory: 9500)
idleTimeoutIdle Timeoutint | nullSeconds, 0–65535 (0 = no timeout)
dataFormatData Formatstring | nullAnalogue and temperature models; see Data formats
checksumChecksumboolean | nullFactory: false
serialGatewaySerial Gateway Settingsobject | nullModels with an RS-485 serial gateway: ASCII commands for another device address are passed to the RS-485 port. baudRate: 1200, 2400, 4800, 9600 (factory), 19200, 38400, 57600 or 115200. commandTimeout: milliseconds, 0–50000 (factory: 200)
tunnelingI/O Tunnelingobject | nullSee below

io.protocol.modbus, Modbus TCP Settings:

FieldPage labelTypeNotes
respondToServerIdsRespond to Server IDsstring | null"0 and 255" (factory) or "0-247 and 255"
acceptAllAddressesAccept All Register/ Coil/ Input Addressesboolean | nullFactory: false
tcpPortTCP Portint | null1–65535 (factory: 502)
idleTimeoutIdle Timeoutint | nullSeconds, 0–65535 (0 = no timeout)
maxConnectionsMax. Connectionsint | null1–8 (factory: 8)
dataFormatData Formatstring | nullAnalogue and temperature models; see Data formats

Data formats​

Each protocol has its own Data Format on analogue and temperature models:

Modelio.protocol.ascii.dataFormatio.protocol.modbus.dataFormat
ED-549, ED-560engineeringUnit, percentOfFullScale, hexadecimalengineeringUnit, hexadecimal
ED-582celsius, fahrenheit, kelvin, ohmsthe same
ED-593celsius, fahrenheit, kelvin, millivoltsthe same

Temperature models measure in the data format of the current protocol. Analogue output values are written in the ASCII data format, so a template that sets them must carry io.protocol.ascii.dataFormat too. When such a template changes the ED-560's ASCII data format, BB-Eco writes the protocol settings first, waits for the device to restart, then writes the line settings, so every output value is read in the new format.

io.protocol.ascii.tunneling: I/O tunnelling​

I/O tunnelling mirrors this device's digital inputs onto the outputs of a remote device over the network (ED-516, ED-527, ED-538, ED-588). It is turned on at one device only, the one that connects (the client); the remote device just needs the ASCII protocol and a matching device address (see the caution below).

"tunneling": {
"enabled": true,
"remoteIp": "192.168.1.100",
"remoteAsciiPort": 9500,
"remoteAsciiAddress": 1,
"safeModeOnDisconnect": true
}
FieldPage labelTypeNotes
enabledEnableboolean | nullThis device connects to the remote device and keeps the tunnel open (factory: false)
remoteIpRemote IP Addressstring | nullIPv4 address of the remote device. An enabled tunnel needs a real address; 0.0.0.0 is refused
remoteAsciiPortRemote ASCII Device TCP Portint | nullThe remote device's ASCII TCP port, 1–65535: its io.protocol.ascii.tcpPort (factory: 9500)
remoteAsciiAddressRemote ASCII Device Addressint | null1–255 (factory: 1). Must equal the remote device's own io.protocol.ascii.address; see the caution below
safeModeOnDisconnectSet to Safe Mode When Disconnectedboolean | nullOutputs go to their safe values when the tunnel drops (factory: true)

{ "io": { "protocol": { "ascii": { "tunneling": { "enabled": true } } } } } on its own turns a tunnel back on with the remote device the device already has.

caution

The two devices address each other by ASCII device address: this device's remoteAsciiAddress must equal the remote device's io.protocol.ascii.address, and the remote device's remoteAsciiAddress must equal this device's io.protocol.ascii.address. A remote device that has never been configured keeps the factory remoteAsciiAddress of 1, so leave this device's address at 1 or set the remote's to match. Both devices must use the ASCII protocol with the same checksum setting. When the addresses disagree, the tunnel connects but never completes; the device's Protocol page, and the Info tab in BB-Eco, show Negotiate IO tunneling settings....

An export carries remoteIp, so applying one template to several devices points all of them at the same remote device. Changing any tunnel setting restarts the device, like the other protocol settings.

How a line is named​

Each entry in a group's lines is named two ways, and an export writes both:

  • line is the line's label on the device's page: "DOut 0", "RL 0" (relays), "DIn 0", "AIn 0", "AOut 0", "RTD 0", "TC 0".
  • index is its ASCII line number, the number ASCII commands use. Outputs and analogue lines count from 0. Digital inputs follow the outputs: on a model with outputs DIn 0 is line 8 (ED-588, ED-038); on the input-only ED-516 it is line 0.

A template you write needs one of the two, and when it has both they must agree. Neither is a setting: they say which line the settings are for. A template can list only the lines it changes: apply reads the device's current line configuration and merges, because the device accepts line configuration only as a complete set. If the device reports an active ASCII/Modbus client connection, it refuses line changes until the client disconnects; apply reports the device's own message when that happens.

io.digitalOutputs and io.digitalInputs​

FieldPage labelTypeNotes
digitalOutputs.polarityOutput Polaritystring | null"activeHigh" (1 = ON, factory) or "activeLow" (0 = ON). Shown on the ASCII Protocol tab; applies in every protocol
digitalInputs.polarityInput Polaritystring | null"activeHigh" (High = 1, factory) or "activeLow" (Low = 1)
digitalInputs.counterUpdateDirectionCounter Update Directionstring | null"fallingEdge" (factory) or "risingEdge"
digitalInputs.counterModeCounter Modestring | null"16bit" (factory) or "32bit"

Each line of digitalOutputs.lines (relays included):

FieldPage labelTypeNotes
powerOnValuePower-On Value0 | 1 | nullThe state at power-up
safeValueSafe Value0 | 1 | nullThe state when all network connections are lost
quickStopValueQuick Stop Value0 | 1 | nullThe state when the quick stop fires. Models with a quick stop

Each line of digitalInputs.lines:

FieldPage labelTypeNotes
debounceMsDebounce Time(ms)int | null0–9999 milliseconds, on models with debounce

Digital lines keep their factory names, so they have no name.

io.quickStop​

On models with both digital inputs and outputs, an input can drive every output to its Quick Stop Value. The page reads Please Select Quick Stop IO Line [DIn 0] when [Falling Edge]:

FieldPage labelTypeNotes
lineQuick Stop IO Linestring | null"disabled" (factory) or an input's label, "DIn 0"
edgeQuick Stop Edgestring | null"fallingEdge" (factory) or "risingEdge"

Analogue and temperature lines​

analogInputs (ED-549), analogOutputs (ED-560) and rtdInputs (ED-582) have no settings of their own beyond their lines. thermocoupleInputs (ED-593) also has the page's thermocouple settings:

FieldPage labelTypeNotes
openWireDetectionEnable Open-Wire Detectionboolean | nullFactory: true
coldJunctionCompensationEnable Cold Junction Compensationboolean | nullFactory: true
manualOffsetT manual offsetnumber | nullAdded to every reading, in manualOffsetUnit: −99.99 to +99.99 in celsius or kelvin, −179.96 to +179.96 in fahrenheit (factory: 0)
manualOffsetUnitstring | null"celsius", "fahrenheit" or "kelvin": the unit manualOffset is written in

Each analogue or temperature line:

FieldPage labelLinesNotes
nameNameallAt most 10 characters
enabledEnabledinputstrue or false. Analogue outputs have no Enabled column
fullScaleRangeFull Scale RangeanalogueInputs: +/-10V, +/-5V, +/-2.5V, +/-1V, +/-500mV, +/-250mV, +/-150mV, +/-75mV, +/-20mA, +0 to 20mA, +4 to 20mA. Outputs: 0 to 10V, 0 to 20mA, 4 to 20mA. The page's ± sign is accepted
powerOnValue, safeValuePower-On Value, Safe Valueanalogue outputsWritten in the ASCII data format: a signed number within the range in engineeringUnit ("+05.000"), 0 to 100 in percentOfFullScale, four hex digits ("0800") in hexadecimal
rtdTypeRTD TypeRTDPt100-385, Pt100-3911, Pt100-3916, Pt100-3926, Pt1000-385, Pt1000-3911, Pt1000-3916, Pt1000-3926, Pt1000-375, Ni120-672
connectionConnectionRTD"4-wire" (factory), "3-wire" or "2-wire"
thermocoupleTypeTC TypethermocoupleJ, K, T, E, R, S, B, N

serialPorts: serial ports (ES, and ED models with a Serial Port page)​

One entry per serial port, grouped the way the port's own page is: Serial Port 1, then Default Port Settings, Protocol Settings and Serial Tunneling. A port's export has only the settings its page shows:

"serialPorts": [
{
"port": 1,
"portType": "RS422/485",
"defaultSettings": {
"alwaysUseTheseSettings": false,
"baudRate": 19200,
"dataBits": 8,
"parity": "even",
"stopBits": "1",
"flowControl": "none",
"duplex": "halfDuplexAutogating"
},
"protocolSettings": {
"protocol": "rawTcp",
"localTcpPort": 9001,
"connectionTimeout": 0
},
"tunneling": {
"enabled": false,
"remoteIp": "192.168.0.0",
"remoteTcpPort": 9001
}
}
]
FieldPage labelTypeNotes
portSerial Port 1intThe port's number, 1–8. Required: it says which port the settings are for, and is never written
portTypePort Typestring | null"RS232" or "RS422/485". On RS232/422/485 ports, whose page offers the choice

defaultSettings, Default Port Settings:

FieldPage labelTypeNotes
alwaysUseTheseSettingsAlways use these settings (Ignore settings from application)boolean | nullFactory: false
baudRateBaud Rateint | nullBit/s, 60–1,000,000 (up to 3,000,000 on some ES-522 hardware). Any rate in range is accepted (factory: 115200)
dataBitsData Bitsint | null5–8 (factory: 8)
parityParitystring | null"none" (factory), "odd", "even", "mark", "space"
stopBitsStop Bitsstring | null"1" (factory), "2", "1.5" (1.5 on some hardware)
flowControlFlow Controlstring | null"none" (factory), "rtsCts", "dtrDsr", "xonXoff". RTS/CTS and DTR/DSR need those lines on the port; the device rejects a mode its port cannot do
fifoEnable FIFOboolean | nullOn ports whose page shows it. Changing it restarts the device
duplexDuplex Modestring | null"fullDuplex" (RS422 full-duplex mode), "halfDuplexAutogating" (RS485 half-duplex autogating mode), or "none" while a switchable port is in RS232 mode. Not on an RS232-only port

protocolSettings, Protocol Settings:

FieldPage labelTypeNotes
protocolProtocol Typestring | null"telnet" (Telnet, factory) or "rawTcp" (Raw TCP)
localTcpPortLocal TCP Portint | null1–65535; a port already in use on the device is rejected (factory: 9001 for port 1, 9002 for port 2, …)
connectionTimeoutConnection Timeoutint | nullSeconds, 0–65535 (0 = not used)

tunneling, Serial Tunneling: a tunnel to another device's serial port across the network:

FieldPage labelTypeNotes
enabledEnableboolean | nullFactory: false
remoteIpRemote IP Addressstring | nullThe other device. Enabling the tunnel needs a real address, the template's or the port's own: the factory 192.168.0.0 (and 0.0.0.0) is refused
remoteTcpPortRemote TCP Portint | null0–65535 (factory: the port's own Local TCP Port)

A template may set only the settings it cares about. Apply reads the port's current configuration and merges before writing, because the device accepts port configuration only as a complete set.

warning

A serial port with a connected TCP client rejects configuration changes: apply reports the error and the port keeps its old settings. Disconnect clients first.

Apply order​

bb-eco template apply pushes the sections to the device in a fixed order so a connection-dropping change can't strand the rest of the apply:

  1. device: name, location (no connection impact)
  2. security: credentials (no connection impact)
  3. io (ED): protocol changes restart the device
  4. serialPorts (ES, and ED models with a Serial Port page), one port at a time: FIFO changes restart the device
  5. network.webPort + network.upnp: may disconnect if the web port changes
  6. network.dhcp, staticIp, subnet, gateway: may disconnect if the IP changes

If the device becomes unreachable after step 5 or 6, apply treats this as success: the new settings took effect, and the device is now at its new address. bb-eco discover finds it again.

Versioning​

version: 6 is the current schema, and the only one BB-Eco reads. A template from an earlier release (BB-Eco 1.0 exports version: 3) is refused with a request to re-export it from the device; a template from a newer release is refused until you update BB-Eco. template validate, template apply and template diff all refuse it, so a diff never reports a device compliant on settings it could not read.

Version 6 grouped the settings the way each device's page groups them. A hand-written template that still uses an earlier key is refused with the key's place now, even when it has no version, rather than applied with that setting left out. For example io.asciiAddress is now io.protocol.ascii.address, and serialPorts[0].baudRate is now serialPorts[0].defaultSettings.baudRate.

Don't set the field manually; BB-Eco writes the right value on export, and a hand-written template can leave it out.

More resources​