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