Skip to content

CommandServer (TCP, UDP, HTTP) ​

A CommandServer is a dedicated connection of the nomos system Controller to a device, via TCP, UDP, HTTP, HTTPS or a serial interface. Profiles with the engine type CommandServer ("engineType": "") describe this connection in the structure block. How the rest of a profile is built is described under Profile Format; complete examples are under Example Profiles.

How it works ​

json
"structure": {
	"config":   { "SERVERPROTOCOL": {"value": "TCP", "editable": false}, … },
	"commands": { "READ": {"sequence": "…", "parameterCount": 0}, … },
	"mappings": { "POWER": {"match": "…", "condition": "…", "onChange": true}, … }
}
  1. Connection (config): Where and how the controller sends.
  2. Commands (commands): What is sent. An action of the profile, a polling or an automation calls a command, if required with a value.
  3. Response patterns (mappings): What the controller reads from the device's responses and messages. Every pattern that matches delivers a named value, e.g. POWER=1250.
  4. The profile's feedback values (mappings at the top level) fetch these values via their property: {"method": "get", "property": "POWER"}.

When a device is created, each connection gets its own class name made of className and a sequential number, e.g. KEBA_P30_1. Under this name you can send commands manually in the Internal Service, e.g. <KEBA_P30_1><REPORT2></KEBA_P30_1> or <KEBA_P30_1><SETCURR=16000></KEBA_P30_1>, and follow the responses in the output.

Connection ​

Each setting has a value and a statement whether it is requested when the device is created:

json
"SERVERIP":   {"value": "", "editable": {"type": "host", "default": ""}},
"SERVERPORT": {"value": "502", "editable": {"type": "integer", "default": "502"}},
"SERVERMODE": {"value": "HEX", "editable": false}
  • editable: false: The value is fixed.
  • editable: {"type": …, "default": …}: The Create device dialog requests the value, pre-filled with default. type is host, string, password, bool (becomes YES/NO) or integer/float (number field). In addition, help is possible as a help text. Note: a password is also shown in plain text in the dialog.
  • comment: true: The setting has no effect but can still be used as a placeholder.
SettingMeaningDefault
SERVERPROTOCOLTCP, UDP, HTTP, HTTPS or SERIAL. A misspelt value counts as UDPUDP
SERVERIPIP address of the device. For TCP and UDP only an IPv4 address, no host name. For HTTP(S) a host name is also possibleRequired
SERVERPORTPort. Serial (see Serial devices): device;baud;databits;parity;stopbits;flow, e.g. /dev/ttyUSB0;9600;8;N;1;NONE or AUTO;9600;8;N;1;NONERequired
SERVERMODEASCII (text) or HEX (bytes as hex digits). Always ASCII for HTTPRequired
SERVERTIMEOUT_IN_MSHow long the connection stays open after the last command and waits for responses, in ms. NONE keeps it open permanently. Takes precedence over SERVERTIMEOUTRequired¹
SERVERTIMEOUTThe same in secondsRequired¹
MATCHINGFULL or NORMAL, see Response patterns. FULL for almost all devicesNORMAL
SPACINGPause after each sent command in ms0
CONNECTEVENT, DISCONNECTEVENTMessage when the connection is established or ends, usually REACHABLE=1 and REACHABLE=0, see Reachability–
STARTUPCMDCommands sent at startup, separated by commas–
SHUTDOWNCMDCommands sent every time the connection ends–
HEARTBEATCMDcommand;seconds, e.g. REPORT2;30: sent if no other command has run for that long–
HEARTBEATTIMEOUTTime in ms to wait for any response after the heartbeat; without a response, the connection is ended1000
RECONNECTYES: send STARTUPCMD again after the connection has ended. Only works together with HEARTBEATCMD and STARTUPCMDNO
RETRYINTERVALSeconds until the next attempt if connecting or sending fails. Repeats indefinitely, see Reachability0
CMDPREFIX, CMDSUFFIXText before or after every command, e.g. /api/ for HTTP or \x0D for text protocols–
SEQUENCEPREFIX, SEQUENCESUFFIXText before or after the whole sent packet–
MAPPREFIX, MAPSUFFIXText before or after every response pattern–
STARTDELIMITER, ENDDELIMITERStart or end of a message, see Messages across several packets–
SERVERTYPEChecksum, HEX only: MODBUS (CRC16 for Modbus RTU, placeholder XXXX in the sequence), SUM, SUM=a-b, XOR, XOR=a-b (over bytes a to b, appended), plus RUS, ADNOTAM, SPEAKERCRAFT, TECNOALARM, SOUNDWEB, ESP3 for the protocols of the same name–
USERNAME, PASSWORDHTTP(S): basic authentication if both are set. TCP, UDP, serial: after the connection is established, first the user name and then the password are sent, each with a line ending. 31 characters at most each–
LOCALPORTUDP: local port to which the device responds; RANDOM for a random one. If the setting is missing, SERVERPORT appliesSERVERPORT
NEGOTIATETELNET: answer the Telnet negotiation and remove it from the responses–
RESOLVE_SYSVARSYES: insert variables in square brackets ([ID]) in sequences every time something is sent, see VariablesNO
URL_SAFE_CHARSHTTP(S): characters that should not be encoded in the address, e.g. ()–
DEBUGYES: output every sequence in the Internal ServiceNO
ACTIVENO switches the connection offYES

