Embedded Systems C Bridge API: Unterschied zwischen den Versionen

Aus expecco Wiki (Version 2.x)
Zur Navigation springen Zur Suche springen
 
(41 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 207: Zeile 207:
== JSON Library ==
== JSON Library ==


Messages are interchanged as JSON encoded packages. For this, a JSON library is part of the cLibrary, to parse and generate JSON.
Messages are interchanged as JSON encoded packages. For this, a JSON library is part of the cLibrary (and thus available to bridged C code), to parse and generate JSON.


JSON objects are references (pointers) to a data structure which contains type and contents information. All allocted JSON objects will be freed after the requests target function returns. This means: you can create such object and write them to an output pin, but the memory will be freed (and the pointer become void) once the action'sd function returns.
JSON objects are references (pointers) to a data structure which contains type and contents information. All allocted JSON objects will be freed after the request's target function returns. This means: you can create such object and write them to an output pin, but the memory will be freed (and the pointer becomes void) once the action's function returns.
They are ONLY meant to transfer data to and from expecco.
They are ONLY meant to transfer data to and from expecco.

Primitive C-code can refer to incoming data either as json object (i.e. "jsonValue(<inPin>)") or if it the type of the incoming object is known via "longValue(<inPin>)", "doubleValue(<inPin>" etc.). The first gives C-code access to arbitrary objects (arrays and structs), whereas the later check for the correct type and simplify the C-code in common situations).


=== Creating JSON Objects ===
=== Creating JSON Objects ===


===== Structs and Arrays =====
===== Structs and Arrays =====
* jsonObject json_newStruct(int n)<br>creates a new JSON struct, with estimated n fields. n is a hint so that space for n values is preallocated; this value does not need to be exact; if more elements are later added, the memory will be automatically reallocated. However, for best performance (avoiding reallocs), give the number of fields which will be added.
* ''jsonObject'' '''json_newStruct'''(''int'' n)<br>creates a new JSON struct, with estimated n fields. n is a hint so that space for n values is preallocated; this value does not need to be exact; if more elements are later added, the memory will be automatically reallocated. However, for best performance (avoiding reallocs), give the number of fields which will be added.


* jsonObject json_newArray(int n)<br>allocate an array with n elements
* ''jsonObject'' '''json_newArray'''(''int'' n)<br>allocate an array with n elements


===== Byte Array =====
===== Byte Array =====
* jsonObject json_newBytesWithLength(unsigned char* bytes, int len)<br>to allocate a json bytearray;this copies the passed bytes into an internal buffer (which is freed at the end of the execute function)
* ''jsonObject'' '''json_newBytesWithLength'''(''unsigned char''* bytes, ''int'' len)<br>to allocate a json bytearray;this copies the passed bytes into an internal buffer (which is freed at the end of the execute function)


* jsonObject json_newBytesWithLengthByRef(unsigned char* bytes, int len)<br>to allocate a json bytearray;this references the passed in pointer. It must point to data which survives the execute function (i.e. it may NOT be pointing to a local variable). The memory is not freed at the end of the execute function.
* ''jsonObject'' '''json_newBytesWithLengthByRef'''(''unsigned char''* bytes, ''int'' len)<br>to allocate a json bytearray; this references the passed in pointer. It must point to data which survives the execute function (i.e. it may NOT be pointing to a local variable). The memory is not freed at the end of the execute function.


===== Strings =====
===== Strings =====
* jsonObject json_newString(char* str)<br>to allocate a json string with regular 1-byte encoding (ISO8859-1). The string must be 0-terminated.
* ''jsonObject'' '''json_newString'''(''char''* str)<br>to allocate a json string with regular 1-byte encoding (ISO8859-1). The string must be 0-terminated.


* jsonObject json_newStringWithLength(char* str, int n)<br>like above, but the string does not need to be 0-terminated (i.e. to extract a partial string and/or limit the max. size).
* ''jsonObject'' '''json_newStringWithLength'''(''char''* str, ''int'' n)<br>like above, but the string does not need to be 0-terminated (i.e. to extract a partial string and/or limit the max. size).


* jsonObject json_newStringByRef(char* str)<br>like newString, with byRef behavior as described above
* ''jsonObject'' '''json_newStringByRef'''(''char''* str)<br>like newString, with byRef behavior as described above


* jsonObject json_newStringWithLengthByRef(char* str, int n)<br>like newStringWithLength, with byRef behavior as described above
* ''jsonObject'' '''json_newStringWithLengthByRef'''(''char''* str, ''int'' n)<br>like newStringWithLength, with byRef behavior as described above


* jsonObject json_newWideString(widechar* str)<br>to allocate a json string from a given wide string (16bit chars)
* ''jsonObject'' '''json_newWideString'''(''widechar''* str)<br>to allocate a json string from a given wide string (16bit chars)


* jsonObject json_newWideStringWithLength(widechar* str, int n)<br>like above, but the string does not need to be 0-terminated (i.e. to extract a partial string and/or limit the max. size).
* ''jsonObject'' '''json_newWideStringWithLength'''(''widechar''* str, ''int'' n)<br>like above, but the string does not need to be 0-terminated (i.e. to extract a partial string and/or limit the max. size).


* jsonObject json_newWideStringByRef(widechar* str)<br>to allocate a json string from a given wide string (16bit chars)
* ''jsonObject'' '''json_newWideStringByRef'''(''widechar''* str)<br>to allocate a json string from a given wide string (16bit chars)


* jsonObject json_newWideStringWithLengthByRef(widechar* str, int n)<br>like above, but the string does not need to be 0-terminated (i.e. to extract a partial string and/or limit the max. size).
* ''jsonObject'' '''json_newWideStringWithLengthByRef'''(''widechar''* str, ''int'' n)<br>like above, but the string does not need to be 0-terminated (i.e. to extract a partial string and/or limit the max. size).

widechar = unsigned short; UTF16 kodiert.


===== Scalars =====
===== Scalars =====
* jsonObject json_newBoolean(int boolValue)<br>this is actually simply returning one of the two singleton objects: json_TRUE or json_FALSE.
* ''jsonObject'' '''json_newBoolean'''(''int'' boolValue)<br>this is actually simply returning one of the two singleton objects: '''json_TRUE''' or '''json_FALSE'''.


* jsonObject json_newTrue()<br>this is actually simply returning the singleton object: json_TRUE.
* ''jsonObject'' '''json_newTrue'''()<br>this is actually simply returning the singleton object: '''json_TRUE'''.


* jsonObject json_newFalse()<br>this is actually simply returning the singleton object: json_TRUE.
* ''jsonObject'' '''json_newFalse'''()<br>this is actually simply returning the singleton object: '''json_TRUE'''.


* jsonObject json_newNull()<br>this is actually simply returning the singleton object: json_NULL.
* ''jsonObject'' '''json_newNull'''()<br>this is actually simply returning the singleton object: '''json_NULL'''.


* jsonObject json_newLong(long lVal)<br>a long integer (signed).
* ''jsonObject'' '''json_newLong'''(''long'' lVal)<br>a long integer (signed).


* jsonObject json_newULong(unsigned long lVal)<br>an unsigend long integer.
* ''jsonObject'' '''json_newULong'''(''unsigned long'' lVal)<br>an unsigend long integer.


