1#!/usr/bin/env python3
  2
  3# SK 11/05/2023 created
  4
  5"""Convert TM raw telemetry packets entries to TM events."""
  6
  7import logging
  8
  9from chart.db import ts
 10from chart.events.event import Event
 11from chart.events.event import include_event_check
 12from chart.products.events.multitable_utils import process_ordering_sensing_time
 13from chart.products.pus.packetdef import PacketDef
 14from chart.products.pus.packetdef import PacketDomain
 15from chart import settings
 16from chart.db.model.table import TableInfo
 17from chart.common.traits import is_listlike
 18from chart.web.user import User
 19
 20# This module is used for event classes where <db-table> matches DECODER_ID
 21DECODER_ID = 'BATTERY-EVENTS'
 22
 23# Table to read all telemetry from
 24TM_TABLE = 'TM'
 25
 26# ??? !!! For some reason we can't get this from the events XML file where we should get it
 27# from, so as a terrible and lazy hack it's hard-coded here
 28EVENT_CLASSNAME = 'BATTERY-EVENTS'
 29
 30tm_parameter = {
 31    'CFT0042F': 'PWR_VBUS1_AT_MIN_DETECT',
 32    'CFT00439': 'PWR_VBUS2_AT_MIN_DETECT',
 33    'CFT0042E': 'PWR_VBUS3_AT_MIN_DETECT',
 34    'CFTD6608': 'HKF_PWR_DOD'
 35}
 36
 37# print("tm_parameter:")
 38# for k, v in tm_parameter.items():
 39    # print(f"{k}: {v}")
 40
 41logger = logging.getLogger()
 42
 43def find_events(sid,
 44                start_time,
 45                start_time_eq,
 46                stop_time,
 47                event_classes,
 48                ordering,
 49                from_to,
 50                properties,
 51                count,
 52                single,
 53                filtering,
 54                multitable_extra_params,
 55                user: User = None):
 56    """Build Event records from records extracted from ts table in given time range.
 57
 58    Args:
 59        `sid` (SID): Source-ID to search for
 60        `start_time` (datetime): Start of time range to search
 61        `stop_time` (datetime):  End of time range to search
 62        `event_classes` (list of EventClass): Class definitions to search for - this is relevant for
 63            implementations of find_event() which are reading from tables which could return
 64            multiple different types of event.
 65        `ordering` (str or list of str): Field(s) to sort by
 66        `from_to` (tuple): (page_start, page_stop)
 67        `properties`: (list): list of property triplets (name, value, op)
 68        `count`: (bool): just return the number of events found
 69        `single`: (bool): just return the first event which matches input requirements
 70        `filtering` (EventTimeFilter): Specify if the start_time/stop_time range is applied
 71            to the sensing_time or the execution_time column
 72        `multitable_extra_params` (tuple): Special method of storing additional table-specific information
 73            in an Event object, which is passed via json to the event viewer table and back,
 74            and used again in this function to ecreate the original Event
 75
 76    Returns:
 77        event_count: (int) number of events which match input requirements
 78        events: list of events which match input requirements
 79    """
 80
 81    if from_to is not None:
 82        page_start, page_stop = from_to
 83
 84    # if returning a single record, set limit = 1
 85    # Note: there can be so many TM packets so when a single set, for the 'Snap' case
 86    #        set limit option to prevent database hanging. Do not set limit when single
 87    #        set due selecting event pop-up as handled by ts.select
 88    limit = None
 89    if single and start_time_eq is None:
 90        limit = 1
 91
 92    # Instead of returning an iterator, we build the whole list and return it
 93    events = []
 94    # Total count of events. This may be larger than len(events) since we may be pagingating
 95    # using the `from_to` parameter and as an optimisation, we don't bother to instantiate all
 96    # the required Events objects, just enough for the event viewer to work. However we still
 97    # want to tell the user how many events are available
 98    event_count = 0
 99
100    # Reset ordering for sensing_time
101    order_clauses = process_ordering_sensing_time(ordering)
102
103    table_info = TableInfo(TM_TABLE, sid=sid, fast_load=True)
104
105    if not single and count:
106        # just return count of events
107        event_count = ts.count(
108            table=table_info.storage_table,
109            field='SENSING_TIME',
110            method='ap',
111            sid=sid,
112            sensing_start=start_time,
113            sensing_stop=stop_time)
114        return event_count, events
115
116    fields = ['SENSING_TIME']
117    for key in tm_parameter.keys():
118        fields.append(table_info.fields.get(key))
119
120    # get each row using ts select
121    for row in ts.select(sid=sid,
122                         sensing_start=start_time,
123                         sensing_stop=stop_time,
124                         sensing_time=start_time_eq,
125                         table=table_info,
126                         fields=fields,
127                         ordering=order_clauses,
128                         limit=limit):
129
130        sensing_time = row[0]
131        CFT0042F_values = row[1]
132        CFT00439_values = row[2]
133        CFT0042E_values = row[3]
134        CFTD6608_values = row[4]
135
136        inst_properties = {
137                'SID': sid,
138                'CFT0042F': CFT0042F_values,
139                'CFT00439': CFT00439_values,
140                'CFT0042E': CFT0042E_values,
141                'CFTD6608': CFTD6608_values,
142                }
143
144        if multitable_extra_params is not None:
145            req_spid, req_sequence_count = multitable_extra_params
146            if req_spid != packet_reader.spid or req_sequence_count != sequence_count:
147                continue
148
149        # now filter properties in inst_properties before keeping event for viewing
150        if not include_event_check(properties, inst_properties):
151            continue
152
153        # if we are counting, just bump the counter, else yield event and bump counter
154        # Note: must count all events which meet the filtering criterea to keep paging upto date
155        event_count += 1
156
157        if not count:
158            # build event and append to events to return
159            if from_to is None or (page_start <= event_count and event_count <= page_stop):
160                # build event and append to events to return, if within Eventviewer display page
161                events.append(Event(sid=sid,
162                                    event_classname=EVENT_CLASSNAME,
163                                    start_time=sensing_time,
164                                    #multitable_extra_params=(spid, sequence_count),
165                                    instance_properties=inst_properties))
166
167            if single:
168                # just return first event, which matches search requirements
169                break
170
171    return event_count, events