openmct historical plugin MVP
All checks were successful
Go / build (1.21) (push) Successful in 1m16s
Go / build (1.22) (push) Successful in 1m15s

This commit is contained in:
saji 2024-03-05 09:49:08 -06:00
parent 9ec01c39de
commit d90d7a0af4
3 changed files with 180 additions and 31 deletions

27
db.go
View file

@ -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
View file

@ -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) {}
}

View file

@ -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();