Interface control documentation (Remote interface) - general information


  1. Introduction
  2. Configuration
  3. Testing tool

Introduction and important points to note

Overview

CLS2Sim connects BEH motion and I/O hardware to known aircraft simulation software like X-Plane, MFSX, or Prepare3D. In case the customer wants to use other hardware or simulation programs, CLS2Sim provides a network interface for manipulating hardware and simulation, named "Remote Control".

Function overview

The interface can be accessed over TCP or UDP and the application protocol remains the same for both connection types. The protocol is implemented on a request-response base similar to HTTP. The client (the 3rd-party application) sends a packet to the server (CLS2Sim), which sends a response back. If UDP is used for transport, the recipient of the response can be a third machine in the network.

Endianness

All values are transmitted over network as Little Endian. Therefore if the value 0xCC is used for an Int32, the byte stream transmitted over the network must read [CC, 00, 00, 00].

Single- and dualseat setups

When using cls system for pilot and copilot, each axis (elevator, aileron , ...) will be synchronized between pilot and copilot. writing different settings to synchronized axes would result in errors. Therefore there is only one setting address per setting. The only exception are reading forces and yoke buttons. We wanted to give the user the freedom to get forces and button presses separately for pilot and copilot seats. The position is always synchronized, therefore it wouldn't make sense to provide two values to read.

General tips for communication

Settings are meant to be write only. it is also good practice to write all settings after connecting, to overwrite different values. Position, force and button bitmask are an exception to this, as they are meant to be read. When wanting to dynamically adjust forces on the devices, the force scale factor should be used. writing the force profile causes quite an amount of traffic and should not be written during flight.

Protocol version 1 and 2

Protocol version 1 is deprecated and is only included to support old third-party software! Please use
Protocol version 2! The exception to that rule is the external control command, which can be used indefinitely.


Go to top

Configuration

Settings window



Enabled

This setting is the main switch for the Remote Control feature. Check it to activate Remote Control.

External Control

This checkbox activates the "CLS external control" part of the protocol. All other features of Remote Control and CLS2Sim will be deactivated, including platform control and simulator connection.

Send UDP Response back to Sender

This switch defines the active communication mode of the UDP interface. If this switch is on, response packets will be sent back to the same socket, which sent the request packets. If this switch is on, the values of "UDP Response IP and Port" will be ignored.

TCP Server Port

This setting defines the port number on which the TCP socket of the Remote Control interface will be listening for incoming connections.

UDP Server Port

This setting defines the port number on which the UDP socket of the Remote Control interface will be listening for incoming datagrams.

UDP Response IP / Port

These two settings allow the definition of a target IP address and port, to which UDP response packets will be sent to, allowing for different sockets or even machines for sending and receiving data.
If "Send UDP Response back to Sender" is set to true, the values of "UDP response IP and Port" will be ignored.


Go to top

Testing tool

We've written a tool to help testing the UDP side of the protocol implementation. It can generate all packets for this protocol. It's written in python for version 2.7, the name is prototester.py. Calling this script without arguments will display its usage.


We will use the protocol v2 command SimulationControl.GetStatus as an example:
python.exe prototester.py 127.0.0.1 15090 II =HBBBH 0xD3 0x00
Argument Description
python.exe The python executable.
prototester.py Our script
127.0.0.1 The host to send data to
15090 The port to send data to
II The format string used to format the outgoing data. Its 0xD3 and 0x00 both interpreted Int32, so the outgoing data will be 0xD3 00 00 00 ' 00 00 00 00.
=HBBBH Format string used for the received data, in this example 5, 0, 1, 0, 0.
The = is a prefix, that will set alignment to 1 byte, so uint8/uint16 don't eat more data than than their size.
See https://docs.python.org/2/library/struct.html#struct-alignment
The format string will cut the received bytes into the following output:
1x UInt16 (H)
3x Byte (BBB)
1x UInt16 (H)
0xD3 0x00 The data to be used with the send format string. It can be written as hex with "0x" prefix, or normal as decimal number, if a float is required it can be written with a dot (eg. 1.0) and it will be parsed as such.

