;docformat='rst' ;+ ; Author: Chris Pankratz, Bill Barrett ; ; PURPOSE: ; Retrieve telemetry data for the New Horizons AIM instrument. Multiple ; telemetry items may be retrieved with a single call to this function. ; ; INPUT PARAMETERS: ; startTime - ; The lower time range for which data will be returned. ; May be specified as julian days or gps microseconds. ; ; stopTime - ; The upper time range for which data will be returned ; May be specified as julian days or gps microseconds. ; ; RETURN VALUE ; The telemetry data retrieved. For example, here is how one ; might "unwrap" the return data structure. There will be ; one data element per telemetry item requested. ; ; Also, the DN field will be a two dimensional variable. The ; first dimension respresents the maximum length of the ; integer data array. The second dimension is equal to the ; number of timetags returned. ; ; IDL> help, data ; DATA STRUCT = -> Array[1] ; ; IDL> help, /st, data ; ** Structure <2173840>, 1 tags, length=4, data length=4, refs=1: ; AIM$FPGA_TEMP POINTER ; ; IDL> help, *data.(0), /st ; ** Structure <21b0a00>, 2 tags, length=2046724, data length=2046724, refs=1: ; HOUSEKEEPING STRUCT -> Array[85280] ; N_HOUSEKEEPING LONG 85280 ; ; IDL> help, /st, (*data.(0)).housekeeping ; ** Structure <3e4938>, 3 tags, length=456, refs=3: ; TIMETAG DOUBLE Array[19] ; DN DOUBLE Array[19] ; EU DOUBLE Array[19] ; ; ; ; Keyword Arguments: ; ; SID - The system ID possible values are ; 1 - sc_flight ; 2 - sc_test ; 3 - flatsat ; 4 - isim ; default value = 2 ; ; SCT_Cycle - This is used to differentiate between various sets of test ; data when the clock is rolled back to zero. The default value ; is 1. ; ; ; tlmId - a numerical (integer) id of the telemetry item desired. ; Note that this value must match an entry in the ; SORCE_CT TelemetryItemDefinition table. Supersedes any ; values for externalElement or itemName. ; An array of tlmId's is permitted. ; ; externalElement - ; The external element string from which the telemetry item ; originates. Must be used in conjunction with the itemName ; keyword, and the supplied value must exist in the ; TelemetryItemDefinition database table. ; ; An array is permitted, but must be the same length ; as itemName. ; ; itemName - ; The item name of the telemetry item requested. Must be used with ; the externalElement keyword. ; ; An array is permitted, but must be the same length ; as externalElement. ; ; gps ; If set, indicates that input times are to be interpreted as ; microseconds since Jan 6, 1980 midnight UT. (default) ; ; julianDays ; If set, indicates that the input times are to be interpreted as ; julian day numbers. ; ; info ; If specified with a named variable, metadata describing the ; returned telemetry items will be returned as a single IDL structure ; with one structure field per telemetry item requested. ; IDL> help, /st, info ; ** Structure , 1 tags, length=56, refs=1: ; SOLSTICE_A$GRAT_POS ; STRUCT = -> Array[1] ; IDL> help, /st, info.(0) ; ** Structure , 7 tags, length=56, refs=2: ; TYPE STRING 'U' ; MNEMONIC_NAME STRING 'solstice_a$grat_pos' ; LIMITS STRING 'No Limits' ; TIME_UNITS STRING 'microseconds since GPS epoch' ; UNITS STRING 'any' ; ANALOG_CONVERSION ; STRING = 'No conversions' ; DESCRIPTION STRING 'solstice_a grating position.' ; ; verbose - ; Set this keyword to see progress messages as telemetry are retrieved ; ; Return Data: ; This function returns an array of IDL structures that contain ; the telemetry data requested. Note that the timetags present in ; the return data are always "gps microseconds", the number of ; microseconds elapsed since January 6, 1980. There are many time ; conversions available to convert these into other time formats. ; ; Example Usage: ; start_time = 827344382066688 ; stop_time = 827344449175552 ; data = get_aim_hk_telemetry(start_time, stop_time, /gps, $ ; externalElement='ac', itemName='adpid') ; hk = (*data.aim$fpga_temp).housekeeping ; ; convert timetags to julian days ; jd = gps2jd(hk.timetag / 1.d6) ; ; setup time scale for plot ; date_label = label_date(DATE_FORMAT=['%M %D!C%H:%I']) ; xtickformat='LABEL_DATE', xtickunits=['Time'] ; plot, jd, hk.eu, xtickformat='LABEL_DATE', xtitle='Date', psym=3 ; ;- ; ------------------------------------------------------------------------ ; Obtain data from the TelemetryItemDefinition table given a ; telemetry id. ; ------------------------------------------------------------------------ pro get_tlmid_item_definition, tlmId, tlmItem_rec ; Keep a cache of the telemetry item definitions retrieved from the database, ; so that it is not necessary to continually repeat the same database query. common cached_tlm_item_records, $ tlm_item_record_hash if ~keyword_set(tlm_item_record_hash) then tlm_item_record_hash = hash() ; The cached telemetry item definitions are stored as a hash where the telemetry id ; is the key and the definitions are the values keys = tlm_item_record_hash.keys() ; If there is a cached one on hand then use it rather than querying the database if keys.where(tlmId) ne !NULL then begin tlmItem_rec = tlm_item_record_hash[tlmId] if get_debug_level() ge 7 then $ print, get_routine_name() + ' using cached tlm id: ' + strtrim(string(tlmId), 2) endif else begin ;Query the TelemetryItemDefinition table for the desired tlmId sql = "select * from AIM_CT_SC.TelemetryItemDefinition " + $ "where tlmId = " + STRTRIM(string(tlmId), 2) get_database_login, 'AIM_CT_SC', user=user, password=password, url=url query_database, sql, tlmItem_rec, nrows, $ user=user, $ password=password, $ dburl = url, /dbConnect ; If no data was returned then flag the data as bad. ; Otherwise store this data in the hash if (nrows eq 0) then tlmItem_rec = -1 else $ tlm_item_record_hash[tlmId] = tlmItem_rec endelse end ; ------------------------------------------------------------------------ ; Query either the TelemetryLimits or TelemetryStateConversion table to obtain data, if present ; ------------------------------------------------------------------------ pro get_state_or_limits_or_analogconv, tlmId, valid, data, table_name ; Keep a cache of the telemetry item limits retrieved from the database, ; so that it is not necessary to continually repeat the same database query. common cached_tlm_item_limits, $ tlm_item_limit_hash if ~keyword_set(tlm_item_limit_hash) then tlm_item_limit_hash = hash() ; The cached telemetry item limit are stored as a hash where the table name ; combined with telemetry id is the key and the limits are the values keys = tlm_item_limit_hash.keys() key = strtrim(table_name, 2) + '_' + strtrim(string(tlmId), 2) ; If there is a cached one on hand then use it rather than querying the database if keys.where(key) ne !NULL then begin data = tlm_item_limit_hash[key] if get_debug_level() ge 7 then $ print, get_routine_name() + ' using cached tlm limit from: ' + key endif else begin sql = 'select * from AIM_CT_SC.' + table_name + ' where tlmId = ' + STRTRIM(string(tlmId), 2) get_database_login, 'AIM_CT_SC', user=user, password=password, url=url query_database, sql, data, nrows, $ dburl=url, $ user=user, $ password=password, /dbConnect ; If no data is returned, store the scalar value 0 in the hash; ; otherwise keep what the query has returned tlm_item_limit_hash[key] = nrows le 0 ? 0 : data endelse ; The data is valid if the query returns a structure valid = size(data, /type) eq 8 end ; ------------------------------------------------------------------------ ; For a telemetry item that has limits, this procedure ; fills the telemetry limits record to be returned to the user ; ------------------------------------------------------------------------ pro fill_tlmlimits, tlmLimits_data, hasLimits, limits limits = hasLimits eq 0 ? 'No Limits' : { hasLimits:hasLimits, $ redLow:tlmLimits_data.redLow, $ redHigh:tlmLimits_data.redHigh, $ yellowLow:tlmLimits_data.yellowLow, $ yellowHigh:tlmLimits_data.yellowHigh, $ deltaLimit:tlmLimits_data.deltaLimit} end ; ------------------------------------------------------------------------ ; Once the state conversion information is retrieved, theis ; procedure is used to fill the state information record ; ------------------------------------------------------------------------ pro fill_state_info, stateConv_data, hasStateConversions, tlmItem_data, $ limits, hasLimits, mnemonic, info_rec if (hasStateConversions) then begin info_rec = { type:tlmItem_data.dataType, $ mnemonic_name:mnemonic, $ time_units:'microseconds since GPS epoch', $ limits:limits, $ hasLimits:hasLimits, $ hasStateConversions:hasStateConversions, $ state_dn:stateConv_data.value, $ desirability:stateConv_data.desirability, $ states:stateConv_data.state, $ description:tlmItem_data.description} endif else begin info_rec = { type:tlmItem_data.dataType, $ mnemonic_name:mnemonic, $ time_units:'microseconds since GPS epoch', $ limits:limits, $ hasLimits:hasLimits, $ hasStateConversions:hasStateConversions, $ state_dn:0, $ desirability:'', $;stateConv_data.desirability, $ states:'no conversion', $ description:tlmItem_data.description} endelse end ; ------------------------------------------------------------------------ ; If the analog telemetry item has analog conversions, ; The "analog_conversions" procedure is used to create the ; analog_conversion info structure for return to the caller ; ------------------------------------------------------------------------ pro fill_analog_conversions, analogConv_data, hasAnalogConversions, analog_conversion if (hasAnalogConversions) then begin analog_conversion = {segment:analogConv_data.segmentNumber, $ low_dn:analogConv_data.lowValue, $ high_dn:analogConv_data.highValue, $ type:analogConv_data.conversionType, $ coeff:[analogConv_data.c0, analogConv_data.c1, $ analogConv_data.c2, analogConv_data.c3, $ analogConv_data.c4, analogConv_data.c5, $ analogConv_data.c6, analogConv_data.c7]} endif else begin analog_conversion = 'No conversions' endelse end ; ------------------------------------------------------------------------ ; The "fill_analog_info" procedure is used to fill the analog info record. ; ------------------------------------------------------------------------ pro fill_analog_info, tlmItem_data, hasAnalogConversions, $ analog_conversion, limits, hasLimits, $ mnemonic, info_rec info_rec = { type:tlmItem_data.dataType, $ mnemonic_name:mnemonic, $ limits:limits, $ hasLimits:hasLimits, $ time_units:'microseconds since GPS epoch', $ units:tlmItem_data.euUnits, $ hasAnalogConversions:hasAnalogConversions, $ analog_conversion:analog_conversion, $ description:tlmItem_data.description} end ; ------------------------------------------------------------------------ ; Use the state conversion to convert the dn values to corresponding ; state names. ; ------------------------------------------------------------------------ pro apply_state_conversions, state_data, stateConv_data, hasStateConversions, state if (hasStateConversions) then begin state = STRARR(N_ELEMENTS(state_data.dn)) for i = 0, N_ELEMENTS(stateConv_data) -1 do begin pick = WHERE(state_data.dn eq stateConv_data[i].value, num) if (num gt 0) then begin state[pick] = stateConv_data[i].state endif endfor endif else begin ; fill state with null strings state = REPLICATE('', N_ELEMENTS(state_data.dn)) endelse end ; ------------------------------------------------------------------------ ; Once the data for the state telemetry item is retrieved, ; the procedure "build_state_structure" is used to fill the state ; data record ; ------------------------------------------------------------------------ pro build_state_structure, state_data, state, data_rec data_rec = { timetag:state_data.timetag, $ dn:state_data.dn, $ state:state} end ; ------------------------------------------------------------------------ ; Query the database for analog or discrete conversion data ; ------------------------------------------------------------------------ pro get_l1_or_l1a_data, database, table, tlmId, start_time, stop_time, SID, SCT_Cycle, data, nrows ;Query the TMdiscrete table for the desired tlmId sql = "select Value dn, Value eu, " + $ " SCT_VTCW timetag from " + database + "." + table + " where " + $ " SID = " + STRTRIM(string(SID), 2) + " and " + $ " TMID = " + STRTRIM(string(tlmId), 2) + " and " + $ " SCT_Cycle = " + STRTRIM(string(SCT_Cycle), 2) + " and " + $ " SCT_VTCW between " + STRTRIM(string(start_time), 2) + $ " and " + STRTRIM(string(stop_time), 2) + " order by SCT_VTCW" get_database_login, database, user=user, password=password, url=url query_database, sql, data, nrows, $ dburl=url, $ user=user, $ pass=password, /dbconnect end ; ----------------------------------------------------------------------- ; Use the analog_conversion to convert the analog dn values to ; engineering units ; NOTE: Needs to handle all conversion types, not just polynomials ; ------------------------------------------------------------------------ pro apply_analog_conversions, analog_data, $ analogConv_data, $ hasAnalogConversions, $ eu_value if (hasAnalogConversions) then begin ; conversion is necessary, so proceed eu_value = DBLARR(N_ELEMENTS(analog_data)) type = analogConv_data.conversionType ; tye type of conversion ; if there is only one polynomial segment, perform a single conversion, ; otherwise perform all require conversions, for each segment if(N_ELEMENTS(analogConv_data) eq 1) then begin ; one segment only coeffs = [analogConv_data.c0, analogConv_data.c1, analogConv_data.c2, $ analogConv_data.c3, analogConv_data.c4, analogConv_data.c5, $ analogConv_data.c6, analogConv_data.c7] eu_value = peval(coeffs, analog_data.dn) endif else begin for j = 0, N_ELEMENTS(analogConv_data) -1 do begin coeffs = [analogConv_data[j].c0, $ analogConv_data[j].c1, $ analogConv_data[j].c2, $ analogConv_data[j].c3, $ analogConv_data[j].c4, $ analogConv_data[j].c5, $ analogConv_data[j].c6, $ analogConv_data[j].c7] pick = where (analog_data.dn ge analogConv_data[j].lowValue and $ analog_data.dn le analogConv_data[j].highValue, num) if num gt 0 then begin analog_data_tmp = analog_data[pick] eu_value[pick] = peval(coeffs, analog_data_tmp.dn) endif endfor endelse analog_data.eu = TEMPORARY(eu_value) endif else begin ; no conversion necessary analog_data.eu = DOUBLE(analog_data.dn) endelse end ; ------------------------------------------------------------------------ ; ------------------------------------------------------------------------ ; The main procedure that is called by the user is ; "get_sorce_telemetry" ; ------------------------------------------------------------------------ ; ------------------------------------------------------------------------ function get_aim_hk_telemetry, startTime, stopTime, $ SID = SID, $ SCT_Cycle = SCT_Cycle, $ tlmId = tlmId, $ externalElement = externalElement, $ itemName = itemName, $ info = info, $ gps = gps, $ ; indicates that input times are GPS microseconds julianDays = julianDays, $ ; interpret input times as julian day numbers help = help, $ verbose = verbose if N_ELEMENTS(SID) eq 0 then SID = 1 if N_ELEMENTS(SCT_Cycle) eq 0 then SCT_Cycle = 1 if (N_ELEMENTS(verbose) eq 0) then verbose = 0 else verbose=KEYWORD_SET(verbose) ; print usage information if help is set or insufficient arguments are present if (KEYWORD_SET(help) or N_PARAMS() eq 0) then begin DOC_LIBRARY, 'get_aim_hk_telemetry' RETURN, -1 endif ; check if sufficient keywords were supplied if (N_ELEMENTS(tlmId) eq 0 and $ N_ELEMENTS(externalElement) eq 0 and $ N_ELEMENTS(itemName) eq 0) then begin DOC_LIBRARY, 'get_aim_hk_telemetry' RETURN, -1 endif if N_ELEMENTS(externalElement) gt 0 and N_ELEMENTS(itemName) le 0 then $ DOC_LIBRARY, 'get_aim_hk_telemetry' if N_ELEMENTS(itemName) gt 0 and N_ELEMENTS(externalElement) le 0 then $ DOC_LIBRARY, 'get_aim_hk_telemetry' ;First, it is determined what the user inputs. When it is found what ;the user inputs, it is used to retrieve data from the ;TelmetryItemDefinition table ;Find out if the user input the startTime and stopTime in GPS ;microseconds, mission days, or julianDays if (N_ELEMENTS(startTime) ne N_ELEMENTS(stopTime)) then begin MESSAGE, 'Both startTime and stopTime must be provided.' RETURN, -1 endif if (N_ELEMENTS(missionDays) ne 0) then begin ; user specified time in mission (sorce) days start_time = ULONG64(sd2gps(startTime)*1.d6) stop_time = ULONG64(sd2gps(stopTime)*1.d6) endif else if (N_ELEMENTS(julianDays) ne 0) then begin ; user specified time in julian days start_time = ULONG64(jd2gps(startTime)*1.d6) stop_time = ULONG64(jd2gps(stopTime)*1.d6) endif else begin ; user specified timetags in gps microseconds, but ensure that they are ; expressed as an integer start_time = ULONG64(startTime) stop_time = ULONG64(stopTime) endelse ; force start and stop times to be scalars start_time = start_time[0] stop_time = stop_time[0] if (verbose) then PRINT, 'Getting information for each requested telemetry item...' ;Find out if the user input a tlmId, or a combination of ; external element and item name. if (N_ELEMENTS(tlmId) gt 0) then begin ; user specified a telemetry id for j = 0, N_ELEMENTS(tlmId)-1 do begin ; permit multiple tlmIds ; get the telemetry item definition information get_tlmid_item_definition, tlmId[j], tlmItem_rec if (N_ELEMENTS(tlmItem_rec) gt 0 and SIZE(tlmItem_rec, /type) eq 8) then begin ; valid telemetry id, add the record to the array we're building if (j eq 0) then begin tlmItem_data = tlmItem_rec endif else begin tlmItem_data = [TEMPORARY(tlmItem_data), tlmItem_rec] endelse endif else begin ; throw an error if we were not able to find telemetry information MESSAGE, 'Invalid telemetry item specified: ' + STRTRIM(tlmId[j],2) endelse endfor endif else begin ; Now we have externalElement and itemName ; must input equal numbers of externalElements and itemName if (N_ELEMENTS(externalElement) ne 1 and $ N_ELEMENTS(externalElement) ne N_ELEMENTS(itemName)) then begin MESSAGE, 'Equal number of externalElement and itemName must be provided.' RETURN, -1 endif else begin if (N_ELEMENTS(externalElement) eq 1) then begin ee = REPLICATE(externalElement, N_ELEMENTS(itemName)) endif else ee = externalElement for k = 0, N_ELEMENTS(itemName)-1 do begin get_itemname_item_definition, ee[k], itemName[k], tlmItem_rec if (N_ELEMENTS(tlmItem_rec) ne 0 and SIZE(tlmItem_rec, /type) eq 8) then begin if (k eq 0) then begin tlmItem_data = tlmItem_rec endif else begin tlmItem_data = [TEMPORARY(tlmItem_data), tlmItem_rec] endelse endif else begin MESSAGE, 'Invalid telemetry item or external element specified: "' + itemName[k] + '"' RETURN, -1 endelse endfor endelse endelse ; Find out whether the telemetry item is state or numeric then build ; the info and data records ;Once the telemetry item definition table has been queried, a mnemonic ;is made for each item for i = 0, N_ELEMENTS(tlmItem_data) - 1 do begin mnemonic = tlmItem_data[i].externalElement + '$' + tlmItem_data[i].itemName if (verbose) then PRINT, get_routine_name() + ' Getting data for ' + mnemonic ;Get the telemetry limits for all the items and fill the ;limits structure get_state_or_limits_or_analogconv, tlmItem_data[i].tlmId, $ hasLimits, tlmLimits_data, 'TelemetryLimits' fill_tlmlimits, tlmLimits_data, hasLimits, limits ;If the telemetry item is state, get the state conversions and fill ;the state info record. Also get the state data and fill ;the data record if(tlmItem_data[i].dataType eq 'D') then begin get_state_or_limits_or_analogconv, tlmItem_data[i].tlmId, $ hasStateConversions, stateConv_data, 'TelemetryStateConversions' fill_state_info, stateConv_data, hasStateConversions, $ tlmItem_data[i], limits, hasLimits, mnemonic, info_rec data_rec = PTR_NEW() ; presume no data are present ; obtain the data from the housekeeping database, if present get_l1_or_l1a_data, 'AIM_L1', 'TMdiscrete', tlmItem_data[i].tlmId, start_time, stop_time, SID, SCT_Cycle, state_data, nrows if nrows gt 0 then begin apply_state_conversions, state_data, stateConv_data, $ hasStateConversions, state build_state_structure, state_data, state, data_rec endif endif ; D ;If the telemetry item is analog, get the analog conversions and fill ;the analog info record. Also get the analog data and fill ;the analog data record if (tlmItem_data[i].dataType eq 'U' or $ tlmItem_data[i].dataType eq 'I' or $ tlmItem_data[i].dataType eq 'F') then begin get_state_or_limits_or_analogconv, tlmItem_data[i].tlmid, hasAnalogConversions, analogConv_data, 'TelemetryAnalogConversions' fill_analog_conversions, analogConv_data, hasAnalogConversions, $ analog_conversion fill_analog_info, tlmItem_data[i], hasAnalogConversions, $ analog_conversion, limits, hasLimits, $ mnemonic, info_rec data_rec = PTR_NEW() ; obtain the data from the housekeeping database, if present get_l1_or_l1a_data, 'AIM_L1A', 'TManalog', tlmItem_data[i].tlmId, start_time, stop_time, SID, SCT_Cycle, analog_data, nrows if nrows gt 0 then begin apply_analog_conversions, analog_data, analogConv_data, $ hasAnalogConversions, eu_value data_rec = TEMPORARY(analog_data) endif endif ; type U, I, or F if N_ELEMENTS(data_rec) eq 0 then begin MESSAGE, 'Unsupported telemetry item: ' + $ tlmItem_data[i].externalElement + '$' + $ tlmItem_data[i].itemName, /info continue endif p = PTR_NEW(/allocate) *p = CREATE_STRUCT('housekeeping', data_rec) if (SIZE(data_rec, /type) eq 8) then begin n_housekeeping = N_ELEMENTS(data_rec.(0)) endif else begin n_housekeeping = 0L endelse *p = CREATE_STRUCT(TEMPORARY(*p), 'n_housekeeping', n_housekeeping) if (i eq 0) then begin ; first telemetry item encountered info = CREATE_STRUCT(mnemonic, info_rec) ;data = create_struct(mnemonic, data_rec, science_data) data = CREATE_STRUCT(mnemonic, p) ;stop endif else begin data = CREATE_STRUCT(TEMPORARY(data), mnemonic, p) info = CREATE_STRUCT(TEMPORARY(info), mnemonic, info_rec) endelse endfor RETURN, data end