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
"structure": {
"config": { "SERVERPROTOCOL": {"value": "TCP", "editable": false}, … },
"commands": { "READ": {"sequence": "…", "parameterCount": 0}, … },
"mappings": { "POWER": {"match": "…", "condition": "…", "onChange": true}, … }
}- Connection (
config): Where and how the controller sends. - Commands (
commands): What is sent. An action of the profile, a polling or an automation calls a command, if required with a value. - 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. - The profile's feedback values (
mappingsat the top level) fetch these values via theirproperty:{"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:
"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 withdefault.typeishost,string,password,bool(becomesYES/NO) orinteger/float(number field). In addition,helpis possible as a help text. Note: apasswordis also shown in plain text in the dialog.comment: true: The setting has no effect but can still be used as a placeholder.
| Setting | Meaning | Default |
|---|---|---|
SERVERPROTOCOL | TCP, UDP, HTTP, HTTPS or SERIAL. A misspelt value counts as UDP | UDP |
SERVERIP | IP address of the device. For TCP and UDP only an IPv4 address, no host name. For HTTP(S) a host name is also possible | Required |
SERVERPORT | Port. 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;NONE | Required |
SERVERMODE | ASCII (text) or HEX (bytes as hex digits). Always ASCII for HTTP | Required |
SERVERTIMEOUT_IN_MS | How long the connection stays open after the last command and waits for responses, in ms. NONE keeps it open permanently. Takes precedence over SERVERTIMEOUT | Required¹ |
SERVERTIMEOUT | The same in seconds | Required¹ |
MATCHING | FULL or NORMAL, see Response patterns. FULL for almost all devices | NORMAL |
SPACING | Pause after each sent command in ms | 0 |
CONNECTEVENT, DISCONNECTEVENT | Message when the connection is established or ends, usually REACHABLE=1 and REACHABLE=0, see Reachability | – |
STARTUPCMD | Commands sent at startup, separated by commas | – |
SHUTDOWNCMD | Commands sent every time the connection ends | – |
HEARTBEATCMD | command;seconds, e.g. REPORT2;30: sent if no other command has run for that long | – |
HEARTBEATTIMEOUT | Time in ms to wait for any response after the heartbeat; without a response, the connection is ended | 1000 |
RECONNECT | YES: send STARTUPCMD again after the connection has ended. Only works together with HEARTBEATCMD and STARTUPCMD | NO |
RETRYINTERVAL | Seconds until the next attempt if connecting or sending fails. Repeats indefinitely, see Reachability | 0 |
CMDPREFIX, CMDSUFFIX | Text before or after every command, e.g. /api/ for HTTP or \x0D for text protocols | – |
SEQUENCEPREFIX, SEQUENCESUFFIX | Text before or after the whole sent packet | – |
MAPPREFIX, MAPSUFFIX | Text before or after every response pattern | – |
STARTDELIMITER, ENDDELIMITER | Start or end of a message, see Messages across several packets | – |
SERVERTYPE | Checksum, 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, PASSWORD | HTTP(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 | – |
LOCALPORT | UDP: local port to which the device responds; RANDOM for a random one. If the setting is missing, SERVERPORT applies | SERVERPORT |
NEGOTIATE | TELNET: answer the Telnet negotiation and remove it from the responses | – |
RESOLVE_SYSVARS | YES: insert variables in square brackets ([ID]) in sequences every time something is sent, see Variables | NO |
URL_SAFE_CHARS | HTTP(S): characters that should not be encoded in the address, e.g. () | – |
DEBUG | YES: output every sequence in the Internal Service | NO |
ACTIVE | NO switches the connection off | YES |
¹ 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:
"config": {
"DEVICEID": {"value": "1", "editable": {"type": "integer", "default": "1"}, "comment": true}
},
"commands": {
"STATUS": {"sequence": "/api/device/%DEVICEID%/status", "parameterCount": 0}
}Commands
"commands": {
"STATUS": {"sequence": "status?filter=frc,car,ama", "parameterCount": 0},
"SETAMA": {"sequence": "set?ama=\\#", "parameterCount": 1},
"READ_INSTANT": {"sequence": "000000000006010400000020", "parameterCount": 0, "tag": "instant"}
}| Field | Meaning |
|---|---|
| Key | Name of the command in upper case. SETNAME and GETNAME are the commands for actions with "method": "set" or "get" and "property": "NAME" |
sequence | What is sent, see below |
parameterCount | Number of \# in sequence and body |
parameter | Condition and format for the value sent along, see below |
timeout | Replaces SERVERTIMEOUT_IN_MS for this command (ms). 0: send and close the connection immediately. NONE: keep the connection open after this command |
spacing | Replaces SPACING for this command (ms) |
tag | Assigns responses to this command to specific response patterns, see Tags |
httpMethod, contentType, body | HTTP(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%20would 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}"turns5into the textMV05(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.
>=0or<>99. If it is not met, the controller sends nothing.
HTTP
- The address is
http://orhttps://, thenSERVERIP, then the sequence; the port comes fromSERVERPORT. httpMethod:GET(default),POST,PUT,PATCH,DELETE. For everything exceptGET,bodyandcontentTypecan be specified; withoutcontentType, the controller detects JSON ({,[), XML (<) or otherwise a form.\#in the body takes values after the places in the sequence have been filled.
"SETMODE": {"sequence": "/api/mode", "httpMethod": "POST",
"contentType": "application/json", "body": "{\"mode\": \\#}", "parameterCount": 1}- A
GETonly counts as successful if the device responds with200and content. Other methods count as successful with any2xxresponse. - 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;timeouton the command has no effect for HTTP.
Response patterns
"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"}
}| Field | Meaning |
|---|---|
| Key | Name of the value, e.g. CAR. Feedback values of the profile fetch the value with "property": "CAR". |
match | Pattern, see below. Case does not matter |
condition | Condition 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 |
onChange | true: only run localAction if the value has changed. The value itself is always passed on |
localAction | Optional: command that runs on a match; \# stands for the value. See Action and commands |
tag | See Tags |
Patterns
A pattern is text (for HEX: hex digits) in which placeholders start with \. In JSON, \ is written twice: \\*.
| Placeholder | Meaning | Example |
|---|---|---|
\* | Captures everything up to the text that follows | VOL=\*| on VOL=42|MUTE=0 gives 42 |
\^ | Captures exactly one character; \^^^^ captures four | PWR\^ on PWR1 gives 1 |
\? | Skips exactly one character; \?? two | 01\?\?\^^ on 0102030405 gives 03 |
\% | Skips everything up to the text that follows | B=\% \*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 findpowerif 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:
\?\?\^^\^^on0001020304gives0102. - 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
MATCHINGFULL: 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:
"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:
"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 fromstructure.commands, SYSfor system commands, for example:
| Command | Effect |
|---|---|
SETVAR=ID,value | Sets the variable ID, see Variables |
GETVAR=ID | Returns the value of the variable |
WAKEONLAN=00:11:22:33:44:55 | Sends a Wake-on-LAN packet to this MAC address |
GETSERIALPORTLIST | Lists 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:
"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}.
| Filter | Effect | Example |
|---|---|---|
%+n, %-n, %*n, %/n | Arithmetic. The result has as many decimal places as n | 4523.7 with %/1000 → 5, with %/1000.00 → 4.52 |
%int16, %int32 | Hex as a signed number | FFFF → -1 |
%float | Hex as FLOAT32; usually preceded by %swap32 | %swap32,%float: 43668000 → 230.500000 |
%swap16, %swap32 | Swap the byte order | A1B2C3D4 → d4c3b2a1 (swap32) |
%dec | Hex as an unsigned number | 00FF → 255 |
%hex, %hexN | Integer as hex, with N digits | 255 with %hex4 → 00FF |
%hex8 | Number as FLOAT32 in hex | 1.5 → 3FC00000 |
%ascii | Hex as text | 414243 → ABC |
%in1|2|3 | 1 if the value is in the list, otherwise 0 | 3 with %in2|3 → 1 |
%&n, %|n | Bitwise AND, OR | 12 with %&8 → 8 |
%shrN, %shlN | Shift bits | 256 with %shr8 → 1 |
%rOLD,NEW | Replace text | ON with %rON,1 → 1 |
%cS,L | L characters from position S | Hello with %c2,3 → ell |
%preTEXT, %sufTEXT | Prepend or append text | |
%raw | Leave the hex value unchanged | |
%h, %m, %s | Seconds 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:
| Where | When |
|---|---|
| Actions and commands in the Internal Service | Every 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 bodies | Every time something is sent, if RESOLVE_SYSVARS is set to YES; otherwise [ remains an ordinary character |
| Response patterns and formats | Every 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:
"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:
"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:
| Protocol | CONNECTEVENT | DISCONNECTEVENT |
|---|---|---|
| TCP | Connection established | Connection ended: device closes, sending fails, heartbeat without response, time expired |
| UDP | At every start, even if the device is off | Heartbeat without response |
| HTTP(S) | Every successful request | Request failed |
It follows that:
- TCP: Set
SERVERTIMEOUT_IN_MStoNONE. With a finite time, the connection ends after every idle period, and the device briefly appears as not reachable each time. WithSTARTUPCMD,HEARTBEATCMDandRECONNECT YES, the controller re-establishes the connection itself after an interruption. - UDP: Only a heartbeat says something about the device:
HEARTBEATCMDwith a command the device responds to, andHEARTBEATTIMEOUT. Leave outCONNECTEVENTand deriveREACHABLE=1from 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 with200and content. Do not setRETRYINTERVALfor HTTP. RETRYINTERVAL: If the device is not available when the connection is established, the controller tries again and again, withoutDISCONNECTEVENT. 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:
"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
SERVERPORTeditable 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:HEARTBEATCMDand a serialSERVERPORT, 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.