added openmct plugin and embedding
All checks were successful
Go / build (1.21) (push) Successful in 1m17s
Go / build (1.22) (push) Successful in 1m15s

This commit is contained in:
saji 2024-03-03 23:04:41 -06:00
parent 93be82f416
commit 8b8619dd8a
10 changed files with 9232 additions and 0 deletions

View file

@ -69,6 +69,10 @@ func extractLimitModifier(r *http.Request) (*LimitOffsetModifier, error) {
return nil, nil
}
type RouterMod func (chi.Router)
var RouterMods = []RouterMod{}
func TelemRouter(log *slog.Logger, broker *Broker, db *TelemDb) http.Handler {
r := chi.NewRouter()
@ -84,6 +88,9 @@ func TelemRouter(log *slog.Logger, broker *Broker, db *TelemDb) http.Handler {
r.Mount("/api/v1", apiV1(broker, db))
for _, mod := range RouterMods {
mod(r)
}
// To future residents - you can add new API calls/systems in /api/v2
// Don't break anything in api v1! keep legacy code working!
@ -301,3 +308,4 @@ func apiV1GetRecord(db *TelemDb) http.HandlerFunc {
func apiV1UpdateRecord(db *TelemDb) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {}
}

29
openmct.go Normal file
View file

@ -0,0 +1,29 @@
//go:build openmct
package gotelem
import (
"embed"
"io/fs"
"net/http"
"github.com/go-chi/chi/v5"
)
// this package provides a web router for the statif openmct build.
// it should only be included if the build has been run,
// to do so, run npm install and then npm run build.
//go:embed web/dist
var public embed.FS
func OpenMCTRouter(r chi.Router) {
// strip the subdirectory
pfs, _ := fs.Sub(public, "web/dist")
// default route.
r.Handle("/*", http.FileServerFS(pfs))
}
func init() {
RouterMods = append(RouterMods, OpenMCTRouter)
}

131
web/.gitignore vendored Normal file
View file

@ -0,0 +1,131 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

8710
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

30
web/package.json Normal file
View file

@ -0,0 +1,30 @@
{
"name": "g1_openmct",
"version": "1.0.0",
"description": "dev environment for openmct plugins for g1 strategy tool",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"serve": "webpack serve"
},
"author": "",
"license": "ISC",
"dependencies": {
"dotenv-webpack": "^8.0.1",
"lodash": "^4.17.21",
"openmct": "^2.0.5",
"socket.io": "^4.6.0",
"socket.io-client": "^4.6.0"
},
"devDependencies": {
"copy-webpack-plugin": "^12.0.2",
"eslint": "^8.28.0",
"html-webpack-plugin": "^5.5.0",
"ts-loader": "^9.4.2",
"typescript": "^4.9.5",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
}
}

12
web/src/app.js Normal file
View file

@ -0,0 +1,12 @@
import openmct from 'openmct';
import PhoebusPlugin from "./phoebusPlugin";
openmct.setAssetPath('openmct');
openmct.install(openmct.plugins.LocalStorage());
openmct.install(openmct.plugins.MyItems());
openmct.install(openmct.plugins.UTCTimeSystem());
openmct.time.clock('local', {start: -5 * 60 * 1000, end: 0});
openmct.time.timeSystem('utc');
openmct.install(openmct.plugins.Espresso());
openmct.install(PhoebusPlugin());
openmct.start();

11
web/src/index.html Normal file
View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Open MCT Tutorials</title>
<script src="openmct/openmct.js"></script>
</head>
<body>
<script>
</script>
</body>
</html>

243
web/src/phoebusPlugin.js Normal file
View file

@ -0,0 +1,243 @@
// import openmct from "openmct";
import {_} from "lodash";
import { io } from "socket.io-client";
// Pho
function PhoebusObjectProvider(identifier) {
// console.log("obj provider", identifier)
const board_regex = /^board\.([a-z-_]+)$/
const packet_regex = /^board\.([a-z-_]+)\.([a-z-_]+)$/
const data_field_regex = /^board\.([a-z-_]+)\.([a-z-_]+)\.([a-z-_]+)$/ // board.wavesculptor.wsl_heatsink_motor_temp.motor_temp
const data_field_regex_num = /^board\.([a-z-_]+)\.([a-z-_]+)\.([A-Za-z0-9]+)$/ // board.car_control.dashboard_system_timeout_test.flag_set0
const packet_regex_underscore_num = /^board\.([a-z-_]+)\.([a-z-_]+(_[A-Za-z0-9]+)+)$/ // board.steering.steering_press_count_1
const data_field_regex_underscore_num1 = /^board\.([a-z-_]+)\.([a-z-_]+(_[A-Za-z0-9]+)+)\.([A-Za-z0-9]+)$/ // board.steering.steering_press_count_1.button1
const data_field_regex_underscore_num2 = /^board\.([a-z-_]+)\.([a-z-_]+)\.([a-z-_]+(_[A-Za-z0-9]+)+)$/ // board.wavesculptor.wsr_status_information.error_flags_0
const data_field_regex_underscore_num3 = /^board\.([a-z-_]+)\.([a-z-_]+(_[A-Za-z0-9]+)+)\.([A-Za-z0-9]+(_[A-Za-z0-9]+)+)$/ // board.wavesculptor.wsr_15_165_voltage_rail.reference_165v
if (identifier.key === 'car') {
// get the car schema from the phoebus server
return Promise.resolve({
identifier: identifier,
name: "car!",
type: 'folder',
location: "ROOT"
})
} else if (board_regex.test(identifier.key)) {
// it's a board object. get the last word (ie board.bms -> bms)
// then use it to construct a get request
// console.log("doing board for ", identifier)
const board_name = identifier.key.match(board_regex)[1]
return fetch(`${process.env.VUE_APP_TELEM}/schema/` + board_name).then((resp) => {
return resp.json()
})
} else if (packet_regex.test(identifier.key)) {
const m = identifier.key.match(packet_regex)
const board_name = m[1]
const pkt_name = m[2]
return fetch([`${process.env.VUE_APP_TELEM}/schema`, board_name, pkt_name].join("/")).then((resp) => {
return resp.json()
})
} else if (data_field_regex.test(identifier.key)) {
const m = identifier.key.match(data_field_regex)
const board_name = m[1]
const pkt_name = m[2]
const df_name = m[3]
return fetch([`${process.env.VUE_APP_TELEM}/schema`, board_name, pkt_name, df_name].join("/")).then((resp) => {
return resp.json();
})
} else if (data_field_regex_num.test(identifier.key)) {
const m = identifier.key.match(data_field_regex_num)
const board_name = m[1]
const pkt_name = m[2]
const df_name = m[3]
return fetch([`${process.env.VUE_APP_TELEM}/schema`, board_name, pkt_name, df_name].join("/")).then((resp) => {
return resp.json();
})
} else if (packet_regex_underscore_num.test(identifier.key)) {
const m = identifier.key.match(packet_regex_underscore_num)
const board_name = m[1]
const pkt_name = m[2]
return fetch([`${process.env.VUE_APP_TELEM}/schema`, board_name, pkt_name].join("/")).then((resp) => {
return resp.json();
})
} else if (data_field_regex_underscore_num1.test(identifier.key)) {
const m = identifier.key.match(data_field_regex_underscore_num1)
const board_name = m[1]
const pkt_name = m[2]
const df_name = m[4]
return fetch([`${process.env.VUE_APP_TELEM}/schema`, board_name, pkt_name, df_name].join("/")).then((resp) => {
return resp.json();
})
} else if (data_field_regex_underscore_num2.test(identifier.key)) {
const m = identifier.key.match(data_field_regex_underscore_num2)
const board_name = m[1]
const pkt_name = m[2]
const df_name = m[3]
return fetch([`${process.env.VUE_APP_TELEM}/schema`, board_name, pkt_name, df_name].join("/")).then((resp) => {
return resp.json();
})
} else if (data_field_regex_underscore_num3.test(identifier.key)) {
const m = identifier.key.match(data_field_regex_underscore_num3)
const board_name = m[1]
const pkt_name = m[2]
const df_name = m[4]
return fetch([`${process.env.VUE_APP_TELEM}/schema`, board_name, pkt_name, df_name].join("/")).then((resp) => {
return resp.json();
})
}
console.error("bad! no match for object.")
return {}
}
function getSchema() {
return fetch(`${process.env.VUE_APP_TELEM}/schema`).then((resp) => {
const res = resp.json()
console.log("got schema", res)
return res
})
}
const CarCompositionProvider = {
appliesTo: function (domainObject) {
return domainObject.identifier.namespace === "umnsvp.phoebus" &&
domainObject.identifier.key === "car"
},
load: function (domainObject) {
return getSchema()
}
}
// the board composition provider takes a board type object, and gets all the telem objects for that board.
// it's like a folder.
const BoardCompositionProvider = {
appliesTo: function (domainObject) {
return domainObject.identifier.namespace === "umnsvp.phoebus" &&
domainObject.type === "umnsvp-board"
},
load: function (domainObject) {
// console.log("board comp provider for ", domainObject.packets)
return Promise.resolve(domainObject.packets)
// const board_name = domainObject.identifier.key.match(/^board\.([a-z-_]+)$/)
// return fetch("${process.env.VUE_APP_TELEM}/schema/" + board_name[1] + "/packets").then((resp) => {
// return resp.json()
// })
}
}
const PacketCompositionProvider = {
appliesTo: function (domainObject) {
return domainObject.identifier.namespace === "umnsvp.phoebus" && domainObject.type === "umnsvp-packet"
},
load: function (domainObject) {
// console.log("packet comp provider for ", domainObject.data_fields)
return Promise.resolve(domainObject.data_fields)
}
}
const PhoebusCompositionProviders = [
CarCompositionProvider,
BoardCompositionProvider,
PacketCompositionProvider
]
function PhoebusRealTime() {
const socket = io(`ws://localhost:${process.env.VUE_APP_TELEM_PORT}`)
let callback_list = {};
socket.on("packet", (data) => {
// if (data[0].packet_name === "wsl_bus_measurement") {
// console.log("got a wave meas packt", data[0])
// }
try {
const data_fields = data[0].data[0] // object that defines the measurement in each packet which are sent to openmct
// const key = ["board", data[0].board, data[0].packet_name].join(".")
console.log(Object.keys(data_fields))
console.log(callback_list)
// for (let i = 0; i < callback_list.length; i++) {
// if (Object.keys(data_fields).includes())
// }
Object.keys(data_fields).forEach((data_field) => {
let key = ["board", data[0].board, data[0].packet_name, data_field].join(".");
if (Object.keys(callback_list).includes(key)) {
let callBack = callback_list[key];
let timestamp = new Date().getTime();
// let realTimeData = data_fields;
let realTimeData = {}
realTimeData[data_field] = data_fields[data_field]
realTimeData.timestamp = timestamp;
realTimeData.id = key;
// console.log(realTimeData)
callBack(realTimeData);
}
})
} catch (e) {
// do nothing
console.warn("a", e)
}
})
const provider = {
supportsSubscribe(domainObject) {
console.log("checking if supports subscribe", domainObject)
return domainObject.type === "umnsvp-data"
},
subscribe(domainObject, callback) {
console.log("subscribing for ", domainObject)
console.log("callback", callback)
// register us in the callback list
callback_list[domainObject.identifier.key] = callback
return function unsubscribe() {
delete callback_list[domainObject.identifier.key]
}
}
}
return provider;
}
function PhoebusHistorical() {
return {
supportsRequest(domainObject) {
return domainObject.type === "umnsvp-packet"
},
request(domainObject, options) {
// return fetch()
}
}
}
function PhoebusPlugin() {
return function install(openmct) {
openmct.types.addType('umnsvp-board', {
name: "UMN SVP Board",
description: 'A board that sends telemetry packets/data',
createable: false,
cssClass: "icon-suitcase"
})
openmct.types.addType('umnsvp-packet', {
name: "UMN SVP Packet",
description: "A packet of telemetry from the car",
creatable: false,
cssClass: "icon-suitcase"
})
openmct.types.addType('umnsvp-data', {
name: "UMN SVP Data Field",
description: "A data field of a packet from the car",
creatable: false,
cssClass: "icon-telemetry"
})
openmct.objects.addRoot({
namespace: "umnsvp.phoebus",
key: 'car'
})
openmct.objects.addProvider('umnsvp.phoebus', {get:PhoebusObjectProvider})
openmct.telemetry.addProvider(PhoebusRealTime())
PhoebusCompositionProviders.forEach((provider) => {
openmct.composition.addProvider((provider))
})
console.log("hello~!")
}
}
export default PhoebusPlugin;

11
web/tsconfig.json Normal file
View file

@ -0,0 +1,11 @@
{
"compilerOptions": {
"baseUrl": "./src",
"target": "es6",
"checkJs": true,
"moduleResolution": "node",
"paths": {
"openmct": ["node_modules/openmct/dist/openmct.d.ts"]
}
}
}

47
web/webpack.config.js Normal file
View file

@ -0,0 +1,47 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyPlugin = require("copy-webpack-plugin");
const Dotenv = require('dotenv-webpack');
module.exports = {
entry: './src/app.js',
mode: "development",
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html',
filename: 'index.html',
}),
new Dotenv(),
new CopyPlugin({
patterns: [
{ from: "**/*", to: "openmct/", context: "node_modules/openmct/dist"},
]
})
],
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
externals: {
openmct: "openmct",
},
devServer: {
static: [{
// eslint-disable-next-line no-undef
directory: path.join(__dirname, '/node_modules/openmct/dist'),
publicPath: '/node_modules/openmct/dist'
}]
},
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
};