« Back to Product

Documentation

Templates (file format)

Require: Symcon >= 7.0

The "ModBus Device" instance can export its complete configuration as a template and import it again. A template is a JSON file containing all addresses, virtual addresses, required variable profiles, the byte order and the polling settings. A device that has been set up once can thus be set up on other systems with a few clicks or shared with other users.

Warning

Many ready-made templates can be found in our community: Show templates

This page describes the file format completely, so that templates can also be created directly from a manufacturer's data sheet or generated by a script.

Import and export

Action Description
Export template Creates a JSON file from the current configuration. All used profiles that do not start with "~" are exported as well.
Import template Replaces addresses, virtual addresses, byte order and polling settings in the configuration form. The changes are only saved with "Apply". Missing profiles are created in the process, existing profiles are not modified.

The file is checked before the import. If it does not contain the three keys "Addresses", "VirtualAddresses" and "Profiles", the import is aborted with the message "This is not a valid ModBus template!". If a profile already exists with different settings, a hint is shown (e.g. "2 profiles do not match!"). In this case the existing profile is used.

Warning

The import overwrites the existing configuration of the instance. Variables whose ident no longer occurs in the new configuration are deleted when applying - including their archive data.

Structure

A template is a JSON object with the following keys:

Key Type Required Description
Addresses Array Yes List of Modbus addresses, see Addresses
VirtualAddresses Array Yes List of virtual addresses, see Virtual addresses, may be empty
Profiles Object Yes Variable profiles created on import, see Profiles, may be empty
ByteOrder Number No Byte order of the instance, see Byte order
Requests Object No Polling settings, see Polling

The order of the keys is arbitrary. Unknown keys are ignored. A minimal, valid template with a single address looks like this:

{
    "Addresses": [
        {
            "Active": true,
            "Name": "Battery state of charge",
            "Ident": "battery_soc",
            "Translation": [
                { "Language": "de", "Name": "Batterieladezustand" }
            ],
            "DataType": 2,
            "ReadFunctionCode": 4,
            "ReadAddress": 33139,
            "WriteFunctionCode": 0,
            "WriteAddress": 0,
            "Factor": 0,
            "Length": 0,
            "ByteOrder": -1,
            "Profile": "~Battery.100"
        }
    ],
    "VirtualAddresses": [],
    "Profiles": {},
    "ByteOrder": 0,
    "Requests": {
        "Type": 0,
        "Interval": 5000,
        "DataBlocks": []
    }
}

Addresses

Each entry in "Addresses" describes one value of the device. A variable is created below the instance for each active entry.

Field Type Description
Active Bool Defines whether the variable is created and the value is read. Inactive entries remain in the template and can be activated by the user later.
Name String Name of the variable. An English name translated via "Translation" is recommended.
Ident String Ident of the variable. If the field is empty, the ident is generated automatically, see Ident.
Translation Array Translations of the name as a list of objects with "Language" (e.g. "de") and "Name".
DataType Number Data type of the register, see Data types.
ReadFunctionCode Number Function code for reading: 0 (do not read), 1, 2, 3 or 4, see Function codes.
ReadAddress Number Read address (start register or coil), see Addressing.
WriteFunctionCode Number Function code for writing: 0 (do not write), 5, 6, 15 or 16. If a function code is set, the variable gets a standard action.
WriteAddress Number Write address. Usually identical to the read address.
Factor Number Factor the read value is multiplied with. 0 means "no factor", see Factor.
Length Number Strings only: length in bytes (2 bytes per register). 0 for all other data types.
ByteOrder Number Byte order of this address. -1 uses the setting of the instance, see Byte order.
Profile String Name of the variable profile or empty. The profile type must match the variable type, see Data types.
Warning

All fields should always be specified completely and with the correct JSON type. Numbers must not be given as a string ("3") or as null, otherwise applying the configuration fails. The exception is "Active": if it is missing, the address is considered active.

Older exports sometimes contain additional fields like "SwapBytes" or "CustomFactor". These are ignored and can be omitted. The value of a custom factor is always stored in the field "Factor".

Data types in templates

In the template the data type is stored as a number. For compatibility reasons the numbers are not sorted by size.

DataType Shown in the console Registers Variable type (without factor)
0 BOOL 1 bit Boolean
1 UINT8 (MSB) 1 Integer
12 UINT8 (LSB) 1 Integer
2 UINT16 1 Integer
3 UINT32 2 Integer
11 UINT64 4 Float
4 INT8 (MSB) 1 Integer
13 INT8 (LSB) 1 Integer
5 INT16 1 Integer
6 INT32 2 Integer
8 INT64 4 Float
7 FLOAT32 2 Float
9 FLOAT64 4 Float
10 STRING (PLAIN) Length/2 String
14 STRING (HEX) Length/2 String
  • MSB/LSB: UINT8/INT8 read a whole register and use the high-order (MSB) or the low-order byte (LSB). This way two 8 bit values can be read from one register with two entries on the same address.
  • 64 bit: INT64 and UINT64 are mapped to a float variable because Symcon also supports 32 bit systems.
  • STRING (PLAIN): The bytes are interpreted as text. Spaces and null bytes at the beginning and end are removed.
  • STRING (HEX): The bytes are shown as a hex string (upper case), e.g. "0A1B". This is useful for bit fields, version numbers or MAC addresses.
  • Factor: As soon as a factor other than 0 is set, the variable is always created as float - also for integer data types and also for the factor 1.
