diff --git a/db.go b/db.go index b46c93b..039641d 100644 --- a/db.go +++ b/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 } - - diff --git a/http.go b/http.go index 112cc66..43e9b76 100644 --- a/http.go +++ b/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) {} } - diff --git a/web/src/app.js b/web/src/app.js index 9dab67d..bae3947 100644 --- a/web/src/app.js +++ b/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();