* jsonObject json_newLongLong(long long lVal)<br>a long long integer (signed).
* ''jsonObject'' '''json_newLongLong'''(''long long'' lVal)<br>a long long integer (signed).


* jsonObject json_newULongLong(unsigned long long lVal)<br>an unsigend long long integer.
* ''jsonObject'' '''json_newULongLong'''(''unsigned long long'' lVal)<br>an unsigend long long integer.


* jsonObject json_newDouble(double dVal)<br>a double precision float number.
* ''jsonObject'' '''json_newDouble'''(''double'' dVal)<br>a double precision float number.


* jsonObject json_newPointer(void* anything)<br>an opaque pointer passed to expecco
* ''jsonObject'' '''json_newPointer'''(''void''* anything)<br>an opaque pointer passed to expecco


=== Adding Fields and Array Slots ===
=== Adding Fields and Array Slots ===


* ''int'' '''json_addStructField'''(''jsonObject'' jStruct, ''char''* fieldName, ''jsonObject'' el)<br>adds a field named ''name'' to the struct with given value. The struct was previously created with json_newStruct(n). The struct will be automatically resized, if more than n fields are added (although it is better to provide a fitting n, to avoid those reallocations later). Returns the 0-based index of added the field (i.e. = current number of fields minus 1).
* json_setArrayElement(jsonObject jArr, int idx, jsonObject el)<br>set the idx'th array element in a json array, which was previously created with json_newArray(n); 0 <= idx < n.

* ''void'' '''json_setArrayElement'''(''jsonObject'' jArr, ''int'' idx, ''jsonObject'' el)<br>set the idx'th array element in a json array, which was previously created with json_newArray(n); 0 <= idx < n.

* ''int'' '''json_addArrayElement'''(''jsonObject'' jArr, ''jsonObject'' el)<br>adds an array element to a json array, which was previously created with json_newArray(n). The array is resized if required (but better to give a matching size when crating). Returns the 0-based idndex of the added element (i.e. = current array size minus 1).

=== Queries ===
*''bool'' '''json_isArray'''(''jsonObject'' obj)<br>returns a C-true (1) if the argument is a json-array object, C-false (0) otherwise.<br>Useful to check for or case-switch on the the type of a jsonObject fetched from an input pin via "jsonValue(<inPin>)".

*''bool'' '''json_isTrue'''(''jsonObject'' obj)
:''bool'' '''json_isFalse'''(''jsonObject'' obj)
:''bool'' '''json_isBoolean'''(''jsonObject'' obj)
:''bool'' '''json_isNull'''(''jsonObject'' obj)
:''bool'' '''json_isLong'''(''jsonObject'' obj)
:''bool'' '''json_isULong'''(''jsonObject'' obj)
:''bool'' '''json_isLongLong'''(''jsonObject'' obj)
:''bool'' '''json_isULongLong'''(''jsonObject'' obj)
:''bool'' '''json_isInteger'''(''jsonObject'' obj)
:''bool'' '''json_isDouble'''(''jsonObject'' obj)
:''bool'' '''json_isPointer'''(''jsonObject'' obj)
:''bool'' '''json_isBytes'''(''jsonObject'' obj)
:''bool'' '''json_isFloats'''(''jsonObject'' obj)
:''bool'' '''json_isDoubles'''(''jsonObject'' obj)
:''bool'' '''json_isString'''(''jsonObject'' obj)
:''bool'' '''json_isWideString'''(''jsonObject'' obj)
:''bool'' '''json_isStruct'''(''jsonObject'' obj)<br>Similar to above. Use when an input pin is of type ANY and you fetch the value via "jsonValue(<pin>)" to case-switch into different branches to fetch the value.

*''int'' '''json_arraySize'''(''jsonObject'' obj)<br>returns the number of elements in a json array

*''int'' '''json_structSize'''(''jsonObject'' obj)<br>returns the number of struct fields in a json struct object


*''char*'' '''json_getIndexedStructFieldNameC'''(''jsonObject'' obj, ''int'' index)<br>returns the name of the struct element field at index (0-based). This returns a C-char* which becomes void after the action's function (i.e. the underlying json field name object will be freed),

=== Value Extraction ===
*''int'' '''json_booleanValue'''(''jsonObject'' obj)<br>returns a C-boolean (i.e. 0 or 1) from a jsonObject which must be a jsonBoolean (i.e. "json_isBoolean()" should have returned true).<br>Fails if the object is not a jsonBoolean.
*''long'' '''json_longValue'''(''jsonObject'' obj)
*''unsigned long'' '''json_uLongValue'''(''jsonObject'' obj)
*''double'' '''json_doubleValue'''(''jsonObject'' obj)
*''int'' '''json_booleanValue'''(''jsonObject'' obj)
*''long long'' '''json_longLongValue'''(''jsonObject'' obj)
*''unsigned long long'' '''json_uLongLongValue'''(''jsonObject'' obj)
*''char*'' '''json_stringValue'''(''jsonObject'' obj)<br>the returned char* will be invalid after the action's execution
*''unsigned char*'' '''json_bytesValue'''(''jsonObject'' obj)
:''unsigned int'''''json_bytesLength'''(''jsonObject'' obj)<br>the retrieved byte* will be invalid after the action's execution

*''jsonObject'' '''json_getArrayElement'''(''jsonObject'' obj, ''int'' index)<br>returns the value of the array element at index (0-based).

*''jsonObject'' '''json_getIndexedStructFieldValue'''(''jsonObject'' obj, ''int'' index)<br>returns the value of the struct element field at index (0-based).


=== Value Setting ===
* json_addStructField(jsonObject jStruct, char* fieldName, jsonObject el)<br>adds a field named ''name'' to the struct with given value. The struct was previously created with json_newStruct(n). The struct will be automatically resized, if more than n fields are added (although it is better to provide a fitting n, to avoid those reallocations later).
*''void'' '''json_setArrayElement'''(''jsonObject'' array, ''unsigned'' index, ''jsonObject'' newValue)<br>the index is 0-based.


=== JSON Examples ===
=== JSON Examples ===
Zeile 944: Zeile 997:


--- to be added here ---
--- to be added here ---

[[Category: Advanced/en]]<br>

Aktuelle Version vom 17. Juli 2022, 16:08 Uhr

Inhaltsverzeichnis

Embedded Systems C Bridge API[Bearbeiten]

We provide the CBridge code both as a DLL (shared object) and as a static link library upon request. If linked against the application, or dynamically loaded into the application, expecco can interact with it - even if the target is an embedded system.

In addition, a fully linked executable called "cBridge" ("cBridge.exe" for Windows) is provided to be used as a default bridge for elementary C actions. This executable is found in the expecco installation folder.

The protocol as described here is also used for Smalltalk bridges and the newest version of the Qt bridge.

Architecture[Bearbeiten]

The CBridge opens a socket and listens for an incoming connection. Only one such connection may be active at any time (per bridge). If multiple bridges are to be used on the same host machine, each must use a different port number, which will typically be passed via a command line argument or via a configuration setting inside the target.

Expecco will connect to a bridge, given hostname and port. For the implicit default bridge, expecco uses "localhost:8855". Once connected, expecco will send requests and receive responses, which are exchanged in human readable JSON format, which is similar to the ChromeV8 debug or VSCode debug interface message format.

The set of supported messages is defined by the target system - therefore customers have full control over which messages are allowed to be interchanged, and how they are handled. This is done before the bridge is activated by so called "message registration" calls.