Warning

The type of a profile must match the variable type. A UINT16 register without factor results in an integer variable and can therefore not get the float profile "~Watt", for example. If an integer value should get a float profile, the factor 1 can be set.

Function codes and addresses

Function code Direction Name Allowed data types
1 Read Read Coils BOOL
2 Read Read Discrete Inputs BOOL
3 Read Read Holding Registers all except BOOL
4 Read Read Input Registers all except BOOL
5 Write Write Single Coil BOOL
15 Write Write Multiple Coils BOOL
6 Write Write Single Register 8/16 bit types only
16 Write Write Multiple Registers all except BOOL

Invalid combinations are rejected with an error message when applying, e.g. "Non-Bit values must use function Read Holding Registers/Read Input Registers" or "Writing in only one register is not possible for multi register values like Int32/UInt32, Int64/UInt64, Float32/Float64, String".

Addressing: "ReadAddress" and "WriteAddress" are the addresses that are actually transmitted in the Modbus telegram, starting at 0. Many data sheets use the classic notation with a prefix instead (e.g. 40001 for the first holding register). In this case the prefix has to be subtracted, see Function codes. Other manufacturers already specify the addresses directly (e.g. Solis with 33000 for an input register). If in doubt, a test with a known value like the serial number or the grid frequency helps.

Byte order

The byte order is defined for the whole instance in the top-level key "ByteOrder". Single addresses can override it with their own "ByteOrder" field. The value -1 uses the setting of the instance. The examples show how the 32 bit value 0x11223344 is transmitted by the device:

ByteOrder Name Transmitted bytes Typical description in the data sheet
-1 Inherited from device ----------------- address level only
0 Big-Endian (Standard) 11 22 33 44 "High word first", "ABCD", Modbus standard
1 Little-Endian 44 33 22 11 "DCBA"
2 Big-Endian (Byte Swap) 22 11 44 33 "BADC"
3 Little-Endian (Byte Swap) 33 44 11 22 "Low word first", "Word swap", "CDAB"

For 16 bit values only 1 and 2 have an effect (bytes swapped within the register). The byte order has no meaning for BOOL values.

Factor

The raw value is multiplied by "Factor" before it is written into the variable. When writing, the value is divided by the factor first. The factor is always a multiplier: a division by 10 is specified as 0.1.

Data sheet Factor
Unit 0.1 V 0.1
Unit 0.01 Hz 0.01
Unit 10 W 10
Wh, wanted in kWh 0.001
no factor 0

The factor must be 0 for BOOL and string addresses. If the device provides its own scale factor in a register (e.g. SunSpec "Scale Factor"), it can be applied using a virtual address.

Ident

The ident identifies the variable permanently. If "Ident" is empty, it is generated from data type, function code and read address: A[DataType][ReadFunctionCode]_[ReadAddress], e.g. "A_7_3100". For virtual addresses the name is used, with all characters except letters, digits and underscore replaced by "".

Warning

Without a fixed ident, the ident changes as soon as data type, function code or address (or the name for virtual addresses) are modified. The old variable is then deleted including its archive data and a new one is created. Templates should therefore always set a fixed, meaningful ident like "battery_soc". Each ident must only occur once - also not between addresses and virtual addresses.

The ident is also used in the scripts of the virtual addresses to access the values.

Virtual addresses

Virtual addresses calculate a variable with PHP from the values of the other addresses or distribute a written value to one or more addresses.

Field Type Description
Active Bool Defines whether the variable is created and the scripts are executed.
Name String Name of the variable
Ident String Ident of the variable. If the field is empty, the ident is generated from the name.
Translation Array Translations of the name, like for addresses
VariableType Number 0 = Boolean, 1 = Integer, 2 = Float, 3 = String
Profile String Name of the variable profile or empty. The profile type must match the VariableType.
ReadAction String PHP code to calculate the value, empty for no calculation
WriteAction String PHP code for writing. If it is set, the variable gets a standard action.

The scripts only contain the body of a function, i.e. without <?php. Line breaks are given as \n (or \r\n) in JSON.

