openmct historical plugin MVP
This commit is contained in:
parent
9ec01c39de
commit
d90d7a0af4
27
db.go
27
db.go
|
@ -163,7 +163,7 @@ func (tdb *TelemDb) GetPackets(ctx context.Context, filter BusEventFilter, optio
|
|||
// if we're filtering by names, add a where clause for it.
|
||||
if len(filter.Names) > 0 {
|
||||
names := strings.Join(filter.Names, ", ")
|
||||
qString := fmt.Sprintf("name IN (%s)", names)
|
||||
qString := fmt.Sprintf(`name IN ("%s")`, names)
|
||||
whereFrags = append(whereFrags, qString)
|
||||
}
|
||||
// TODO: identify if we need a special case for both time ranges
|
||||
|
@ -181,7 +181,7 @@ func (tdb *TelemDb) GetPackets(ctx context.Context, filter BusEventFilter, optio
|
|||
}
|
||||
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString("SELECT * from \"bus_events\"")
|
||||
sb.WriteString(`SELECT * from "bus_events"`)
|
||||
// construct the full statement.
|
||||
if len(whereFrags) > 0 {
|
||||
// use the where clauses.
|
||||
|
@ -231,8 +231,8 @@ func (tdb *TelemDb) GetPackets(ctx context.Context, filter BusEventFilter, optio
|
|||
// Datum is a single measurement - it is more granular than a packet.
|
||||
// the classic example is bms_measurement.current
|
||||
type Datum struct {
|
||||
Timestamp time.Time `db:"timestamp"`
|
||||
Value any `db:"val"`
|
||||
Timestamp time.Time `db:"timestamp" json:"ts"`
|
||||
Value any `db:"val" json:"val"`
|
||||
}
|
||||
|
||||
// GetValues queries the database for values in a given time range.
|
||||
|
@ -291,19 +291,18 @@ func (tdb *TelemDb) GetValues(ctx context.Context, bef BusEventFilter,
|
|||
return data, nil
|
||||
}
|
||||
|
||||
|
||||
// PacketDef is a database packet model
|
||||
type PacketDef struct {
|
||||
Name string
|
||||
Name string
|
||||
Description string
|
||||
Id int
|
||||
Id int
|
||||
}
|
||||
|
||||
type FieldDef struct {
|
||||
Name string
|
||||
Name string
|
||||
SubName string
|
||||
Packet string
|
||||
Type string
|
||||
Packet string
|
||||
Type string
|
||||
}
|
||||
|
||||
// PacketNotFoundError is when a matching packet cannot be found.
|
||||
|
@ -313,23 +312,19 @@ func (e *PacketNotFoundError) Error() string {
|
|||
return "packet not found: " + string(*e)
|
||||
}
|
||||
|
||||
|
||||
// GetPacketDefN retrieves a packet matching the given name, if it exists.
|
||||
// returns PacketNotFoundError if a matching packet could not be found.
|
||||
func (tdb *TelemDb) GetPacketDefN(name string) (*PacketDef, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetPacketDefF retrieves the parent packet for a given field.
|
||||
// GetPacketDefF retrieves the parent packet for a given field.
|
||||
// This function cannot return PacketNotFoundError since we have SQL FKs enforcing.
|
||||
func (tdb *TelemDb) GetPacketDefF(field FieldDef) (*PacketDef, error) {
|
||||
return nil, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
// GetFieldDefs returns the given fields for a given packet definition.
|
||||
func (tdb *TelemDb) GetFieldDefs(pkt PacketDef) ([]FieldDef, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
|
|
28
http.go
28
http.go
|
@ -39,7 +39,7 @@ func extractBusEventFilter(r *http.Request) (*BusEventFilter, error) {
|
|||
if err != nil {
|
||||
return bef, err
|
||||
}
|
||||
bef.TimerangeStart = t
|
||||
bef.TimerangeEnd = t
|
||||
}
|
||||
return bef, nil
|
||||
}
|
||||
|
@ -69,9 +69,9 @@ func extractLimitModifier(r *http.Request) (*LimitOffsetModifier, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
type RouterMod func (chi.Router)
|
||||
var RouterMods = []RouterMod{}
|
||||
type RouterMod func(chi.Router)
|
||||
|
||||
var RouterMods = []RouterMod{}
|
||||
|
||||
func TelemRouter(log *slog.Logger, broker *Broker, db *TelemDb) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
|
@ -222,17 +222,12 @@ func apiV1GetPackets(tdb *TelemDb) http.HandlerFunc {
|
|||
var res []skylab.BusEvent
|
||||
if lim != nil {
|
||||
res, err = tdb.GetPackets(r.Context(), *bef, lim)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
res, err = tdb.GetPackets(r.Context(), *bef)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
b, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
|
@ -269,7 +264,13 @@ func apiV1GetValues(db *TelemDb) http.HandlerFunc {
|
|||
// override the bus event filter name option
|
||||
bef.Names = []string{name}
|
||||
|
||||
res, err := db.GetValues(r.Context(), *bef, field, lim)
|
||||
var res []Datum
|
||||
// make the call, skip the limit modifier if it's nil.
|
||||
if lim == nil {
|
||||
res, err = db.GetValues(r.Context(), *bef, field)
|
||||
} else {
|
||||
res, err = db.GetValues(r.Context(), *bef, field, lim)
|
||||
}
|
||||
if err != nil {
|
||||
// 500 server error:
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
@ -309,4 +310,3 @@ func apiV1GetRecord(db *TelemDb) http.HandlerFunc {
|
|||
func apiV1UpdateRecord(db *TelemDb) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {}
|
||||
}
|
||||
|
||||
|
|
156
web/src/app.js
156
web/src/app.js
|
@ -3,17 +3,171 @@ import PhoebusPlugin from "./phoebusPlugin";
|
|||
openmct.setAssetPath('openmct');
|
||||
openmct.install(openmct.plugins.LocalStorage());
|
||||
openmct.install(openmct.plugins.MyItems());
|
||||
openmct.install(openmct.plugins.Timeline());
|
||||
openmct.install(openmct.plugins.UTCTimeSystem());
|
||||
openmct.install(openmct.plugins.Clock({ enableClockIndicator: true }));
|
||||
openmct.install(openmct.plugins.Timer());
|
||||
openmct.install(openmct.plugins.Timelist());
|
||||
openmct.time.clock('local', {start: -5 * 60 * 1000, end: 0});
|
||||
openmct.time.timeSystem('utc');
|
||||
openmct.install(openmct.plugins.Espresso());
|
||||
|
||||
openmct.install(
|
||||
openmct.plugins.Conductor({
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'Fixed',
|
||||
timeSystem: 'utc',
|
||||
bounds: {
|
||||
start: Date.now() - 30000000,
|
||||
end: Date.now()
|
||||
},
|
||||
|
||||
},
|
||||
{
|
||||
name: 'Realtime',
|
||||
timeSystem: 'utc',
|
||||
clock: 'local',
|
||||
clockOffsets: {
|
||||
start: -30000000,
|
||||
end: 30000
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
|
||||
if (process.env.BASE_URL) {
|
||||
console.log("got a thing")
|
||||
console.log(process.env.BASE_URL)
|
||||
}
|
||||
function GotelemPlugin() {
|
||||
|
||||
var schemaCached = null;
|
||||
function getSchema() {
|
||||
if (schemaCached === null) {
|
||||
return fetch(`${process.env.BASE_URL}/api/v1/schema`).then((resp) => {
|
||||
const res = resp.json()
|
||||
console.log("got schema, caching", res);
|
||||
schemaCached = res
|
||||
return res
|
||||
})
|
||||
}
|
||||
return Promise.resolve(schemaCached)
|
||||
}
|
||||
|
||||
const objectProvider = {
|
||||
get: function (id) {
|
||||
return getSchema().then((schema) => {
|
||||
if (id.key === "car") {
|
||||
const comp = schema.packets.map((x) => {
|
||||
return {
|
||||
key: x.name,
|
||||
namespace: "umnsvp"
|
||||
}
|
||||
})
|
||||
return {
|
||||
identifier: id,
|
||||
name: "the solar car",
|
||||
type: 'folder',
|
||||
location: 'ROOT',
|
||||
composition: comp
|
||||
}
|
||||
}
|
||||
var pkt = schema.packets.find((x) => x.name === id.key)
|
||||
if (pkt) {
|
||||
// if the key matches one of the packet names,
|
||||
// we know it's a field.
|
||||
const comp = pkt.data.map((field) => {
|
||||
return {
|
||||
// we have to do this since
|
||||
// we can't get the packet name otherwise.
|
||||
key: `${pkt.name}.${field.name}`,
|
||||
namespace: "umnsvp"
|
||||
}
|
||||
})
|
||||
return {
|
||||
identifier: id,
|
||||
name: pkt.name,
|
||||
type: 'folder',
|
||||
composition: comp
|
||||
}
|
||||
}
|
||||
// at this point it's definitely a field aka umnsvp-datum
|
||||
var [pktName, fieldName] = id.key.split('.')
|
||||
return {
|
||||
identifier: id,
|
||||
name: fieldName,
|
||||
type: 'umnsvp-datum',
|
||||
telemetry: {
|
||||
values: [
|
||||
{
|
||||
key: "value",
|
||||
source: "val",
|
||||
name: "Value",
|
||||
"format": "float",
|
||||
hints: {
|
||||
range: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "utc",
|
||||
source: "ts",
|
||||
name: "Timestamp",
|
||||
format: "utc",
|
||||
hints: {
|
||||
domain: 1
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const TelemHistoryProvider = {
|
||||
supportsRequest: function (dObj) {
|
||||
return dObj.type === 'umnsvp-datum'
|
||||
},
|
||||
request: function (dObj, opt) {
|
||||
var [pktName, fieldName] = dObj.identifier.key.split('.')
|
||||
var url = `${process.env.BASE_URL}/api/v1/packets/${pktName}/${fieldName}?`
|
||||
var params = new URLSearchParams({
|
||||
start: new Date(opt.start).toISOString(),
|
||||
end: new Date(opt.end).toISOString(),
|
||||
})
|
||||
return fetch(url + params).then((resp) => {
|
||||
return resp.json()
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function GotelemPlugin() {
|
||||
return function install(openmct) {
|
||||
|
||||
openmct.types.addType('umnsvp-datum', {
|
||||
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",
|
||||
key: 'car'
|
||||
}, openmct.priority.HIGH)
|
||||
openmct.objects.addProvider('umnsvp', objectProvider);
|
||||
openmct.telemetry.addProvider(TelemHistoryProvider)
|
||||
}
|
||||
}
|
||||
|
||||
openmct.install(GotelemPlugin())
|
||||
|
||||
openmct.start();
|
||||
|
|
Loading…
Reference in a new issue