Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
dmitrydwhite committed Aug 27, 2021
0 parents commit 6e40cba
Show file tree
Hide file tree
Showing 23 changed files with 3,840 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
faker_satellite/file_lib/**.*
faker_satellite/for_downlink/**.*
run/downlinked_files/for_downlink/**.*
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Demo Gateway in NodeJS
A Gateway is the translation layer between Major Tom's Mission Control and your Satellite. This repo contains an example Gateway in JavaScript, and can be run locally.

## Background
Gateways are asynchronous by nature -- the primary interface is a bi-directional websocket. Actions taken by the operator are sent from Major Tom to your Gateway as messages. The underlying library then executes methods based on the type of message. NodeJS is uniquely suited to handle a Gateway's asynchronous behavior using its trademark Event and Stream paradigms.

## Gateway Package
This demo gateway uses the [Major Tom Gateway Package](https://npmjs.com/package/majortom-gateway) from npm. If you are looking to use this gateway as an example to write your own, you'll use that package to handle messages to and from Major Tom.


## Local Setup
1. Make sure you have git and [NodeJS & npm](https://nodejs.org) installed
2. Clone this repo with git
3. In a terminal window, navigate into the demo folder and install the project dependencies from npm
```sh
$ cd mt-demo-js
$ npm install
```

## Major Tom Setup
Before running this app locally, you'll need to make a Gateway in Major Tom! Once you've received your login credentials for Major Tom, make a new mission, and you'll be prompted to add a Gateway to that mission.

Once you add the Gateway, you'll need the **Authentication Token** to connect the demo gateway. You can find it on the Gateway Page:
![Gateway Page](assets/gateway_page.png "Gateway Page in Major Tom")

## Connect the Gateway
1. You'll need to edit the file found at `mt-demo-js/connect/connection.json` in the following way:
```json
{
"gatewayToken": "<YOUR-GATEWAY-TOKEN>",
"host": "app.majortom.cloud" // Or your Major Tom url host location
}
```
2. From a terminal window in the `mt-demo-js` directory, start the app using the start script:
```sh
$ npm run start
```
3. You should see the intro text in the window, and then a JSON message from Major Tom:
```sh
{"type":"hello","hello":{"mission":"YOUR-MISSION-NAME"}}
```
4. You'll also see a log message showing that the example sat has sent Major Tom a copy of its command definitions. This will serve to automatically create the satellite in Major Tom if it doesn't exist yet, and set it up with the commands it recognizes.

### Note on Major Tom Deployment Environments
If you have Basic Authentication enabled on your deployment (usually true if you're accessing it over anything other than `app.majortom.cloud`), you'll need to enter those credentials in `mt-demo-js/connect/connection.json` to connect.

Enter basicauth login credentials for your Major Tom deployment (if it’s active) in the form `"basicAuth": "<username>:<password>@"`, for example: `"basicAuth": "kubos:password123@"`.

If you are running the on-prem version of Major Tom, you'll need to include the field `"http": true` in `mt-demo-js/connect/connection.json`, as we currently do not support https for on prem.

## What does this Demo do?
Now that you've connected the gateway, it will automatically create a new satellite (as noted above) and load command definitions for it.

You can now issue those commands to the satellite through the connected Gateway, which accomplish a wide variety of simulated tasks. Open each command and read its description to understand what it does!

To find these commands, go to Major Tom under the mission you originally created and look up JS Oddity in the Satellites Menu:

![Satellites Menu](assets/satellites_menu.png "Satellites Menu in Major Tom")

Clicking on it will take you to its commanding page, and you can fire away!

## What's next?
### Set up your Mission Dashboard
The Mission Dashboard allows you to monitor and control everything on the Mission. Add cards and play with their settings to see what you can do!

### Integrate Your System
Now that you understand how the integration works, try augmenting the Demo Gateway to actually communicate with your hardware and software! Then you can begin controlling and monitoring your own spacecraft. If you want to get a better sense of how easy it is to build a Gateway in NodeJS, start by looking at the source code in `mt-demo-js/index.js`!
Binary file added assets/gateway_page.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/satellites_menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 47 additions & 0 deletions connect/command_definitions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"type": "command_definitions_update",
"command_definitions": {
"system": "JS Oddity",
"definitions": {
"ping": {
"display_name": "1-ping",
"description": "A simple ping. The Gateway should contact the satellite to return a pong.",
"fields": []
},
"telemetry": {
"display_name": "2-start telemetry beacon",
"description": "Commands the spacecraft to beacon Health and Status Telemetry for 3 minutes. After executing this command, you can see the telemetry by navigating to the System Telemetry tab or the Analytics Page.",
"fields": []
},
"update_file_list": {
"display_name": "3-update file list",
"description": "Gets the latest list of downloadable files from the spacecraft.",
"fields": []
},
"uplink_file": {
"display_name": "4-uplink file",
"description": "Uplink a file from your computer to the spacecraft.",
"fields": [
{ "name": "gateway_download_path", "type": "string" }
]
},
"downlink_file": {
"display_name": "5-downlink file",
"description": "Downlink an image from the Spacecraft.",
"fields": [
{ "name": "filename", "type": "string" }
]
},
"connect": {
"display_name": "6-establish rf lock",
"description": "Points antennas and starts broadcasting carrier signal to establish RF lock with the spacecraft.",
"fields": []
},
"safemode": {
"display_name": "7-safemode command",
"description": "Commands the spacecraft into safemode for 3 minutes, shutting down telemetry and all non-essential systems.",
"fields": []
}
}
}
}
4 changes: 4 additions & 0 deletions connect/connection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"gatewayToken": "",
"host": "app.majortom.cloud"
}
27 changes: 27 additions & 0 deletions faker_antenna/broadcastCarrierSignal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const broadcastCarrierSignal = ({ updateFn }) => new Promise((resolve) => {
const AWAITING_ACK = 'awaiting satellite ack';
const statusPhases = [
'checking for sideband traffic',
'broadcasting callsign and net clear',
'initiating carrier frequency',
AWAITING_ACK,
];
const maxWait = 6000;
const intervalWait = 1000;
let waited = 0;
let idx = 0;

const bcastInt = setInterval(() => {
if (waited >= maxWait) {
clearInterval(bcastInt);
updateFn(100, 'awaiting satellite ack');
resolve();
} else {
updateFn((idx + 1) * 10, statusPhases[idx] || AWAITING_ACK);
idx += 1;
waited += intervalWait;
}
}, intervalWait);
});

module.exports = broadcastCarrierSignal;
19 changes: 19 additions & 0 deletions faker_antenna/orientAntenna.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const getRandomInt = require("./utils/getRandomInt");

const orientAntenna = ({ updateFn }) => new Promise((resolve) => {
const totalToRotate = getRandomInt(0, 268);
let done = 0;

const rotInterval = setInterval(() => {
if (done >= totalToRotate) {
clearInterval(rotInterval);
updateFn(totalToRotate, 0);
resolve();
} else {
done += 3;
updateFn(done, totalToRotate - done);
}
}, 200);
});

module.exports = orientAntenna;
41 changes: 41 additions & 0 deletions faker_antenna/prepGroundHardware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* @callback UpdateFunction
* @param {Number} percentComplete The percentage of the task that is completed
* @param {String} phaseLabel A string identifying the phase of the task
* @returns
*/

