Skip to main content

Connecting the widget with the Event Bus

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