The bridge code as provided by exept includes handlers for various common tasks, which can be registered as option. Except also provides a ready-to-run cBridge executable, which is configured to register handlers for remote code execution (i.e. for Bridged C Actions).

As an alternative to using the exept library, customers can also implement the wire protocol, which is described below.

CBridge Message Scheme[Bearbeiten]

The protocol consists of messages, which have a type field of "request", "event" or "response". All requests will be answered by a response, which contains a success status field and optional additional data. Events are not answered. In theory, the protocol allows for multiple requests to be sent and answered asynchronously, and even out of order, as every request includes a sequence number, which is reflected in the corresponding response. However, expecco does not (and will not) make use of this, so the cBridge code in the client can safely assume that no new request will be sent until the current request is answered. This makes the target implementation easier and especially the memory allocation patterns deterministic.

CBridge Message Interchange Sequence Diagram[Bearbeiten]

In the following swim lane diagram,

  • function calls are marked as "=====>"
  • socket messages as "------>"
  • parallel execution as "||"
  • thread blocking (suspend / wait) as "***"
  • normal thread execution as "..."
expecco side         target                                cBridge library          cBridge thread
--------------------+-------------------------------------+------------------------+----------------------------
     ...            |    ...                              |                        | 
                    |initialization:                      |                        |
                    |  cBrige_registerHandler(...) =======> register handler       |
                    |                                     | immediate return       |
                    |                              <=======                        |
                    |     ...                             |                        | 
                    |  cBrige_registerHandler(...) =======> register handler       |
                    |                                     | immediate return       |
                    |                              <=======                        |
                    |     ...                             |                        | 
                    |  cBrige_startAsync(...)      =======> starts cBridge thread  | thread started    
                    |                                     | immediate return       | opens listen socket
                    |     ...                      <=======                        | await connection       
                    |  target runs as usual               |                        | ***
                    |     ...                             |                        | await incoming message
                    |                                     |                        | ***
     ...            |                                     |                        |
   send request -------------------------------------------------------------------> receive request (JSON)
   await response   |                                     |                        | decode request
     ***            |                                     |                        | prepare response object
                    |                                     |                        | call handler(req, resp)
                    |     ... || handler in target        <=========================
                    |     ... || fills response           |                        |
                    |     ... ||                          =========================> send response
                    |                                     |                        | encode response
   send request <------------------------------------------------------------------- send response (JSON)
     ...            |     ...                             |                        | await incoming message
                    |                                     |                        | ***

Different Threading Models[Bearbeiten]

The bridge library was designed to be usable in near real time environments and prevents blocking on I/O operations. Depending on constraints of the target system, it can be used in various different configurations:

Threaded with separate Socket handling code[Bearbeiten]

This is the easiest mode to use. The socket handler is started as a separate thread, which blocks in the listen(), read() and possibly in write() calls to the socket.

The target's code must be changed to perform a few calls into the cLibrary (to start the asynchronous bridge thread and for handler registration). This is usually done at the very beginning of the target's operation (i.e. in its main() function) or whenever a debug option is enabled in it (eg. via a menu function). Typically targets will either take an additional command line argument or a configuration file setting to enable the cBridge.

This mode should be used if the target is a Windows program or if the target's operating system supports Posix threads.

Non-Threaded with Socket Polling[Bearbeiten]

In this mode, the main application should poll (or select on) the socket itself, and call into the cLibrary when either a connection is established, or an incoming message is received.

In addition to the handler registration calls and handler code implementation, the target needs to be changed to call the library's incomingMessage handler and to provide a function for outgoing messages to be sent back to expecco.

This mode can be used when no thread support is provided by the operating system, or if there already exists an event handling loop which selects on input streams (eg. Qt, X11 or OSX main event loops), and sockets are to be used for bridge communication. This mode can also be used in systems without thread support, which have a cyclic execution scheme, such as many programmable control systems.

Non Threaded with Packet Delivery (non-Socket Communication)[Bearbeiten]

In this mode, the main application should use any mechanism available to exchange messages via any external port (USB, serial, socket) or via shared memory, and call into the cLibrary when an incoming message arrived. It must also register a packet delivery function, for the cLibrary to send back responses.

This mode must be used, if no socket communication is possible. An adaptor will be needed on the expecco side to replace the socket communication code. This needs to be implemented upon request and as per specification and is not provided out of the standard deployed box.

Different Memory Allocation Models[Bearbeiten]

Message interchange between expecco and the cBridge is encoded as JSON objects. Because these JSON objects vary in size and structure, they must use dynamically allocated memory. Because many embedded systems will not allow or support the use of "malloc/free", the library can be configured to:

  • either use malloc / free dynamically (i.e. for every object) (dynamic malloc scheme)
  • or to use a single malloc'd area, which is allocated once at startup (single malloc scheme)
  • or to not use malloc/free at all, but instead use a static memory area for temporary data (static data scheme)

Memory needs to be dynamically allocated whenever an incoming message is parsed from JSON into a jsonObject, when additional fields are added to a jsonObject (i.e. when a requestHandler constructs a response object), or when a jsonObject response is finally encoded into JSON and sent back to expecco.

All memory which is allocated for the request and response objects will be released when the message handler's response object has been sent to expecco. Pointers into it and field references are no longer valid, as the memory will be reused for the next request/response.

Dynamic Malloc Scheme[Bearbeiten]

This is the standard behavior when the target runs on Windows or Unix systems.

Allocations will be done by calling "malloc" multiple times, once for each individual jsonObject field and possibly for field values. The objects will be freed after the transaction.

Single Malloc Scheme[Bearbeiten]

This should be used if the target system does provide malloc, but dynamic malloc is not allowed or not wanted due to other constraints (thread safety, predictability, real time constraints etc.).

A single bigger memory area is allocated using "malloc" at startup time. All jsonObject fields will be allocated inside this arena, which will never be freed. The size of the arena defines the maximum JSON message size which can be interchanged, and an operation may fail due to that constraint.

Static Data Scheme[Bearbeiten]

This mode should be used, if no malloc is available or allowed at all inside the target system.

Similar to the previous scheme, but static memory (a data buffer provided by the target) is used, and malloc/free are never called.

CBridge Library[Bearbeiten]

Initialization & Configuration[Bearbeiten]

The bridge code is configured and initialized typically inside the target's main() function. Usually dependent on either a command line argument, or a setting in a configuration file. In targets with a GUI, it may also be called due to the user selecting a menu function or hot key.

cBridge_config[Bearbeiten]
cBridge_config(cBridgeConfigParameter what, void* valuePtr);