/**
*
* @param {Object} param0 params
* @param {UpdateFunction} param0.updateFn Will be called as the task updates
* @returns {Promise}
*/
const prepGroundHardware = ({ updateFn }) => new Promise((resolve, reject) => {
const statusPhases = [
'actuator power cycle',
'gimballing hardware',
'hardware clearance check',
'radio power cycle',
'digesting tle',
'computing pass angles',
'translating pass trajectory',
'calibrating radio oscillator',
'refining radio frequency',
'finishing pass prep',
];
let i = 0;

const calcInterval = setInterval(() => {
if (i >= statusPhases.length) {
clearInterval(calcInterval);
updateFn(100, 'done');
resolve();
} else {
updateFn(Math.floor((i / (statusPhases.length - 1)) * 100) , statusPhases[i]);
i += 1;
}
}, 1700);
});

module.exports = prepGroundHardware;
62 changes: 62 additions & 0 deletions faker_antenna/syncCarrierSignalWithSat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const syncCarrierSignalWithSat = ({ updateFn, satellite }) => new Promise((resolve, reject) => {
const maxAttempts = 10;
const checksumWords = [
'ALBUM',
'BANTU',
'COMET',
'DOETH',
'EARTH',
'FORCE',
'GALAX',
'HEXAD',
'INGOT',
'JUMBO',
];
let didReceiveAck = false;
let attempts = 0;
let pingInterval;
let checkWord;

const carrierPingListener = ({ type, word }) => {
if (!didReceiveAck) {
updateFn('acked_by_system');
didReceiveAck = true;
}

if (type === 'checksum_pong') {
updateFn('downlinking_from_system', attempts, maxAttempts);

if (word === checkWord) {
clearInterval(pingInterval);
satellite.off('message', carrierPingListener);
resolve(`${word}${checkWord}`);
}
}
};

updateFn('uplinking_to_system');
checkWord = checksumWords[attempts];
attempts += 1;
satellite.on('message', carrierPingListener);
satellite.send({ type: 'checksum_ping', word: checkWord });
updateFn('transmitted_to_system');

pingInterval = setInterval(() => {
if (attempts >= maxAttempts) {
clearInterval(pingInterval);
satellite.off('message', carrierPingListener);

reject(new Error(
didReceiveAck
? 'Contact made with satellite but could not sync carriers'
: `No contact with satellite after ${maxAttempts} seconds`
));
} else {
checkWord = checksumWords[attempts];
attempts += 1;
satellite.send({ type: 'checksum_ping', word: checkWord });
}
}, 1000);
});

module.exports = syncCarrierSignalWithSat;
6 changes: 6 additions & 0 deletions faker_antenna/utils/getRandomInt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = (min, max) => {
min = Math.ceil(min);
max = Math.floor(max);

return Math.floor(Math.random() * (max - min + 1) + min);
};
9 changes: 9 additions & 0 deletions faker_antenna/validateChecksum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const validateChecksum = ({ updateFn }) => new Promise(resolve => {
updateFn('calculating checksum');
setTimeout(() => {
updateFn('resolved check value');
resolve(`VALID::${(Math.random() * Date.now()).toFixed(4)}`);
}, 1500);
});

module.exports = validateChecksum;
3 changes: 3 additions & 0 deletions faker_satellite/file_lib/file_724334
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This is a test
of the file uplink system
-- Sincerely, Dmitry
Loading

0 comments on commit 6e40cba

Please sign in to comment.