¹ One of the two time settings must be set.

WARNING

A faulty connection is discarded without a message. If SERVERMODE or a time setting is missing, the port is invalid, or SERVERIP contains a host name instead of an IP address for TCP/UDP, the controller still creates the device, but it never sends or receives anything. In this case, check the Internal Service; at startup it shows config of … is not valid - skipping.

Placeholders ​

In sequences, response patterns, conditions, bodies and camera URLs, the controller replaces %NAME% with the value of the setting NAME as it was entered when creating the device, otherwise with the value in the profile. In addition, %CLASSNAME% (e.g. KEBA_P30_1) and %PLATFORM% are available. Settings of your own that the device does not know are allowed; with comment: true they have no effect and only serve as placeholders:

json
"config": {
	"DEVICEID": {"value": "1", "editable": {"type": "integer", "default": "1"}, "comment": true}
},
"commands": {
	"STATUS": {"sequence": "/api/device/%DEVICEID%/status", "parameterCount": 0}
}

Commands ​

json
"commands": {
	"STATUS": {"sequence": "status?filter=frc,car,ama", "parameterCount": 0},
	"SETAMA": {"sequence": "set?ama=\\#", "parameterCount": 1},
	"READ_INSTANT": {"sequence": "000000000006010400000020", "parameterCount": 0, "tag": "instant"}
}
FieldMeaning
KeyName of the command in upper case. SETNAME and GETNAME are the commands for actions with "method": "set" or "get" and "property": "NAME"
sequenceWhat is sent, see below
parameterCountNumber of \# in sequence and body
parameterCondition and format for the value sent along, see below
timeoutReplaces SERVERTIMEOUT_IN_MS for this command (ms). 0: send and close the connection immediately. NONE: keep the connection open after this command
spacingReplaces SPACING for this command (ms)
tagAssigns responses to this command to specific response patterns, see Tags
httpMethod, contentType, bodyHTTP(S) only, see HTTP

Sequence ​

  • ASCII: Text. Write control characters and arbitrary bytes as \x0D (carriage return), \x0A (line feed) etc. A semicolon as \x3B.
  • HEX: Hex digits only, two per byte, without spaces, e.g. 000000000006010400000020.
  • HTTP(S): Path with query, e.g. /api/status?filter=car. Write characters as they are meant: the controller encodes the address itself; a pre-encoded %20 would become %2520.

\# is the place for the value the command is called with. Several values are passed separated by commas and fill the places in order. If the value is missing entirely, the controller sends nothing and reports an error.

In HEX mode, a value without a format is inserted as exactly one byte (5 → 05, -1 → FF). For larger values, specify a format such as {%hex4} (two bytes).

Parameter ​

parameter has the form condition{%format}; both are optional:

  • The format is applied to the value before it is inserted. Example: "sequence": "MV\#", "parameter": "{%+100,%c2,2}" turns 5 into the text MV05 (5 + 100 = 105, of which two characters from the second position). All filters are listed under Format filters.
  • The condition checks the value, e.g. >=0 or <>99. If it is not met, the controller sends nothing.