to configure bridge parameters. This function can be called multiple times (with different what parameters. Some of those config calls must be done before cBridge_startXXXServer() is called.

Currently supported configuration parameters are:

  • BRIDGE_PAYLOAD_LIMIT
    valuePtr must point to an integer (i.e. it must be an int*), which holds the max. payload length. This will be sent to a client in the handshake. If the client does not honor this limit, incoming packets will be ignored or responded with an error. Must be set before the cBridge server is started.
  • BRIDGE_LOGGING
    valuePtr must point to an integer (an int*), which holds a boolean (0 or 1) to disable/enable logging. Can be called at any time.
  • BRIDGE_PORT
    valuePtr must point to an integer (an int*), which holds the port. The default port is (currently) 8855. If a port different from the default is to be used, this must be set before the cBridge server is started.
  • SYSTEM_NAME
    valuePtr points to the name of the system (a char*). This is only used to display friendly connection names to humans inside expecco - it has no semantic meaning. This will be sent to expecco when the next connection is established and is remembered in the expecco side in the bridge connection object. It may be used by the test suite in combination with SYSTEM_VERION below, to adapt the test cases dynamically to specific hard- or software configurations.
  • SYSTEM_VERSION
    valuePtr (a char*) points to the version name. Same use as SYSTEM_NAME above.
cBridge_registerHandler[Bearbeiten]

Before the cBridge server is started, callback handlers must be registered. The initialization code should call:

cBridge_registerHandler(char* command, handlerFunction handler);

to define a handler for a particular message.

The handlerFunction argument must be a function defined as:

requestStatus handler(jsonObject request, jsonObject response)

This handler will be called when a corresponding request is received (i.e. with "request":"<command>" or with "event":"<command>"). There are no disjoint "namespaces" for requests and events. The handler will receive the request/event object and an empty response object, which it should fill with values.

Because the command is also found inside the passed request object, it is possible to define a single handler function for all requests and events. In such a configuration, the handler function should itself extract the "command"/"event" from the JSON object and switch internally. However, the code is usually better structured and easier to extent later, if individual handlers are registered.

Request handlers must return a requestStatus, which is an enum { REQUEST_OK, REQUEST_ERROR }. This will be returned to expecco as "success" status of the request (i.e. in the response's "success" slot). For events, no response is sent back to expecco, and the returned status is ignored.

cBridge_registerStandardActionHandlers[Bearbeiten]
cBridge_registerStandardActionHandlers();

to define standard handlers for action definition and calling (for "bridged C action" support in expecco).

Startup[Bearbeiten]

cBridge_startAsyncServer[Bearbeiten]

to start the server in a separate thread.

int cBridge_startAsyncServer(int port, bool_t keepAlive, int memoryLimitOrZero);
  • keepAlive
    if true, the server stays in it wait-for-connection loop and will serve multiple sessions (in sequence, but only one at a time). If false, the server will close the listening socket after the first session and will finish its operation. I.e. will only serve one single session.
cBridge_isAsyncServerRunning[Bearbeiten]

To check if a server is already running:

bool_t cBridge_isAsyncServerRunning();

Returns true (1) if a server is already running in the background thread.

cBridge_waitForAsyncServerToFinish[Bearbeiten]
int cBridge_waitForAsyncServerToFinish();

will block and wait until the background server thread finishes. With keepAlive being false, this will wait until one session is complete.

cBridge_server[Bearbeiten]

To enter the bridge server loop,

cBridge_server(int port, bool_t keepAlive, int memoryLimitOrZero);

This will block on a socket and is internally called by startAsyncServer(). Simple non-threaded "bridge-only" systems can call this from their main thread.

  • keepAlive as described above.

JSON Library[Bearbeiten]

Messages are interchanged as JSON encoded packages. For this, a JSON library is part of the cLibrary (and thus available to bridged C code), to parse and generate JSON.

JSON objects are references (pointers) to a data structure which contains type and contents information. All allocted JSON objects will be freed after the request's target function returns. This means: you can create such object and write them to an output pin, but the memory will be freed (and the pointer becomes void) once the action's function returns. They are ONLY meant to transfer data to and from expecco.

Primitive C-code can refer to incoming data either as json object (i.e. "jsonValue(<inPin>)") or if it the type of the incoming object is known via "longValue(<inPin>)", "doubleValue(<inPin>" etc.). The first gives C-code access to arbitrary objects (arrays and structs), whereas the later check for the correct type and simplify the C-code in common situations).

Creating JSON Objects[Bearbeiten]

Structs and Arrays[Bearbeiten]
  • jsonObject json_newStruct(int n)
    creates a new JSON struct, with estimated n fields. n is a hint so that space for n values is preallocated; this value does not need to be exact; if more elements are later added, the memory will be automatically reallocated. However, for best performance (avoiding reallocs), give the number of fields which will be added.
  • jsonObject json_newArray(int n)
    allocate an array with n elements
Byte Array[Bearbeiten]
  • jsonObject json_newBytesWithLength(unsigned char* bytes, int len)
    to allocate a json bytearray;this copies the passed bytes into an internal buffer (which is freed at the end of the execute function)
  • jsonObject json_newBytesWithLengthByRef(unsigned char* bytes, int len)
    to allocate a json bytearray; this references the passed in pointer. It must point to data which survives the execute function (i.e. it may NOT be pointing to a local variable). The memory is not freed at the end of the execute function.
Strings[Bearbeiten]
  • jsonObject json_newString(char* str)
    to allocate a json string with regular 1-byte encoding (ISO8859-1). The string must be 0-terminated.
  • jsonObject json_newStringWithLength(char* str, int n)
    like above, but the string does not need to be 0-terminated (i.e. to extract a partial string and/or limit the max. size).
  • jsonObject json_newStringByRef(char* str)
    like newString, with byRef behavior as described above
  • jsonObject json_newStringWithLengthByRef(char* str, int n)
    like newStringWithLength, with byRef behavior as described above
  • jsonObject json_newWideString(widechar* str)
    to allocate a json string from a given wide string (16bit chars)
  • jsonObject json_newWideStringWithLength(widechar* str, int n)
    like above, but the string does not need to be 0-terminated (i.e. to extract a partial string and/or limit the max. size).
  • jsonObject json_newWideStringByRef(widechar* str)
    to allocate a json string from a given wide string (16bit chars)
  • jsonObject json_newWideStringWithLengthByRef(widechar* str, int n)
    like above, but the string does not need to be 0-terminated (i.e. to extract a partial string and/or limit the max. size).

widechar = unsigned short; UTF16 kodiert.

Scalars[Bearbeiten]
  • jsonObject json_newBoolean(int boolValue)
    this is actually simply returning one of the two singleton objects: json_TRUE or json_FALSE.
  • jsonObject json_newTrue()
    this is actually simply returning the singleton object: json_TRUE.
  • jsonObject json_newFalse()
    this is actually simply returning the singleton object: json_TRUE.
  • jsonObject json_newNull()
    this is actually simply returning the singleton object: json_NULL.
  • jsonObject json_newLong(long lVal)
    a long integer (signed).
  • jsonObject json_newULong(unsigned long lVal)
    an unsigend long integer.
  • jsonObject json_newLongLong(long long lVal)
    a long long integer (signed).
  • jsonObject json_newULongLong(unsigned long long lVal)
    an unsigend long long integer.
  • jsonObject json_newDouble(double dVal)
    a double precision float number.
  • jsonObject json_newPointer(void* anything)
    an opaque pointer passed to expecco

Adding Fields and Array Slots[Bearbeiten]

  • int json_addStructField(jsonObject jStruct, char* fieldName, jsonObject el)
    adds a field named name to the struct with given value. The struct was previously created with json_newStruct(n). The struct will be automatically resized, if more than n fields are added (although it is better to provide a fitting n, to avoid those reallocations later). Returns the 0-based index of added the field (i.e. = current number of fields minus 1).
  • void json_setArrayElement(jsonObject jArr, int idx, jsonObject el)
    set the idx'th array element in a json array, which was previously created with json_newArray(n); 0 <= idx < n.
  • int json_addArrayElement(jsonObject jArr, jsonObject el)
    adds an array element to a json array, which was previously created with json_newArray(n). The array is resized if required (but better to give a matching size when crating). Returns the 0-based idndex of the added element (i.e. = current array size minus 1).

