Connecting the widget with the Event Bus
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.
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:
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();
}
const path = require('path');
module.exports = {
plugins: [path.join(__dirname, 'src', 'plugins', 'mock-server.ts')]
};
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:
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:
- it registers a listener on the
reset
channel address and logs messages to that channel to the console (for your reset buttons) - every two seconds, send a connected status (
boolean
) to the channel addresssystem-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:
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.
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:
// [...]
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:

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:
// [...]
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