Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Tests

on:
push:
branches: [main, master]
pull_request:
branches: [main, master]

jobs:
test:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [18, 20, 22]

steps:
- uses: actions/checkout@v4

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Run tests
run: npm test
99 changes: 86 additions & 13 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,65 @@ Installation

$ npm install cdp-client

Proxy Support (CDP 5.1+)
------------------------

The client supports the Generic Proxy Protocol which allows a single WebSocket connection to access
multiple backend CDP applications through multiplexing.

When connecting to a CDP application configured as a proxy, the client automatically:

- Sends a ServicesRequest to discover available proxy services
- Receives ServicesNotification with available backend applications
- Establishes virtual connections to backend applications with ``type: 'websocketproxy'`` and ``metadata.proxy_type: 'studioapi'``

All discovered applications appear as children of the root system node, enabling transparent access
to their structure and values through the standard API.

Use Cases
~~~~~~~~~

- Simplified firewall configuration - only one port needs to be opened
- SSH port forwarding - forward a single port to access entire CDP system

Example
~~~~~~~

.. code:: javascript

// Connect to a proxy-enabled CDP application (with authentication)
const client = new studio.api.Client("127.0.0.1:7690", {
credentialsRequested: async (request) => {
return { Username: "cdpuser", Password: "cdpuser" };
}
});

// Track subscribed apps to avoid duplicates
const subscribedApps = new Set();

function subscribeToApp(app) {
const appName = app.name();
if (subscribedApps.has(appName)) return;
subscribedApps.add(appName);

// subscribeWithResume automatically restores subscriptions after reconnection
client.subscribeWithResume(appName + '.CPULoad', value => {
console.log(`[${appName}] CPULoad: ${value}`);
});
}

client.root().then(root => {
// Subscribe to apps already visible
root.forEachChild(app => subscribeToApp(app));

// Subscribe to structure changes to catch sibling apps as they're discovered
root.subscribeToStructure((name, change) => {
if (change === 1) { // ADD
root.child(name).then(app => subscribeToApp(app));
}
});
}).catch(err => console.error("Connection failed:", err));

API
---

Expand Down Expand Up @@ -303,6 +362,20 @@ client.find(path)
// use the load object referring to CPULoad in MyApp
});

client.close()
^^^^^^^^^^^^^^

- Usage

Close all connections managed by this client. This stops reconnection attempts
and cleans up all resources. Call this when you are done using the client.

- Example

.. code:: javascript

client.close();

Instance Methods / INode
~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -334,11 +407,11 @@ node.info()
+------------------+------------------------------+---------------------------------------------------------------+
| Property | Type | Description |
+==================+==============================+===============================================================+
| Info.node_id | number | Application wide unique ID for each instance in CDP structure |
| Info.nodeId | number | Application wide unique ID for each instance in CDP structure |
+------------------+------------------------------+---------------------------------------------------------------+
| Info.name | string | Nodes short name |
+------------------+------------------------------+---------------------------------------------------------------+
| Info.node_type | studio.protocol.CDPNodeType | | Direct CDP base type of the class. One of the following: |
| Info.nodeType | studio.protocol.CDPNodeType | | Direct CDP base type of the class. One of the following: |
| | | - CDP_UNDEFINED |
| | | - CDP_APPLICATION |
| | | - CDP_COMPONENT |
Expand All @@ -351,7 +424,7 @@ node.info()
| | | - CDP_OPERATOR |
| | | - CDP_NODE |
+------------------+------------------------------+---------------------------------------------------------------+
| Info.value_type | studio.protocol.CDPValueType | | Optional: Value primitive type the node holds |
| Info.valueType | studio.protocol.CDPValueType | | Optional: Value primitive type the node holds |
| | | | if node may hold a value. One of the following: |
| | | - eUNDEFINED |
| | | - eDOUBLE |
Expand All @@ -367,15 +440,15 @@ node.info()
| | | - eBOOL |
| | | - eSTRING |
+------------------+------------------------------+---------------------------------------------------------------+
| Info.type_name | string | Optional: Class name of the reflected node |
| Info.typeName | string | Optional: Class name of the reflected node |
+------------------+------------------------------+---------------------------------------------------------------+
| Info.server_addr | string | Optional: StudioAPI IP present on application nodes that |
| | | have **Info.is_local == false** |
| Info.serverAddr | string | Optional: StudioAPI IP present on application nodes that |
| | | have **Info.isLocal == false** |
+------------------+------------------------------+---------------------------------------------------------------+
| Info.server_port | number | Optional: StudioAPI Port present on application nodes that |
| | | have **Info.is_local == false** |
| Info.serverPort | number | Optional: StudioAPI Port present on application nodes that |
| | | have **Info.isLocal == false** |
+------------------+------------------------------+---------------------------------------------------------------+
| Info.is_local | boolean | Optional: When multiple applications are present in root node |
| Info.isLocal | boolean | Optional: When multiple applications are present in root node |
| | | this flag is set to true for the application that the client |
| | | is connected to |
+------------------+------------------------------+---------------------------------------------------------------+
Expand Down Expand Up @@ -435,7 +508,7 @@ node.forEachChild(iteratorCallback)
.. code:: javascript

cdpapp.forEachChild(function (child) {
if (child.info().node_type == studio.protocol.CDPNodeType.CDP_COMPONENT) {
if (child.info().nodeType == studio.protocol.CDPNodeType.CDP_COMPONENT) {
// Use child object of type {INode} that is a CDP component.
}
});
Expand Down Expand Up @@ -477,7 +550,7 @@ node.subscribeToValues(valueConsumer, fs, sampleRate)
- Usage

Subscribe to value changes on this node. On each value change valueConsumer function is called
with value of the nodes value_type and UTC Unix timestamp in nanoseconds (nanoseconds from 01.01.1970).
with value of the nodes valueType and UTC Unix timestamp in nanoseconds (nanoseconds from 01.01.1970).
Timestamp refers to the time of value change in connected application on target controller.

- Example
Expand Down Expand Up @@ -579,7 +652,7 @@ node.subscribeToEvents(eventConsumer, timestampFrom)
+-------------------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------+
| Event.code | studio.protocol.EventCode | Optional: Event code flags. Any of: |
| | +-----------------------------+---------------------------------------------------------------------------------------------+
| | | - eAlarmSet | The alarm's Set flag/state was set. The alarm changed state to "Unack-Set" |
| | | - aAlarmSet | The alarm's Set flag/state was set. The alarm changed state to "Unack-Set" |
| | +-----------------------------+---------------------------------------------------------------------------------------------+
| | | - eAlarmClr | The alarm's Set flag was cleared. The Unack state is unchanged. |
| | +-----------------------------+---------------------------------------------------------------------------------------------+
Expand All @@ -591,7 +664,7 @@ node.subscribeToEvents(eventConsumer, timestampFrom)
| | +-----------------------------+---------------------------------------------------------------------------------------------+
| | | - eNodeBoot | The provider reports that the CDPEventNode just have booted. |
+-------------------+-----------------------------+-----------------------------+---------------------------------------------------------------------------------------------+
| Event.status | studio.protocol.EventStatus | Optional: Value primitive type the node holds if node may hold a value. Any of: |
| Event.status | studio.protocol.EventStatus | Optional: Status flags as a numeric bitfield. Possible flag values: |
| | +-----------------------------+---------------------------------------------------------------------------------------------+
| | | - eStatusOK | No alarm set |
| | +-----------------------------+---------------------------------------------------------------------------------------------+
Expand Down
Loading