Skip to main content

Connecting the widget with the Event Bus

Deprecated: Vert.x-based Telestion

Please note that the application and development of backend services using Vert.x in Telestion are deprecated. In the future, Telestion backend services will be developed using TypeScript and Deno, or through custom integrations with other languages.

While there may be a compatibility layer for Vert.x services in the future, its availability is not guaranteed.

For developing backend services, please refer to the (Work-in-Progress) documentation available here: https://pklaschka.github.io/telestion-docs-new/. Once the documentation is complete, it will be moved to the main Telestion documentation.

Additional Information:

  • NATS will be used as the distributed message bus/message broker for Telestion.
  • NATS' integrated authentication and authorization features will handle authentication and authorization for Ground Station operators, providing a single source of truth.
  • The event bus bridge will no longer be featured, and clients will be directly connected to the NATS server.

To establish a more technology-independent terminology, the Telestion project will modify the naming conventions as follows:

  • The NATS server will be referred to as the message broker, message bus, or NATS server interchangeably.
  • Components that act as services without an attached user interface, will be referred to as services or backend services collectively.
  • Components that provide a user interface, formerly known as "clients," will be referred to as frontends. In most cases, the frontend will authenticate to the message broker as the user, while backend services will act on their own behalf.

These changes aim to provide clearer and more consistent terminology, accounting for the possibility of components having both service and frontend functionalities. Additionally, the use of "client" for frontends will be replaced to avoid potential confusion.

We recommend using the NATS client libraries recommended by NATS itself, unless there are no suitable options available for the targeted language/environment. We will not develop our own client libraries unless there is a lack of suitable options or significant advantages justify the effort.

While Deno/TypeScript is the recommended choice for backend services, its use is not mandatory. Developers will be encouraged to use Deno/TypeScript where appropriate, but other options will still be supported for specific services. Comprehensive documentation and resources will be provided for writing and deploying Deno-based backend services in TypeScript.

Please consider these changes and updates as you continue with Telestion development.

In this tutorial, you'll extend your widget from the Building UI using React Spectrum tutorial to show some actual data and be able to send commands when pressing the "Reset" button.

Prerequisites

To complete this tutorial, you should be familiar with React hooks, NodeJS, and the concept of the Event Bus. You should also have access to the code from the Building UI using React Spectrum tutorial as you'll extend the widget from that tutorial.

What you'll build

You'll use the Event Bus bridge and its APIs from the @wuespace/telestion-client-core package to interact with the Event Bus directly from your widget. You'll also build a mock server so that you can test your widget without the overhead of running the entire (Java-based) Application layer.

First, you'll add a mock Application server. Then, you'll use the useChannelLatest() hook to display actual data. Lastly, you'll send an Event Bus message when an operator presses the "Reset" button.

In the end, you'll have written the following code:

src/plugins/mock-server.ts
import {
CallbackId,
MockServer,
OnClose,
OnInit
} from '@wuespace/vertx-mock-server';

class MyMockServer extends MockServer implements OnInit, OnClose {
private resetListener: CallbackId = 0;
private systemStatusIntervalId: any;

onInit() {
const systems = ['SAT A', 'SAT B', 'SAT C'];

this.resetListener = this.register('reset', raw => {
console.log('Received reset request:', raw.message);
});

this.systemStatusIntervalId = setInterval(() => {
systems.forEach(system => {
this.send(`system-status/${system}`, Math.random() > 0.5);
});
}, 2000);
}

onClose() {
this.unregister(this.resetListener);
clearInterval(this.systemStatusIntervalId);
}
}

const mockServer = new MyMockServer();

export function onReady() {
console.log('Starting mock Application Server');
mockServer.listen();
}
telestion.config.js
const path = require('path');

module.exports = {
plugins: [path.join(__dirname, 'src', 'plugins', 'mock-server.ts')]
};
src/widgets/my-new-widget/widget.tsx
import {
useBroadcast,
useChannelLatest
} from '@wuespace/telestion-client-core';

// [...]

function Indicator(props: { system: string }) {
const broadcast = useBroadcast('reset');
const status = useChannelLatest(`system-status/${props.system}`) ?? false;

return (
<Flex alignItems={'baseline'} gap={'size-200'}>
<StatusLight variant={status ? 'positive' : 'negative'}>
{props.system} {status ? 'Connected' : 'Disconnected'}
</StatusLight>
<ActionButton onPress={() => broadcast({ system: props.system })}>
Reset
</ActionButton>
</Flex>
);
}

Step 1: Add an event bus mock server for local testing

Install the @wuespace/vertx-mock-server package by running the following terminal in your client project folder:

npm i -D @wuespace/vertx-mock-server

Add a file for your new plugin called src/plugins/mock-server.ts with the following code:

src/plugins/mock-server.ts
import {
CallbackId,
MockServer,
OnClose,
OnInit
} from '@wuespace/vertx-mock-server';

class MyMockServer extends MockServer implements OnInit, OnClose {
private resetListener: CallbackId = 0;
private systemStatusIntervalId: any;

onInit() {
const systems = ['SAT A', 'SAT B', 'SAT C'];

this.resetListener = this.register('reset', raw => {
console.log('Received reset request:', raw.message);
});

this.systemStatusIntervalId = setInterval(() => {
systems.forEach(system => {
this.send(`system-status/${system}`, Math.random() > 0.5);
});
}, 2000);
}

onClose() {
this.unregister(this.resetListener);
clearInterval(this.systemStatusIntervalId);
}
}