Queries[Bearbeiten]

  • bool json_isArray(jsonObject obj)
    returns a C-true (1) if the argument is a json-array object, C-false (0) otherwise.
    Useful to check for or case-switch on the the type of a jsonObject fetched from an input pin via "jsonValue(<inPin>)".
  • bool json_isTrue(jsonObject obj)
bool json_isFalse(jsonObject obj)
bool json_isBoolean(jsonObject obj)
bool json_isNull(jsonObject obj)
bool json_isLong(jsonObject obj)
bool json_isULong(jsonObject obj)
bool json_isLongLong(jsonObject obj)
bool json_isULongLong(jsonObject obj)
bool json_isInteger(jsonObject obj)
bool json_isDouble(jsonObject obj)
bool json_isPointer(jsonObject obj)
bool json_isBytes(jsonObject obj)
bool json_isFloats(jsonObject obj)
bool json_isDoubles(jsonObject obj)
bool json_isString(jsonObject obj)
bool json_isWideString(jsonObject obj)
bool json_isStruct(jsonObject obj)
Similar to above. Use when an input pin is of type ANY and you fetch the value via "jsonValue(<pin>)" to case-switch into different branches to fetch the value.
  • int json_arraySize(jsonObject obj)
    returns the number of elements in a json array
  • int json_structSize(jsonObject obj)
    returns the number of struct fields in a json struct object


  • char* json_getIndexedStructFieldNameC(jsonObject obj, int index)
    returns the name of the struct element field at index (0-based). This returns a C-char* which becomes void after the action's function (i.e. the underlying json field name object will be freed),

Value Extraction[Bearbeiten]

  • int json_booleanValue(jsonObject obj)
    returns a C-boolean (i.e. 0 or 1) from a jsonObject which must be a jsonBoolean (i.e. "json_isBoolean()" should have returned true).
    Fails if the object is not a jsonBoolean.
  • long json_longValue(jsonObject obj)
  • unsigned long json_uLongValue(jsonObject obj)
  • double json_doubleValue(jsonObject obj)
  • int json_booleanValue(jsonObject obj)
  • long long json_longLongValue(jsonObject obj)
  • unsigned long long json_uLongLongValue(jsonObject obj)
  • char* json_stringValue(jsonObject obj)
    the returned char* will be invalid after the action's execution
  • unsigned char* json_bytesValue(jsonObject obj)
unsigned intjson_bytesLength(jsonObject obj)
the retrieved byte* will be invalid after the action's execution
  • jsonObject json_getArrayElement(jsonObject obj, int index)
    returns the value of the array element at index (0-based).
  • jsonObject json_getIndexedStructFieldValue(jsonObject obj, int index)
    returns the value of the struct element field at index (0-based).

Value Setting[Bearbeiten]

  • void json_setArrayElement(jsonObject array, unsigned index, jsonObject newValue)
    the index is 0-based.

JSON Examples[Bearbeiten]

To create a struct and pass that back to expecco:

execute() {
   jsonObject retVal, subStruct;

   retVal = json_newStruct(10);
   json_addStructField(retVal, "someBoolean", json_TRUE);
   json_addStructField(retVal, "someNumber1", json_newDouble(3.14159));
   json_addStructField(retVal, "someNumber2", json_newLong((long) 199));

   subStruct = json_newStruct(10);
   json_addStructField(subStruct, "name1", json_newString("whatever you need to be passed here"));
   json_addStructField(subStruct, "name2", json_newString("and another string"));

   json_addStructField(retVal, "moreInfo", subStruct);
   putJsonObject(outPin, retVal);
}

To create an array of strings:

execute() {
   jsonObject retVal;

   retVal = json_newArray(3);
   json_setArrayElement(retVal, 0, json_newString("string element 1"));
   json_setArrayElement(retVal, 1, json_newString("string element 2"));
   json_setArrayElement(retVal, 2, json_newString("string element 3"));

   putJsonObject(outPin, retVal);
}

To create an array of doubles:

execute() {
   jsonObject retVal;

   retVal = json_newArray(3);
   json_setArrayElement(retVal, 0, json_newDouble(1.0));
   json_setArrayElement(retVal, 1, json_newDouble(2.0));
   json_setArrayElement(retVal, 2, json_newDouble(3.0));

   putJsonObject(outPin, retVal);
}

Bridge Wire Protocol[Bearbeiten]

The following wire protocol is used for all C, Node, Python and Smalltalk bridges (by the time of writing). The .NET and Java bridges use a much more sophisticated protocol, which is not described here.


Frame Format[Bearbeiten]

Messages are interchanged in frames which contain the length of a payload and the payload itself. A packet's payload is always encoded as a JSON object.

This payload can be (currently) delivered in one of the following frames:

  • HTTP/REST frame
    the payload is the POST data of an HTTP packet; the payload length is provided by the HTTP header.
  • a binary frame, consisting of 4 bytes of payload length, followed by the payload.
  • a textual frame, consisting of the payload length as ascii string, a space as separator, followed by the payload.

Both the expecco side and the cLibrary support both the binary and textual frame formats. By default (unless otherwise configured), both sides use the textual frame format initially. Every bridge implemntation must at least implement this textual frame format, and must come up with this initially.

Future extensions will be backward compatible.

Currently, the HTML/REST format is not yet supported directly by the cBridge library. However, it can be used if the target app already includes an HTTP/REST service and passes incoming payload to the cLibrary.

Handshake[Bearbeiten]

The bridge client (cLibrary) usually opens a TCP socket and listens for a connection. After accept, a single hello message is sent to the expecco side. By default, this initial hello message is ALWAYS encoded as a textFrame. (however, depending on the hello message's payload, the protocol may be switched to another format for further messages).

Requests (General Format)[Bearbeiten]

Requests can be sent from either side. On the expecco side, a request call will block the calling thread, until a response is received. On the cBridge side, a callback will be called, when the response is received. In theory, the protocol allows for multiple request to be sent and outstanding until corresponding responses are received. However this is actually not needed and therefore currently not supported by either expecco, not the cBridge library.

All requests must conform to the format:

{
    "type":        "request",                    // required
    "command":     "<nameOfCommand>",            // required
    "seq":         sequenceNr                    // required

    "body":        { ... }                       // optional
    "part":        partNr                        // optional
    "more":        boolean                       // optional
}

where:

  • command the name as given to the "cBridge_registerHandler() function previously,
  • seq a unique number assigned to every request/event by the sender side (usually simply a running number, or UUID-string). The value of "seq" will later be found in the corresponding response object.
  • part if present, this must be a running number, sequencing the parts of a single message. Numbers start with 1.
  • more if present, informs that more parts are to be expected for the current message. If missing and this is a part-package, it is assumed that this was the last part and the message is now complete (i.e. defaults to false)
  • body request specific

Long packets may be split into and sent as a sequence of smaller packets, if a partner has limited resources in memory) or to reserve bandwidth for other communication. It should then add both a "part" field (containing a part-sequence number, starting at 1 with every message) and a "more" boolean flag, which tells the other side, that more packets are to be received for the same message. Messages with huge amounts of data will split this way (eg. screenDump). The details on how that bulk data is split among the individual parts is to be documented in the concrete message.