HTTP ​

  • The address is http:// or https://, then SERVERIP, then the sequence; the port comes from SERVERPORT.
  • httpMethod: GET (default), POST, PUT, PATCH, DELETE. For everything except GET, body and contentType can be specified; without contentType, the controller detects JSON ({, [), XML (<) or otherwise a form. \# in the body takes values after the places in the sequence have been filled.
json
"SETMODE": {"sequence": "/api/mode", "httpMethod": "POST",
	"contentType": "application/json", "body": "{\"mode\": \\#}", "parameterCount": 1}
  • A GET only counts as successful if the device responds with 200 and content. Other methods count as successful with any 2xx response.
  • The response runs through the response patterns as text.
  • Authentication: basic only (USERNAME, PASSWORD). With HTTPS, the certificate is not checked, so self-signed certificates work.
  • The time limit of a request is SERVERTIMEOUT_IN_MS; timeout on the command has no effect for HTTP.

Response patterns ​

json
"mappings": {
	"CAR": {"match": "\"car\":\\*,", "onChange": true},
	"WH": {"match": "\"wh\":\\*,", "onChange": true, "condition": "{%/1000.00}"},
	"GRID": {"match": "000000000043010440\\!48!\\^^^^^^^^", "onChange": true,
		"condition": "{%swap32,%float,%*-1}", "tag": "instant"}
}
FieldMeaning
KeyName of the value, e.g. CAR. Feedback values of the profile fetch the value with "property": "CAR".
matchPattern, see below. Case does not matter
conditionCondition and format, as for the parameter of a command. The value is formatted first, then checked. If the condition is not met, the value is not taken over
onChangetrue: only run localAction if the value has changed. The value itself is always passed on
localActionOptional: command that runs on a match; \# stands for the value. See Action and commands
tagSee Tags

Patterns ​

A pattern is text (for HEX: hex digits) in which placeholders start with \. In JSON, \ is written twice: \\*.

PlaceholderMeaningExample
\*Captures everything up to the text that followsVOL=\*| on VOL=42|MUTE=0 gives 42
\^Captures exactly one character; \^^^^ captures fourPWR\^ on PWR1 gives 1
\?Skips exactly one character; \?? two01\?\?\^^ on 0102030405 gives 03
\%Skips everything up to the text that followsB=\% \*X on A=1 B=22 C=3X gives C=3
\!n!Skips n characters (for HEX two characters per byte)01\!4!\^^ on 0102030405 gives 04

A pattern without placeholders only checks whether the text occurs in the response.

Note:

  • A \* at the very end captures everything up to the end of the response. It is usually better to specify the text after it that ends the value, e.g. , or \x0D.
  • The last field of a JSON object does not end with a comma but with }. "\"power\":\\*," therefore does not find power if it is the last field; write "\"power\":\\*}" for this. For devices that send fields in varying order, better query one field more than you need, so that no required field is at the end.
  • Several capture points in one pattern result in one value without separators: \?\?\^^\^^ on 0001020304 gives 0102.
  • If a pattern reaches beyond the end of the response (\^, \? or \!n! after the last character), it does not match. A response that is too short therefore yields no value instead of a wrong one.

FULL and NORMAL ​

  • MATCHING FULL: Every pattern searches the whole response. The right choice for responses with a fixed structure (JSON, Modbus, status lines).
  • NORMAL (default): After a match, the following patterns only see the rest of the response. The order of the patterns must then follow the order of the data.

In both cases, each pattern matches at most once per response. As soon as there is at least one pattern, everything that matches none is discarded.

Name and value ​

If a pattern captures a value, it passes on NAME=value. If it captures none (pattern without placeholders, or \* stays empty), it only passes on the name. That is why patterns for fixed responses carry the value in the name:

json
"POWER=1": {"match": "PWON\\x0D"},
"POWER=0": {"match": "PWSTANDBY\\x0D"}

Both deliver the value POWER, once with 1, once with 0; the feedback value {"property": "POWER"} receives both. The Profile Editor appends =1 itself when saving if a pattern has no placeholder for capturing and the name does not yet contain =.

HEX responses ​

In HEX mode, a response is a sequence of hex digits in upper case. A captured value is converted into decimal as an unsigned number (00FF → 255), unless the format starts with %raw, %swap16, %swap32, %float or %int16/%int32:

  • Signed registers (Modbus INT16/INT32): {%int16} or {%int32}.
  • Decimal numbers (FLOAT32, byte order as usual for Modbus): {%swap32,%float}.

Example Modbus TCP: the response to “Read 32 registers from address 0” starts with 000000000043010440 (transaction, protocol, length 0x43, device 1, function 4, 0x40 bytes of data). \!48! skips the first 24 bytes (6 registers), \^^^^^^^^ captures the next 4 bytes, i.e. a FLOAT32 from register 12.

Tags ​

If a command has a tag, its responses are evaluated only by the patterns with the same tag; responses to commands without a tag only by the patterns without a tag. This makes it possible to distinguish responses with the same structure, e.g. two Modbus queries of different register ranges. The tag applies until the next command is sent, also for messages the device sends on its own. Choose SPACING at least as long as the device's response time, so that a late response is not attributed to the next command.

Calculated values ​

A pattern with an empty match and a condition of the form {$NAME$…} calculates a value from other values:

json
"CHARGING": {"match": "", "onChange": true, "condition": "{$CAR$,%in2}"}

$NAME$ is the last delivered value of another pattern; several can be combined with + - * /. Format filters follow. In the example, CHARGING is 1 if CAR has the value 2, otherwise 0.

Messages across several packets ​

Without delimiters, the controller evaluates each received packet separately. If a longer message arrives in two TCP packets, a pattern then does not match it. With ENDDELIMITER (e.g. \x0D\x0A), the controller collects data until the end arrives and evaluates each message separately. The delimiters belong to the message and also appear in the pattern.

Action and commands ​

Commands to the controller have the form <CLASS><COMMAND></CLASS> or, with a value, <CLASS><COMMAND=value></CLASS>. Several commands of one class are written one after the other, <KEBA_P30_1><REPORT2><REPORT3></KEBA_P30_1>, and the same applies to several classes. Case does not matter. Classes are:

  • every connection of a CommandServer device, e.g. KEBA_P30_1, with the commands from structure.commands,
  • SYS for system commands, for example:
CommandEffect
SETVAR=ID,valueSets the variable ID, see Variables
GETVAR=IDReturns the value of the variable
WAKEONLAN=00:11:22:33:44:55Sends a Wake-on-LAN packet to this MAC address
GETSERIALPORTLISTLists the serial interfaces, see Serial devices

In the Internal Service you send such commands manually. The response appears in the output as parser: reply sequence: …, e.g. <KEBA_P30_1>OK</KEBA_P30_1>; an unknown command responds with NOK.

Action of a response pattern: localAction is such a command. It runs on every match, with onChange only if the value has changed. \# stands for the captured value; if the pattern captures no value, an action with \# does not run. Examples:

json
"REBOOT": {"match": "\"rbt\":\\*,", "onChange": true,
	"localAction": "<SYS><SETVAR=WALLBOX_UPTIME,\\#></SYS>"},
"TV_VOLUME": {"match": "data volume \\*\\x0D", "onChange": true,
	"localAction": "<AVR_1><VOLUME_SET=\\#></AVR_1>"}

The first writes every new value into a variable, the second passes the volume of a TV on to an AV receiver that is created as a separate CommandServer device with the class AVR_1. Logic with conditions, times and several devices belongs in Automations; an action is meant for passing a value on.

Format filters ​

Formats are written in curly brackets at the end of parameter or condition, several separated by commas, and are applied from left to right: {%swap32,%float,%*-1}.

FilterEffectExample
%+n, %-n, %*n, %/nArithmetic. The result has as many decimal places as n4523.7 with %/1000 → 5, with %/1000.00 → 4.52
%int16, %int32Hex as a signed numberFFFF → -1
%floatHex as FLOAT32; usually preceded by %swap32%swap32,%float: 43668000 → 230.500000
%swap16, %swap32Swap the byte orderA1B2C3D4 → d4c3b2a1 (swap32)
%decHex as an unsigned number00FF → 255
%hex, %hexNInteger as hex, with N digits255 with %hex4 → 00FF
%hex8Number as FLOAT32 in hex1.5 → 3FC00000
%asciiHex as text414243 → ABC
%in1|2|31 if the value is in the list, otherwise 03 with %in2|3 → 1
%&n, %|nBitwise AND, OR12 with %&8 → 8
%shrN, %shlNShift bits256 with %shr8 → 1
%rOLD,NEWReplace textON with %rON,1 → 1
%cS,LL characters from position SHello with %c2,3 → ell
%preTEXT, %sufTEXTPrepend or append text
%rawLeave the hex value unchanged
%h, %m, %sSeconds as time, e.g. {%h:%02m}3725 → 1:02

INFO

Calculations use about seven significant digits. Large meter readings (more than seven digits) therefore lose their last digits; better convert them into a larger unit, e.g. Wh into kWh.

A condition before the format compares the formatted value: >0, <100, =5, >=5, <=5, <>5 (not equal) or a text that must match exactly. A text does not meet any numerical comparison; a number such as 0.000000 or 00 counts as 0.

Variables ​

A variable that you create under Variables is written in commands as [ID], with the ID of the variable. The controller inserts its value:

WhereWhen
Actions and commands in the Internal ServiceEvery time they run
Values of the connection (structure.config), e.g. "PASSWORD": {"value": "[WALLBOX_PIN]"}When the engine loads its configuration, i.e. at startup; a changed variable only takes effect after the next start of the engine
Sequences and HTTP bodiesEvery time something is sent, if RESOLVE_SYSVARS is set to YES; otherwise [ remains an ordinary character
Response patterns and formatsEvery time a response is evaluated

If there is no variable with this ID, [ID] remains as text. An action sets a variable with <SYS><SETVAR=ID,value></SYS>; create the variable under Variables anyway, because a variable that only comes into existence this way does not appear there.

Example: an automation sets the variable WALLBOX_MAX to the permitted charging current, and the wallbox query sends it along:

json
"RESOLVE_SYSVARS": {"value": "YES", "editable": false}
…
"LIMIT": {"sequence": "set?ama=[WALLBOX_MAX]", "parameterCount": 0}

[ID] is something different from the placeholders %NAME%: the controller inserts placeholders once when the device is created, variables at runtime.

Reachability ​

The reachability of a CommandServer device comes from CONNECTEVENT and DISCONNECTEVENT. For it to arrive, the profile needs the feedback value:

json
"reachable": {"method": "get", "property": "REACHABLE", "datatype": "bool", "defaultValue": false}

Without CONNECTEVENT/DISCONNECTEVENT, the device always counts as reachable. When the messages arrive depends on the protocol:

ProtocolCONNECTEVENTDISCONNECTEVENT
TCPConnection establishedConnection ended: device closes, sending fails, heartbeat without response, time expired
UDPAt every start, even if the device is offHeartbeat without response
HTTP(S)Every successful requestRequest failed

It follows that:

  • TCP: Set SERVERTIMEOUT_IN_MS to NONE. With a finite time, the connection ends after every idle period, and the device briefly appears as not reachable each time. With STARTUPCMD, HEARTBEATCMD and RECONNECT YES, the controller re-establishes the connection itself after an interruption.
  • UDP: Only a heartbeat says something about the device: HEARTBEATCMD with a command the device responds to, and HEARTBEATTIMEOUT. Leave out CONNECTEVENT and derive REACHABLE=1 from a response instead, as in the KEBA example.
  • HTTP: A regular polling also checks the reachability. In the Profile Editor, an HTTP profile without a polling receives the polling “Reachable Check” on / every 30 seconds; for this, the device's start page must respond with 200 and content. Do not set RETRYINTERVAL for HTTP.
  • RETRYINTERVAL: If the device is not available when the connection is established, the controller tries again and again, without DISCONNECTEVENT. The device keeps its last state in the meantime.

Serial devices ​

Devices with an RS232 or RS485 interface are connected directly to the controller via a USB-to-serial adapter:

json
"SERVERPROTOCOL": {"value": "SERIAL", "editable": false},
"SERVERIP": {"value": "LOCAL", "editable": false},
"SERVERPORT": {"value": "/dev/ttyUSB0;9600;8;N;1;NONE", "editable": {"type": "string", "default": "/dev/ttyUSB0;9600;8;N;1;NONE"}},
"SERVERMODE": {"value": "ASCII", "editable": false},
"SERVERTIMEOUT_IN_MS": {"value": "NONE", "editable": false}

SERVERPORT has six parts, separated by semicolons: device, baud rate, data bits (5–8), parity (N none, E even, O odd), stop bits (1 or 2) and flow control (NONE, RTSCTS, DTRDSR or XONXOFF). Instead of the device, AUTO takes the first interface found.

Which interfaces the controller sees is shown by <SYS><GETSERIALPORTLIST></SYS> in the Internal Service, e.g. <SYS>GETSERIALPORTLIST=/dev/ttyUSB0|/dev/ttyUSB1|</SYS>. Listed are USB adapters (/dev/ttyUSB…) and, if present, the built-in interfaces /dev/ttyS0 and /dev/ttyS1; AUTO is the first entry. An adapter that registers as /dev/ttyACM… is missing from the list and from AUTO, but can be entered with its name.

  • With several adapters: plug them in one at a time and query the list each time; then you know which name belongs to which device.
  • Make SERVERPORT editable as in the example; then the device and settings are entered in Create device.

Limits ​

  • Sequences, patterns and conditions: 255 characters at most; longer ones are cut off. An HTTP body may be longer (up to about 1000 characters).
  • These characters do not work in sequences, patterns and values: semicolon (write it as \x3B), tab, line break, /* and */. A pattern must not start with //. A value containing one of these characters, such as a password with a semicolon, is rejected in Create device; the message names the setting. Exception: HEARTBEATCMD and a serial SERVERPORT, which need their semicolon.
  • Class name: 28 characters at most, so that the sequential number still fits; a longer one is rejected.
  • At most 100 pending commands per connection; anything beyond that is discarded.
  • A value that contains | or = is assigned incorrectly. Replace such characters with %r.