const mockServer = new MyMockServer();

export function onReady() {
console.log('Starting mock Application Server');
mockServer.listen();
}

This code creates a plugin that gets called in the Electron thread once the Electron thread gets started (when running npm start). It creates a mock server which does two things:

  1. it registers a listener on the reset channel address and logs messages to that channel to the console (for your reset buttons)
  2. every two seconds, send a connected status (boolean) to the channel address system-status/[system] for your three systems

To load the plugin, you need to do one last step: in the client's root folder, there is a telestion.config.js that exports an empty object right now. Adjust it to include a list of plugins with your new plugin:

telestion.config.js
const path = require('path');

module.exports = {
plugins: [path.join(__dirname, 'src', 'plugins', 'mock-server.ts')]
};

When you now restart the npm start command (that is, re-run tc-cli start --electron), you can see the message Starting Mock Application Server in the terminal.

tip

When making changes to the telestion.config.js file or any file referenced by it (for example, plugins), you need to restart the tc-cli start command (or npm start) to use your changes.

After logging in as admin into http://localhost:9870/bridge (with an arbitrary password) in your Electron application, after a couple of seconds, you should see the connection status indicator in the navigation bar turning green and saying "Connected."

Step 2: Connect the widget's connection status indicators to the event bus

Now that you have a mock server publishing your system status every two seconds, you need to connect your widget to that data. Thankfully, this isn't too difficult with the help of the useChannelLatest hook exported by the @wuespace/telestion-client-core package.

The useChannelLatest() hook listens to messages on a specific channel address and always returns the latest status from there.

Adjust the <Indicator /> functional component to use the hook to listen to the system status on the system's system status channel and wire up your UI to use the new status:

src/widgets/my-new-widget/widget.tsx
// [...]
import { useChannelLatest } from '@wuespace/telestion-client-core';

// [...]

function Indicator(props: { system: string }) {
const status = useChannelLatest(`system-status/${props.system}`) ?? false;

return (
<Flex alignItems={'baseline'} gap={'size-200'}>
<StatusLight variant={status ? 'positive' : 'negative'}>
{props.system} {status ? 'Connected' : 'Disconnected'}
</StatusLight>
<ActionButton>Reset</ActionButton>
</Flex>
);
}

Take note of the ?? false. This defaults the value to false in case it's undefined. The default value is for when the widget hasn't received a system status yet (which happens when initially loading the widget).

After saving the file and reloading the client, you can see the status indicators change randomly every two seconds:

Link to

Step 3: Wiring up the "Reset" button

To make the "Reset" button broadcast a message to the reset channel (that you're listening for in your mock server) when pressed, you can use the useBroadcast() hook from the @wuespace/telestion-client-core package.

Like before, you can do this directly in your <Indicator /> component. The useBroadcast() hook takes the channel address as its first argument and returns a function to broadcast a message to that address.

Define a function broadcast that publishes to your reset channel. Use the Reset button's onPress event to call that function to broadcast a message to that channel. Pass an object containing details about the system into the message:

src/widgets/my-new-widget/widget.tsx
// [...]
import {
useBroadcast,
useChannelLatest
} from '@wuespace/telestion-client-core';

// [...]

function Indicator(props: { system: string }) {
const broadcast = useBroadcast('reset');
const status = useChannelLatest(`system-status/${props.system}`) ?? false;

return (
<Flex alignItems={'baseline'} gap={'size-200'}>
<StatusLight variant={status ? 'positive' : 'negative'}>
{props.system} {status ? 'Connected' : 'Disconnected'}
</StatusLight>
<ActionButton onPress={() => broadcast({ system: props.system })}>
Reset
</ActionButton>
</Flex>
);
}

When you reload your client application and press the reset buttons, you can see corresponding output in the terminal where you ran npm start:

Received reset request: { system: 'SAT C' }
Received reset request: { system: 'SAT B' }
Received reset request: { system: 'SAT A' }
Received reset request: { system: 'SAT B' }
Received reset request: { system: 'SAT C' }

Next steps

Congratulations, your widget is now fully wired up to the Event Bus. It's now up to the backend developers to create Verticles that connect the actual mission I/O interfaces to these Event Bus messages 😉.

But even more, you're now capable of wiring up any system to the event bus, meaning you can build any widget, you need using plain React for the UI and the APIs to connect to the event bus.

In the following tutorial, you'll learn how you can add configuration options to your widget to allow Ground Station operators (that is, your users) to re-configure your widget's behavior on the fly.

You should also familiarize yourself with the API Reference for the @wuespace/vertx-mock-server package to build more complex mock servers as well as the API Reference for the various Event Bus hooks in the @wuespace/telestion-client-core package:

@wuespace/vertx-mock-server API Reference »https://wuespace.github.io/telestion-client/@wuespace/vertx-mock-server/useBroadcast Hook API Reference »https://wuespace.github.io/telestion-client/@wuespace/telestion-client-core/#useBroadcastuseChannel Hook API Reference »https://wuespace.github.io/telestion-client/@wuespace/telestion-client-core/#useChanneluseChannelLatest Hook API Reference »https://wuespace.github.io/telestion-client/@wuespace/telestion-client-core/#useChannelLatestuseRequest Hook API Reference »https://wuespace.github.io/telestion-client/@wuespace/telestion-client-core/#useRequest