Response (General Format)[Bearbeiten]

Responses are only sent as an answer to a previous incoming request. Every request MUST be answered by a response.

All responses must contain the following fields:

{
    "type":        "response",                   // required
    "requestSeq":  sequenceNr                    // required
    "success":     boolean,                      // required

    "command":     "<nameOfCommand>",            // optional (but recommended)
    "body":        { ... },                      // optional (usually present if success == true)
    "errorMessage":"...",                        // optional (if success == false)
    "part":        partNr                        // optional
    "more":        boolean                       // optional
}

where:

  • command the command from the corresponding request, for which this is the response.
  • requestSeq the seqNr of the corresponding request, for which this is the response.
  • success boolean true or false
  • errorMessage if success is false
  • part and more as described above
  • body response specific

Event (General Format)[Bearbeiten]

Events can be sent at any time (i.e. while waiting for a response, while executing a request action, or at any other time). The bridge ensures, that an ongoing message is not disturbed (i.e. the actual packet sending is atomic). Events are not answered by a response.

All responses must contain the following fields:

{
    "type":        "event",                      // required
    "seq":         sequenceNr                    // required
    "event":       "<nameOfEvent>",              // required

    "arguments":   { ... },                      // optional
    "part":        partNr                        // optional
    "more":        boolean                       // optional
}

where:

  • seq same as in requests. Event sequence numbers may not be the same as request sequence numbers (i.e. you can use one single sequence-nr generator, which simply increments on every packet)
  • arguments event specific arguments

Multipart Packages[Bearbeiten]

Long messages may be split into multiple parts. Then, the first N-1 packets contain the fields:

   "part": <nr>
   "more": true

and the last part contains:

   "part": <N>
   "more": false

where <nr> is a sequence nr starting with 1. Each part should contain a "data": field (or similar), where the values are concatenated to form the effective long data. Typically, the splitted data is a long string containing Base64 encoded bytes (such as a big screenshot's PNG image). The name of the field which contains the splitted data depends on the message type and is specified there. Clients which want to receive splitted packets (due to internal receive buffer limitations) must announce this in their initial handshake, by adding a corresponding limits field.

Requests (from expecco to cBridge)[Bearbeiten]

The following lists well known messages only. The format of user-defined messages (i.e. for which a handler was registered) is variable and opaque to the bridge.

REQUEST: protocol[Bearbeiten]

Sent from expecco to change the protocol. This will only be sent if the bridge announced the availability of another protocol (via a HELLO event), AND expecco decides to use another.

Expecco will choose the first within the list of available protocols, which is also supported by the expecco side.

{
    "type":    "request",                       // required
    "command": "protocol",                      // required
    "seq":     sequenceNr                       // required
    "body":    {                                // required
                 "protocol":  "protocolToUse",      // required
               }
}

when this message arrives, the response MUST be sent in the current protocol mode. Then, any further communication MUST be performed in the new protocol.
Currently, this is not used by expecco when running against the existing bridges. The text/json protocol will always be used (if presented as first message in the hello announcement).

REQUEST: defineAction[Bearbeiten]

This is an internal request for Bridged C actions. Its format is not part of the stable API. The bridge will immediately send a response.

REQUEST: callAction[Bearbeiten]

This is an internal request for Bridged C actions. Its format is not part of the stable API. The bridge will immediately send a response, call the defined action and send a finished event when done (i.e. this is an async call finished notification).

REQUEST: callFunction[Bearbeiten]

This is an internal request for Script languages. Its format is not part of the stable API. The bridge will call the named function and send a response when done (i.e. a synchronous call finished response).
The current CBridge does not need or support this request, but it may be implemented in future versions.

REQUEST: invokeMethod[Bearbeiten]

This is an internal request for Script languages. Its format is not part of the stable API. The bridge will invoke a named method and send a response when done (i.e. a synchronous call finished response).
The current CBridge does not need or support this request, but it may be implemented in future versions (for C++).

REQUEST: queryMethod[Bearbeiten]

This is an internal request for Script languages. Its format is not part of the stable API. The bridge will check for the presence of a named method and send a response containing detail information.
The current CBridge does not need or support this request, but it may be implemented in future versions (for C++).

REQUEST: evalCode[Bearbeiten]

This is an internal request for Script languages. Its format is not part of the stable API. The bridge will evaluate (i.e. interpret) the code and send a response when done (i.e. a synchronous call finished response).
The CBridge does not need or support this request (and likely never will) - it is used for other script languages..

REQUEST: getProperty[Bearbeiten]

This is an internal request to access slots of an object. Its format is not part of the stable API. The bridge will retrieve an object's slot and return it in its synchronous response.
The CBridge does not need or support this request (and likely never will) - it is used for other script languages.

REQUEST: getPropertyNames[Bearbeiten]

This is an internal request to retrieve slot names o an object. Its format is not part of the stable API. The bridge will retrieve an object's slot names and return it in its synchronous response.
The CBridge does not need or support this request (and likely never will) - it is used for other script languages..

REQUEST: releaseReference[Bearbeiten]

This is an internal request used with script languages with garbage collectors. Its format is not part of the stable API.
The CBridge does not need or support this request, but it may be implemented in future versions (for C++).

Events (from cBridge to expecco)[Bearbeiten]

EVENT: hello[Bearbeiten]

Sent from the bridge to expecco to provide version and protocol formats.

{
    "type":      "event",                      // required
    "event":     "hello",                      // required
    "seq":       sequenceNr                    // required
    "body":      {                             // optional
                  "protocols": [ "<supported1>", ... ]       // optional
                  "limits":    [ <limit1>, ... ]             // optional
                  "bridge":    {                             // optional
                                "name":     "<bridgeName>"          // optional
                                "version":  "<version string>",     // optional
                               }
                  "system":    {                             // optional
                                "name":     "<systemName>"     }    // optional
                                "version":  "<systemVersion>"  }    // optional
                               }
                 }
}

Used to announce version information and available protocols.

  • version: a string of the form "<major>.<minor>". For same major numbers, any minor less or equal to a partner's own minor version must be supported. The major version MUST be incremented for incompatible (format) changes. The minor version should be incremented if new protocol messages or new fields are added. Expecco will fall back to the cBridge protocol version, iff the cBridge's hello-announced version is smaller than expecco's own implemented protocol version.
  • protocols: list of supported protocols, in order of preference. Must include at least 'text/json'.
  • limits: list of limits

Each <supported> element describes a protocol which is supported by the target/cLibrary, in order of preference (the targets preference). The <supported> strings are of the form: "frameFmt/payloadFmt[/version]", where frameFmt describes the frame format, payloadFmt the encoding of the payload and the optional version further details on the used payload format. A missing version part defaults to "1".

Currently known protocols are:

  • text/json/1 - JSON encoded payload, in a text frame (i.e. prefixed by payload size in ascii and space)
  • binary/json/1 - JSON encoded payload, in a binary frame (i.e. prefixed by 4 bytes of payload size)
  • http/json/1 - JSON encoded payload, in an HTTP frame

The partner (expecco) may decide to switch the protocol, based on the returned information.