The resulting output will be the following:
sending 8 bytes...
d3 00 00 00 00 00 00 00
received 7 bytes.
05 00 00 01 00 00 00
data:
(5, 0, 4, 0, 0)
    
Data Description
d3 00 00 00 00 00 00 00 The sent raw data.
05 00 00 01 00 00 00 The received raw data.
(5, 0, 1, 0, 0) The received data, formatted by the receive format string.

The received raw data in this example can be parsed as followed:

Data Description
05 00 after this length field, 5 more bytes will follow
00 0x00 Response status OK (Success)
01 Selected simulation: 0x01 Microsoft Flight Simulator X (Can be 0 to 4 depending on which simulation you have configured in connection settings)
00 Connection state: 0x00 Disconnected
00 00 Simulation update rate: 0 Updates per second. (Can be anything, undefined if not connected)

For format string documentation, see https://docs.python.org/2/library/struct.html#format-characters

Source code of prototester.py

#!/usr/bin/env python2

usagemsg = """
usage: prototester.py [-r] host port sendformat recvformat data0,... dataN

-r          - if this flag is set, send and receive will loop forever.
host        - the host to send the packet to.
port        - the port to send the packet to.
sendformat  - a string defining the format of the data to send.
              each char defines the type of 1 senddata argument.
              see python documentation for struct.pack
              for usable chars and their types.
recvformat  - same as sendformat, but parses the received data.
data0-N     - the data to be parsed and sent to the host

example: prototester.py 127.0.0.1 15090 iiffff iiBBhBBhBBh 0xCC 0x11 1.0 0 0 0

explanation:
send to 127.0.0.1:15090  2x a 4byte signed Integer and 4x a 4byte float.
0xCC and 0x11 are the two integers. decimal numbers can also be used as input.
the 1.0 and the 3 zeroes are the floats. if no dot is used (eg 1.0) the input
will be parsed as integer.

the "BBhBBhBBh" part is used by struct.unpack to parse the incoming raw bytes.
in this case 2 unsigned chars are followed by a signed short.
this is repeated 3 times
"""

import socket
import struct
import sys
import os

def toNiceHex(binaryString):
    hexstr = str.encode(binaryString, "hex")
    niceHex = ' '.join([hexstr[i:i+2] for i in range(0, len(hexstr), 2)])
    return niceHex

def parseData(data):
    nr = data
    isHex = False
    val = None
    
    if data[:2] == "0x":
        isHex = True
        nr = nr[2:]
        
    if isHex:
        nr = int(nr, 16)
    
    if '.' in str(data):
        val = float(nr)
    else:
        val = int(nr)
    return val

doLoop = False

# parsing arguments
args = sys.argv[1:]
if len(args) < 5:
    print(usagemsg)
    exit()
if args[0][0:2] == "-r":
    args = sys.argv[2:]
    doLoop = True

host = args[0]
port = int(args[1])
sendformat = args[2]
recvformat = args[3]

data = args[4:]
inputs = [parseData(val) for val in data]

if len(sendformat) != len(inputs):
    print("number of sendformat chars is different from the number of data arguments")
    exit()

# preparing data for sending
bytesToSend = struct.pack(sendformat, *inputs)
niceHex = toNiceHex(bytesToSend)
# prepare socket
size = 8192
timeout = 8
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(timeout)
sock.bind(('', 0))

while True:
    os.system("cls")
    print "sending {} bytes...".format(len(bytesToSend))
    print niceHex
    #send and get response
    sock.sendto(bytesToSend, (host, port))
    response, address = sock.recvfrom(8192)
    print "received {} bytes.".format(len(response))
    print toNiceHex(response)
    # parse result
    result = struct.unpack(recvformat, response)
    print ("data: ")
    print(result)
    if not doLoop:
        break
sock.close()
    

©2024 Brunner Elektronik AG
CH-8335 Hittnau 
http://www.brunner-innovation.swiss