ReadAction: Is executed after each poll. $VALUES contains all address values as an array with the ident as key. Virtual addresses are calculated in order and their result is added to $VALUES as well - a virtual address can therefore access the results of the previous ones. The return value is written into the variable. If null is returned, the variable stays unchanged. The return value must match the VariableType.

return ($VALUES['pv_voltage_1'] ?? 0) * ($VALUES['pv_current_1'] ?? 0);

WriteAction: Is executed when the variable is switched. $VALUE contains the new value, $VALUES the current values of all readable addresses. An array with the ident as key and the value to be written is returned. For each entry the action of the corresponding address is executed, i.e. including factor and data type. If null is returned, nothing is written.

// Only change bits 0-3 of a bit field, keep all other bits
$current = $VALUES['control_register'] ?? 0;
return ['control_register' => ($current & ~0x0F) | ($VALUE & 0x0F)];
Warning

The operator ?? 0 prevents errors as long as an address has not been read yet or is inactive. If a script outputs errors, they can be found in the debug of the instance under the ident of the virtual address.

Values are only written if they change or the variable is older than 60 seconds. This applies to read addresses as well as virtual addresses.

Profiles

"Profiles" is an object with the profile name as key. Profiles whose name starts with "~" are system profiles. They are not exported and do not have to be contained in the template. Custom profiles should get a unique prefix (e.g. "Vendor.Name") so that they do not collide with profiles of other templates.

Field Type Description
Type Number 0 = Boolean, 1 = Integer, 2 = Float, 3 = String
Prefix String Prefix
Suffix String Suffix, including a leading space, e.g. " W"
MinValue Number Minimum value
MaxValue Number Maximum value
StepSize Number Step size. For switchable variables it defines the display as slider.
Digits Number Number of decimal places
Icon String Name of the icon or empty
Associations Array Associations, each with "Value", "Name", "Icon" and "Color" (-1 = no color)

All fields must be specified. The JSON type of "Value" in the associations should match the profile type (true/false for Boolean, whole number for Integer).

"Profiles": {
    "Vendor.OperatingMode": {
        "Type": 1,
        "Prefix": "",
        "Suffix": "",
        "MinValue": 0.0,
        "MaxValue": 0.0,
        "StepSize": 0.0,
        "Digits": 0,
        "Icon": "Information",
        "Associations": [
            { "Value": 0, "Name": "Standby", "Icon": "", "Color": -1 },
            { "Value": 1, "Name": "Running", "Icon": "", "Color": 65280 }
        ]
    }
}
Warning

Only missing profiles are created on import. An existing profile with the same name is not modified, even if it differs.

Polling

The key "Requests" contains the polling settings:

Field Type Description
Type Number 0 = Single addresses, 1 = Data blocks
Interval Number Polling interval in milliseconds for the type "Single addresses". 0 disables polling.
DataBlocks Array Data blocks for the type "Data blocks"

Single addresses: In each interval every active address with a read function code is polled individually. This is simple, but creates a lot of requests for many addresses. Especially with Modbus RTU or slow data loggers the time is often not sufficient.

Data blocks: Each data block reads a contiguous range with a single request and has its own interval. Afterwards all addresses that are completely within the block and use the same function code are updated. This way measured values can be read every 5 seconds and meter readings only every minute, for example.

Field Type Description
Function Number Function code 1, 2, 3 or 4
Address Number Start address
Quantity Number Number of registers or coils (at most 125 registers)
Poller Number Interval in milliseconds
"Requests": {
    "Type": 1,
    "Interval": 5000,
    "DataBlocks": [
        { "Function": 4, "Address": 33049, "Quantity": 10, "Poller": 5000 },
        { "Function": 4, "Address": 33161, "Quantity": 20, "Poller": 60000 },
        { "Function": 3, "Address": 43110, "Quantity": 1, "Poller": 10000 }
    ]
}
Warning

With the type "Data blocks", addresses that are not within any data block are not updated. A data block should only cover registers that the device actually supports - many devices answer a request with a gap in the register range with an error (ILLEGAL_DATA_ADDRESS) for the whole block.

Checklist

  • All three required keys "Addresses", "VirtualAddresses" and "Profiles" are present, even if they are empty.
  • Each address contains all fields with the correct JSON type (no strings or null instead of numbers).
  • Each address and virtual address has a fixed, unique ident.
  • Names are English and have a German translation in "Translation".
  • Addresses are specified as they are transmitted in the telegram (without 30001/40001 prefix).
  • Function code and data type match (BOOL only with 1, 2, 5, 15; multi register values not with 6).
  • The profile type matches the variable type (a factor other than 0 always results in float).
  • Custom profiles have a unique prefix and are completely contained in "Profiles".
  • Rarely needed values are included with "Active": false instead of being omitted.
  • Data blocks are used for many addresses and every active address is within a block.
  • The template has been imported, applied and exported again once. The export must contain the same addresses.
Any questions?