The optional <limit> elements can be used to order the partner to not exceed certain limits. Either side may refuse and ignore packets (or respond with an error) if a requested limit is exceeded. Currently known limits are:

  • { "payloadSize": maxSize }
    tells the partner to not send packets longer than that size. If required, packets must then be split when into smaller continuation packets. This can be used to limit the amount of memory needed inside a partner when converting to/from JSON (i.e. when running in an embedded system). As the overall packet size is hard to predict (due to sequence numbers), this limit will be applied differently depending on the concrete message (see notes there).
  • the optional bridge and system objects allow for additional informal to be sent back. These are optional and may be used by expecco to present nicer connection names or other user hints.
EVENT: finished[Bearbeiten]

This is an internal event for Bridged C actions. Its format is not part of the stable API.

EVENT: out[Bearbeiten]

This is an internal event for Bridged C actions. Its format is not part of the stable API. It is sent from the bridge to send a value to an output pin.

EVENT: output[Bearbeiten]

Sent from the bridge to expecco to output a string to the expecco Transcript (console), to expecco's stdout or to expecco's stderr.

{
    "type":      "event",                      // required
    "event":     "output",                     // required
    "seq":       sequenceNr                    // required
    "body":      {
                  "message":   "<how>",                      // required
                  "target":    "<stream>",                   // required
                  "args":      ["<fmt>",...]                 // required for show or showCR
                 }
}
  • message one of "cr", "show" or "showCR"
  • target one of "Transcript", "Stdout" or "Stderr"
  • args the arguments for the string to be sent. If the first argument ("<fmt>") contains placeholders like "%1", these are replaced by corresponding values from the remaining args (i.e "some error in %1"). Otherwise, the arguments are concatenated to form a single string.

Requests (from cBridge to expecco)[Bearbeiten]

The CBridge uses these to send messages to the expecco Transcript (console), to add entries to the activity log or to write data to output pins.

GUI Automation Support[Bearbeiten]

GUI automation support for QT, OpenETS and Smalltalk use the same protocol and message format as described above. Bridges may choose to implement (or support) either the above code execution messages, or the GUI messages described here, or both.

If a bridge supports only the GUI messages, no remote code execution will be possible. However, the GUI browser will work with the bridged application.

On the other hand, if only the above code execution messages are supported, no GUI browsing will be possible, but remote code execution will still work.

Thus, GUI automation support does NOT require the above code execution protocol as a prerequisite.


Architecture[Bearbeiten]

Modified Application Setup[Bearbeiten]

To interface an application (possibly embedded) with a graphical user interface to expecco for automation, you have to add the following to the (C/C++) application:

  • link the cBridge library statically or dynamically to your application
  • write callback functions as described below
  • call "cBridge_registerHandler(...) for these handlers, as described above
  • call "cBridge_startAsyncServer(...) to start the bridge, as described above

Code Injection Setup[Bearbeiten]

Start the deployed cBridge (which is provided as binary by exept) with an inject command line argument. It will then start the application and inject itself into it. Notice that this is not possible on all supported architectures; currently Windows, Linux and OSX are supported. Embedded systems must use the previous modified application setup.

Once started, the cBridge server awaits incoming connections and calls into the registered handler, when a request arrives.

GUI Handlers[Bearbeiten]

the following functions must be implemented and registered towards the bridge:
Details to be documented...

ui.getTopElements[Bearbeiten]

Returns the GUIs top-level elements. See example below.

ui.getChildElements[Bearbeiten]

Returns an element's child elements. See example below.

ui.getApplicationProperties[Bearbeiten]

Returns properties of the application. See example below.

ui.getElementProperties[Bearbeiten]

returns properties of an element. See example below.

ui.getElementProperty[Bearbeiten]

returns a single property of an element

ui.getScreenShot[Bearbeiten]

returns a screenshot

Additional (optional) GUI Handlers[Bearbeiten]

ui.startRecording[Bearbeiten]

To enable recording in the application.

ui.stopRecording[Bearbeiten]

To disable recording in the application.

ui.recording[Bearbeiten]

This is an alternative to control recording. It passes a true/false parameter.

Events[Bearbeiten]

When in recording mode, events are delivered by calling:

ui.sendRecordedEvent[Bearbeiten]

Example Code Fragments[Bearbeiten]

//
// handler registration
// (called by main, upon startup, before starting the cBridge server)
//
static void 
registerEntries() {
     cBridge_registerHandler("ui.getTopElements", getTopElements);
     cBridge_registerHandler("ui.getChildElements", getChildElements);
     cBridge_registerHandler("ui.getAppProperties", getAppProperties);
     cBridge_registerHandler("ui.getProperties", getProperties);
     cBridge_registerHandler("ui.getProperty", getProperty);
     cBridge_registerHandler("ui.getScreenshot", getScreenshot);
     ...
}
static jsonObject
createElement(char* idString, char* elementType) {
     jsonObject elementInfo = json_newStruct(2);
     json_addStructField(elementInfo, "id", json_newString(idString));
     json_addStructField(elementInfo, "name", json_newString(elementType));
     return elementInfo;
}
static requestStatus 
getTopElements(jsonObject requestIn, jsonObject responseOut) {
     jsonObject body = json_newStruct(1);
     jsonObject arr = json_newArray(6);

     json_addArrayElement(arr, createElement("3335", "label",  ""));
     json_addArrayElement(arr, createElement("2503", "window", "frame1"));
     json_addArrayElement(arr, createElement("2502", "menu",   "mainmenu"));
     json_addArrayElement(arr, createElement("2896", "panel", 	"myTopPanel"));
     json_addArrayElement(arr, createElement("0816", "button", "myButton"));
     json_addArrayElement(arr, createElement("0819", "radioButton", "myRadioButton"));
     json_addStructField(body, "topElements", arr);
     json_addStructField(responseOut, "body", body);
     return REQUEST_OK;
}
static requestStatus 
getChildElements(jsonObject requestIn, jsonObject responseOut) {
     jsonObject arguments = json_getStructField(requestIn, "arguments");
     jsonObject elementId = json_getStructField(arguments, "id");
     char* id = json_stringValue(elementId);
     ...
     same as above, filling an array with child-infos for id
     ...
     return REQUEST_OK;
}
static requestStatus 
getProperties(jsonObject requestIn, jsonObject responseOut) {
     jsonObject arguments = json_getStructField(requestIn, "arguments");
     jsonObject elementId = json_getStructField(arguments, "id");
     char* id = json_stringValue(elementId);
     ...
     fill an array with child-properties for id
     ...
     return REQUEST_OK;
}

Protocol Examples[Bearbeiten]

The following dumps represent requests sent to the cbridge partner and responses received.

Connect[Bearbeiten]

connect to bridge@localhost:8855...
wait for connectAck of bridge@localhost:8855...
## CBridge wait

the bridge partner sends an hello-event message to announce its supported protocols and version identification. This initial event is always transmitted in "simpleText/json" encoding.

<< CBridge: received: >>145 {"type":"event","event":"hello","seq":1,"body":{"bridge":{"name":"cBridge","version":"1.1"},"protocols":["simpleText/json","simpleBinary/json"]}}<<
 
 Incoming Message: Payload:
 {
   "type":"event",
   "event":"hello",
   "seq":1,
   "body":
       {
           "bridge":{
               "name":"cBridge",
               "version":"1.1"
           },
           "protocols":[
               "simpleText/json",
               "simpleBinary/json"
           ]
       }
 }

ui.getTopElements[Bearbeiten]

to ask the bridge partner for a list of top-level UI components (i.e. windows), expecco sends this request:

>> 56 {"seq":1,"type":"request","command":"ui.getTopElements"}

the bridge partner responds with a list of toplevel elements in the body

<< CBridge: received: >>179 {"type":"response","requestSeq":1,"command":"ui.getTopElements","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}<<

Incoming Message: Payload=
{
   "type":"response",
   "requestSeq":1,
   "command":"ui.getTopElements",
   "body":[
       {
           "id":"1",
           "name":"Address Book - common lib",
           "type":"MainWindow",
           "text":"",
           "visible":true
       }],
   "success":true
 }

ui.getChildElements[Bearbeiten]

for each UI-element, the list of child elements is asked for (recursively):

>> 81 {"seq":2,"type":"request","command":"ui.getChildElements","arguments":{"id":"1"}}

the bridge's response is similar to the topElements request's response:

<< CBridge: received: >>223 {"type":"response","requestSeq":2,"command":"ui.getChildElements","body":[{"id":"2","name":"","type":"AddressWidget","text":"","visible":true},{"id":"3","name":"","type":"QMenuBar","text":"","visible":true}],"success":true}<<

Incoming Message: Payload:
 {
   "type":"response",
   "requestSeq":2,
   "command":"ui.getChildElements",
   "body":[
     {
         "id":"2",
         "name":"",
         "type":"AddressWidget",
         "text":"",
         "visible":true
     },
     {
         "id":"3",
         "name":"",
         "type":"QMenuBar",
         "text":"",
         "visible":true
     }
   ],
   "success":true
 }

ui.getAppProperties[Bearbeiten]

this asks for application properties; in this case, properties of the running qt program:

>> 58 {"seq":3,"type":"request","command":"ui.getAppProperties"}

<< CBridge: received: >>2108 {"type":"response","requestSeq":3,"command":"ui.getAppProperties","body":[{"name":"QApplication","properties": [{"propertyName":"autoSipEnabled","propertyValue":"true","propertyType":"bool"},{"propertyName":"cursorFlashTime","propertyValue":"1200","propertyType":"int"}, ... {"propertyName":"windowIcon","propertyValue":"","propertyType":"QIcon"}]},{"name":"QGuiApplication","properties":[ ... ]},{"name":"QCoreApplication","properties":[ ...]},{"name":"QObject","properties":[ ... ]}],"success":true}<<

 Incoming Message: Payload:
 {
   "type":"response",
   "requestSeq":3,
   "command":"ui.getAppProperties",
   "body":[
       {
           "name":"QApplication",
           "properties":[
               {"propertyName":"autoSipEnabled","propertyValue":"true","propertyType":"bool"},
               {"propertyName":"cursorFlashTime","propertyValue":"1200","propertyType":"int"},
               ... 
               {"propertyName":"windowIcon","propertyValue":"","propertyType":"QIcon"}
           ]
       },
       {
           "name":"QGuiApplication",
           "properties":[ 
               ... 
           ]
       },
       {
           "name":"QCoreApplication",
           "properties":[ 
               ... 
           ]
       },
       {
           "name":"QObject",
           "properties":[ ... ]
       }
   ],
   "success":true
 }

ui.getProperties[Bearbeiten]

asks for the properties of an UI element (the id is as retrieved via getTopElements or getChildElements). Properties may be grouped by the bridge (eg. to group them by inheritance). Then the body consists of a vector of individual group properties (as shown below). If not, the body should consist of a "properties" field only.

>> 78 {"seq":4,"type":"request","command":"ui.getProperties","arguments":{"id":"1"}}
<< CBridge: received: >>6022 {"type":"response","requestSeq":4,"command":"ui.getProperties","body":[{"name":"DynamicProperties","properties":[{"propertyName":"expecco-id","propertyValue":"1","propertyType":"QString"}, ... ]},{"name":"QMainWindow","properties":[ ... ]}, ... ],"success":true}<<

Incoming Message: Payload:
 {
   "type":"response",
   "requestSeq":4,
   "command":"ui.getProperties",
   "body":[
       {
         "name":"DynamicProperties",
         "properties":[
           {"propertyName":"expecco-id","propertyValue":"1","propertyType":"QString"}, 
           ... 
         ]
       },
       {
          "name":"QMainWindow",
          "properties":[ 
            ... 
          ]
       }, 
       ... 
   ],
   "success":true
 }

ui.getElementByXPath[Bearbeiten]

retrieves a singe UI element by xpath. Bridge partners MUST at least support a simple full xpath without attribute filters and special navigation elements (parent, sibling etc.). I.e. /A/B/.../X kind of simple xpathes. However, only xpath queries supported by the bridge partner will eventually be allowed as locators in expecco.

>> 95 {"seq":5,"type":"request","command":"ui.getElementByXPath","arguments":{"xpath":"/MainWindow"}}

the response is similar to the one returned by getTopElements or getChildElements; however, only a single element's info is returned, or an error response is returned, if no such element exists.

<< CBridge: received: >>182 {"type":"response","requestSeq":5,"command":"ui.getElementByXPath","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}<<

Incoming Message: Payload:
 {
   "type":"response",
   "requestSeq":5,
   "command":"ui.getElementByXPath",
   "body":[
       {
           "id":"1",
           "name":"Address Book - common lib",
           "type":"MainWindow",
           "text":"",
           "visible":true
       }
   ],
   "success":true}

ui.getElementsByXPath[Bearbeiten]

retrieves a list of element infos for elements matching the given xpath. If the bridge partner should support wildcard xpathes or report an error. Notice that iff the partner does not support wildcards, the corresponding expecco action block will also report an error.

>> 96 {"seq":6,"type":"request","command":"ui.getElementsByXPath","arguments":{"xpath":"/MainWindow"}}
<< CBridge: received: >>183 {"type":"response","requestSeq":6,"command":"ui.getElementsByXPath","body":[{"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}],"success":true}<<

Incoming Message: Payload:
 {
   "type":"response",
   "requestSeq":6,
   "command":"ui.getElementsByXPath",
   "body":[
       {"id":"1","name":"Address Book - common lib","type":"MainWindow","text":"","visible":true}
   ],
   "success":true
 }

Notice, that in the above dump, only a single element matched the given (simple) xpath

ui.handleRemoteAction[Bearbeiten]

this request is sent to force the bridge partner to simulate a user event (action), such as a mouse click, keyboard input etc. The concrete list of supported events depends on the implementation of the bridge ui adapter.

>> 244 {"seq":4,"type":"request","command":"ui.handleRemoteAction","arguments":{"action":"QT::OpenMenuOrTriggerAction","parameters":"path=/MainWindow[1]/QMenuBar[1]/QAction[@name='&Tools']","path":"/MainWindow[1]/QMenuBar[1]/QAction[@name='&Tools']"}}

the bridge partner responds with either a success or error response:

<< CBridge: received: >>83 {"type":"response","requestSeq":4,"command":"ui.handleRemoteAction","success":true}<<

Incoming Message: Payload:
 {
   "type":"response",
   "requestSeq":4,
   "command":"ui.handleRemoteAction",
   "success":true
 }

Disconnect[Bearbeiten]

## CBridge terminate reader
## CBridge close socket
## CBridge reader process got TerminateProcessRequest
## CBridge reader process finished

Sample Application with GUI Automation Support[Bearbeiten]

--- to be added here ---



Copyright © 2014-2024 eXept Software AG