xref: /openbmc/pldm/tools/visualize-pdr/pldm_visualise_pdrs.py (revision 98cb3efbd0c44b022dc1cae7cb99e3c946e6b7a5)
1b8cc3257SManojkiran Eda#!/usr/bin/env python3
2b8cc3257SManojkiran Eda
3b8cc3257SManojkiran Eda"""Tool to visualize PLDM PDR's"""
4b8cc3257SManojkiran Eda
5b8cc3257SManojkiran Edaimport argparse
6b8cc3257SManojkiran Edaimport json
7b8cc3257SManojkiran Edaimport hashlib
8b8cc3257SManojkiran Edaimport sys
9b8cc3257SManojkiran Edafrom datetime import datetime
10b8cc3257SManojkiran Edaimport paramiko
11b8cc3257SManojkiran Edafrom graphviz import Digraph
12b8cc3257SManojkiran Edafrom tabulate import tabulate
13b8cc3257SManojkiran Eda
14b8cc3257SManojkiran Eda
15b8cc3257SManojkiran Edadef connect_to_bmc(hostname, uname, passwd, port):
16b8cc3257SManojkiran Eda
17b8cc3257SManojkiran Eda    """ This function is responsible to connect to the BMC via
18b8cc3257SManojkiran Eda        ssh and returns a client object.
19b8cc3257SManojkiran Eda
20b8cc3257SManojkiran Eda        Parameters:
21b8cc3257SManojkiran Eda            hostname: hostname/IP address of BMC
22b8cc3257SManojkiran Eda            uname: ssh username of BMC
23b8cc3257SManojkiran Eda            passwd: ssh password of BMC
24b8cc3257SManojkiran Eda            port: ssh port of BMC
25b8cc3257SManojkiran Eda
26b8cc3257SManojkiran Eda    """
27b8cc3257SManojkiran Eda
28b8cc3257SManojkiran Eda    client = paramiko.SSHClient()
29b8cc3257SManojkiran Eda    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
30b8cc3257SManojkiran Eda    client.connect(hostname, username=uname, password=passwd, port=port)
31b8cc3257SManojkiran Eda    return client
32b8cc3257SManojkiran Eda
33b8cc3257SManojkiran Eda
34b8cc3257SManojkiran Edadef prepare_summary_report(state_sensor_pdr, state_effecter_pdr):
35b8cc3257SManojkiran Eda
36b8cc3257SManojkiran Eda    """ This function is responsible to parse the state sensor pdr
37b8cc3257SManojkiran Eda        and the state effecter pdr dictionaries and creating the
38b8cc3257SManojkiran Eda        summary table.
39b8cc3257SManojkiran Eda
40b8cc3257SManojkiran Eda        Parameters:
41b8cc3257SManojkiran Eda            state_sensor_pdr: list of state sensor pdrs
42b8cc3257SManojkiran Eda            state_effecter_pdr: list of state effecter pdrs
43b8cc3257SManojkiran Eda
44b8cc3257SManojkiran Eda    """
45b8cc3257SManojkiran Eda
46b8cc3257SManojkiran Eda    summary_table = []
47b8cc3257SManojkiran Eda    headers = ["sensor_id", "entity_type", "state_set", "states"]
48b8cc3257SManojkiran Eda    summary_table.append(headers)
49b8cc3257SManojkiran Eda    for value in state_sensor_pdr.values():
50b8cc3257SManojkiran Eda        summary_record = []
51b8cc3257SManojkiran Eda        sensor_possible_states = ''
52b8cc3257SManojkiran Eda        for sensor_state in value["possibleStates[0]"]:
53b8cc3257SManojkiran Eda            sensor_possible_states += sensor_state+"\n"
54b8cc3257SManojkiran Eda        summary_record.extend([value["sensorID"], value["entityType"],
55b8cc3257SManojkiran Eda                               value["stateSetID[0]"],
56b8cc3257SManojkiran Eda                               sensor_possible_states])
57b8cc3257SManojkiran Eda        summary_table.append(summary_record)
58b8cc3257SManojkiran Eda    print("Created at : ", datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
59b8cc3257SManojkiran Eda    print(tabulate(summary_table, tablefmt="fancy_grid", headers="firstrow"))
60b8cc3257SManojkiran Eda
61b8cc3257SManojkiran Eda    summary_table = []
62b8cc3257SManojkiran Eda    headers = ["effecter_id", "entity_type", "state_set", "states"]
63b8cc3257SManojkiran Eda    summary_table.append(headers)
64b8cc3257SManojkiran Eda    for value in state_effecter_pdr.values():
65b8cc3257SManojkiran Eda        summary_record = []
66b8cc3257SManojkiran Eda        effecter_possible_states = ''
67b8cc3257SManojkiran Eda        for state in value["possibleStates[0]"]:
68b8cc3257SManojkiran Eda            effecter_possible_states += state+"\n"
69b8cc3257SManojkiran Eda        summary_record.extend([value["effecterID"], value["entityType"],
70b8cc3257SManojkiran Eda                               value["stateSetID[0]"],
71b8cc3257SManojkiran Eda                               effecter_possible_states])
72b8cc3257SManojkiran Eda        summary_table.append(summary_record)
73b8cc3257SManojkiran Eda    print(tabulate(summary_table, tablefmt="fancy_grid", headers="firstrow"))
74b8cc3257SManojkiran Eda
75b8cc3257SManojkiran Eda
76b8cc3257SManojkiran Edadef draw_entity_associations(pdr, counter):
77b8cc3257SManojkiran Eda
78b8cc3257SManojkiran Eda    """ This function is responsible to create a picture that captures
79b8cc3257SManojkiran Eda        the entity association hierarchy based on the entity association
80b8cc3257SManojkiran Eda        PDR's received from the BMC.
81b8cc3257SManojkiran Eda
82b8cc3257SManojkiran Eda        Parameters:
83b8cc3257SManojkiran Eda            pdr: list of entity association PDR's
84b8cc3257SManojkiran Eda            counter: variable to capture the count of PDR's to unflatten
85b8cc3257SManojkiran Eda                     the tree
86b8cc3257SManojkiran Eda
87b8cc3257SManojkiran Eda    """
88b8cc3257SManojkiran Eda
89b8cc3257SManojkiran Eda    dot = Digraph('entity_hierarchy', node_attr={'color': 'lightblue1',
90b8cc3257SManojkiran Eda                                                 'style': 'filled'})
91b8cc3257SManojkiran Eda    dot.attr(label=r'\n\nEntity Relation Diagram < ' +
92b8cc3257SManojkiran Eda             str(datetime.now().strftime("%Y-%m-%d %H:%M:%S"))+'>\n')
93b8cc3257SManojkiran Eda    dot.attr(fontsize='20')
94b8cc3257SManojkiran Eda    edge_list = []
95b8cc3257SManojkiran Eda    for value in pdr.values():
96b8cc3257SManojkiran Eda        parentnode = str(value["containerEntityType"]) + \
97b8cc3257SManojkiran Eda                     str(value["containerEntityInstanceNumber"])
98b8cc3257SManojkiran Eda        dot.node(hashlib.md5((parentnode +
99b8cc3257SManojkiran Eda                              str(value["containerEntityContainerID"]))
100b8cc3257SManojkiran Eda                             .encode()).hexdigest(), parentnode)
101b8cc3257SManojkiran Eda
102b8cc3257SManojkiran Eda        for i in range(1, value["containedEntityCount"]+1):
103b8cc3257SManojkiran Eda            childnode = str(value[f"containedEntityType[{i}]"]) + \
104b8cc3257SManojkiran Eda                        str(value[f"containedEntityInstanceNumber[{i}]"])
105b8cc3257SManojkiran Eda            cid = str(value[f"containedEntityContainerID[{i}]"])
106b8cc3257SManojkiran Eda            dot.node(hashlib.md5((childnode + cid)
107b8cc3257SManojkiran Eda                                 .encode()).hexdigest(), childnode)
108b8cc3257SManojkiran Eda
109b8cc3257SManojkiran Eda            if[hashlib.md5((parentnode +
110b8cc3257SManojkiran Eda                            str(value["containerEntityContainerID"]))
111b8cc3257SManojkiran Eda                           .encode()).hexdigest(),
112b8cc3257SManojkiran Eda               hashlib.md5((childnode + cid)
113b8cc3257SManojkiran Eda                           .encode()).hexdigest()] not in edge_list:
114b8cc3257SManojkiran Eda                edge_list.append([hashlib.md5((parentnode +
115b8cc3257SManojkiran Eda                                  str(value["containerEntityContainerID"]))
116b8cc3257SManojkiran Eda                                              .encode()).hexdigest(),
117b8cc3257SManojkiran Eda                                  hashlib.md5((childnode + cid)
118b8cc3257SManojkiran Eda                                              .encode()).hexdigest()])
119b8cc3257SManojkiran Eda                dot.edge(hashlib.md5((parentnode +
120b8cc3257SManojkiran Eda                                      str(value["containerEntityContainerID"]))
121b8cc3257SManojkiran Eda                                     .encode()).hexdigest(),
122b8cc3257SManojkiran Eda                         hashlib.md5((childnode + cid).encode()).hexdigest())
123b8cc3257SManojkiran Eda    unflattentree = dot.unflatten(stagger=(round(counter/3)))
124b8cc3257SManojkiran Eda    unflattentree.render(filename='entity_association_' +
125b8cc3257SManojkiran Eda                         str(datetime.now().strftime("%Y-%m-%d_%H-%M-%S")),
126b8cc3257SManojkiran Eda                         view=False, cleanup=True, format='pdf')
127b8cc3257SManojkiran Eda
128b8cc3257SManojkiran Eda
129260f75a6SBrad Bishopclass PLDMToolError(Exception):
130260f75a6SBrad Bishop    """ Exception class intended to be used to hold pldmtool invocation failure
131260f75a6SBrad Bishop        information such as exit status and stderr.
132260f75a6SBrad Bishop
133260f75a6SBrad Bishop    """
134260f75a6SBrad Bishop
135260f75a6SBrad Bishop    def __init__(self, status, stderr):
136260f75a6SBrad Bishop        msg = "pldmtool failed with exit status {}.\n".format(status)
137260f75a6SBrad Bishop        msg += "stderr: \n\n{}".format(stderr)
138260f75a6SBrad Bishop        super(PLDMToolError, self).__init__(msg)
139*98cb3efbSBrad Bishop        self.status = status
140*98cb3efbSBrad Bishop
141*98cb3efbSBrad Bishop    def get_status(self):
142*98cb3efbSBrad Bishop        return self.status
143260f75a6SBrad Bishop
144260f75a6SBrad Bishop
145260f75a6SBrad Bishopdef process_pldmtool_output(stdout_channel, stderr_channel):
146260f75a6SBrad Bishop    """ Ensure pldmtool runs without error and if it does fail, detect that and
147260f75a6SBrad Bishop        show the pldmtool exit status and it's stderr.
148260f75a6SBrad Bishop
149*98cb3efbSBrad Bishop        A simpler implementation would just wait for the pldmtool exit status
150*98cb3efbSBrad Bishop        prior to attempting to decode it's stdout.  Instead, optimize for the
151*98cb3efbSBrad Bishop        no error case and allow the json decoder to consume pldmtool stdout as
152*98cb3efbSBrad Bishop        soon as it is available (in parallel).  This results in the following
153*98cb3efbSBrad Bishop        error scenarios:
154*98cb3efbSBrad Bishop            - pldmtool fails and the decoder fails
155*98cb3efbSBrad Bishop              Ignore the decoder fail and throw PLDMToolError.
156*98cb3efbSBrad Bishop            - pldmtool fails and the decoder doesn't fail
157*98cb3efbSBrad Bishop              Throw PLDMToolError.
158*98cb3efbSBrad Bishop            - pldmtool doesn't fail and the decoder does fail
159*98cb3efbSBrad Bishop              This is a pldmtool bug - re-throw the decoder error.
160*98cb3efbSBrad Bishop
161260f75a6SBrad Bishop        Parameters:
162260f75a6SBrad Bishop            stdout_channel: file-like stdout channel
163260f75a6SBrad Bishop            stderr_channel: file-like stderr channel
164260f75a6SBrad Bishop
165260f75a6SBrad Bishop    """
166260f75a6SBrad Bishop
167*98cb3efbSBrad Bishop    status = 0
168*98cb3efbSBrad Bishop    try:
169*98cb3efbSBrad Bishop        data = json.load(stdout_channel)
170*98cb3efbSBrad Bishop        # it's unlikely, but possible, that pldmtool failed but still wrote a
171*98cb3efbSBrad Bishop        # valid json document - so check for that.
172260f75a6SBrad Bishop        status = stderr_channel.channel.recv_exit_status()
173260f75a6SBrad Bishop        if status == 0:
174*98cb3efbSBrad Bishop            return data
175*98cb3efbSBrad Bishop    except json.decoder.JSONDecodeError:
176*98cb3efbSBrad Bishop        # pldmtool wrote an invalid json document.  Check to see if it had
177*98cb3efbSBrad Bishop        # non-zero exit status.
178*98cb3efbSBrad Bishop        status = stderr_channel.channel.recv_exit_status()
179*98cb3efbSBrad Bishop        if status == 0:
180*98cb3efbSBrad Bishop            # pldmtool didn't have non zero exit status, so it wrote an invalid
181*98cb3efbSBrad Bishop            # json document and the JSONDecodeError is the correct error.
182*98cb3efbSBrad Bishop            raise
183260f75a6SBrad Bishop
184*98cb3efbSBrad Bishop    # pldmtool had a non-zero exit status, so throw an error for that, possibly
185*98cb3efbSBrad Bishop    # discarding a spurious JSONDecodeError exception.
186260f75a6SBrad Bishop    raise PLDMToolError(status, "".join(stderr_channel))
187260f75a6SBrad Bishop
188260f75a6SBrad Bishop
189*98cb3efbSBrad Bishopdef get_pdrs_one_at_a_time(client):
19091523137SBrad Bishop    """ Using pldmtool over SSH, generate (record handle, PDR) tuples for each
19191523137SBrad Bishop        record in the PDR repository.
19291523137SBrad Bishop
19391523137SBrad Bishop        Parameters:
19491523137SBrad Bishop            client: paramiko ssh client object
19591523137SBrad Bishop
19691523137SBrad Bishop    """
19791523137SBrad Bishop
19891523137SBrad Bishop    command_fmt = 'pldmtool platform getpdr -d {}'
19991523137SBrad Bishop    record_handle = 0
20091523137SBrad Bishop    while True:
20191523137SBrad Bishop        output = client.exec_command(command_fmt.format(str(record_handle)))
20291523137SBrad Bishop        _, stdout, stderr = output
203260f75a6SBrad Bishop        pdr = process_pldmtool_output(stdout, stderr)
20491523137SBrad Bishop        yield record_handle, pdr
20591523137SBrad Bishop        record_handle = pdr["nextRecordHandle"]
20691523137SBrad Bishop        if record_handle == 0:
20791523137SBrad Bishop            break
20891523137SBrad Bishop
20991523137SBrad Bishop
210*98cb3efbSBrad Bishopdef get_all_pdrs_at_once(client):
211*98cb3efbSBrad Bishop    """ Using pldmtool over SSH, generate (record handle, PDR) tuples for each
212*98cb3efbSBrad Bishop        record in the PDR repository.  Use pldmtool platform getpdr --all.
213*98cb3efbSBrad Bishop
214*98cb3efbSBrad Bishop        Parameters:
215*98cb3efbSBrad Bishop            client: paramiko ssh client object
216*98cb3efbSBrad Bishop
217*98cb3efbSBrad Bishop    """
218*98cb3efbSBrad Bishop
219*98cb3efbSBrad Bishop    _, stdout, stderr = client.exec_command('pldmtool platform getpdr -a')
220*98cb3efbSBrad Bishop    all_pdrs = process_pldmtool_output(stdout, stderr)
221*98cb3efbSBrad Bishop
222*98cb3efbSBrad Bishop    # Explicitly request record 0 to find out what the real first record is.
223*98cb3efbSBrad Bishop    _, stdout, stderr = client.exec_command('pldmtool platform getpdr -d 0')
224*98cb3efbSBrad Bishop    pdr_0 = process_pldmtool_output(stdout, stderr)
225*98cb3efbSBrad Bishop    record_handle = pdr_0["recordHandle"]
226*98cb3efbSBrad Bishop
227*98cb3efbSBrad Bishop    while True:
228*98cb3efbSBrad Bishop        for pdr in all_pdrs:
229*98cb3efbSBrad Bishop            if pdr["recordHandle"] == record_handle:
230*98cb3efbSBrad Bishop                yield record_handle, pdr
231*98cb3efbSBrad Bishop                record_handle = pdr["nextRecordHandle"]
232*98cb3efbSBrad Bishop                if record_handle == 0:
233*98cb3efbSBrad Bishop                    return
234*98cb3efbSBrad Bishop        raise RuntimeError(
235*98cb3efbSBrad Bishop            "Dangling reference to record {}".format(record_handle))
236*98cb3efbSBrad Bishop
237*98cb3efbSBrad Bishop
238*98cb3efbSBrad Bishopdef get_pdrs(client):
239*98cb3efbSBrad Bishop    """ Using pldmtool over SSH, generate (record handle, PDR) tuples for each
240*98cb3efbSBrad Bishop        record in the PDR repository.  Use pldmtool platform getpdr --all or
241*98cb3efbSBrad Bishop        fallback on getting them one at a time if pldmtool doesn't support the
242*98cb3efbSBrad Bishop        --all option.
243*98cb3efbSBrad Bishop
244*98cb3efbSBrad Bishop        Parameters:
245*98cb3efbSBrad Bishop            client: paramiko ssh client object
246*98cb3efbSBrad Bishop
247*98cb3efbSBrad Bishop    """
248*98cb3efbSBrad Bishop    try:
249*98cb3efbSBrad Bishop        for record_handle, pdr in get_all_pdrs_at_once(client):
250*98cb3efbSBrad Bishop            yield record_handle, pdr
251*98cb3efbSBrad Bishop        return
252*98cb3efbSBrad Bishop    except PLDMToolError as e:
253*98cb3efbSBrad Bishop        # No support for the -a option
254*98cb3efbSBrad Bishop        if e.get_status() != 106:
255*98cb3efbSBrad Bishop            raise
256*98cb3efbSBrad Bishop    except json.decoder.JSONDecodeError as e:
257*98cb3efbSBrad Bishop        # Some versions of pldmtool don't print valid json documents with -a
258*98cb3efbSBrad Bishop        if e.msg != "Extra data":
259*98cb3efbSBrad Bishop            raise
260*98cb3efbSBrad Bishop
261*98cb3efbSBrad Bishop    for record_handle, pdr in get_pdrs_one_at_a_time(client):
262*98cb3efbSBrad Bishop        yield record_handle, pdr
263*98cb3efbSBrad Bishop
264*98cb3efbSBrad Bishop
265b8cc3257SManojkiran Edadef fetch_pdrs_from_bmc(client):
266b8cc3257SManojkiran Eda
267b8cc3257SManojkiran Eda    """ This is the core function that would use the existing ssh connection
268b8cc3257SManojkiran Eda        object to connect to BMC and fire the getPDR pldmtool command
269b8cc3257SManojkiran Eda        and it then agreegates the data received from all the calls into
270b8cc3257SManojkiran Eda        the respective dictionaries based on the PDR Type.
271b8cc3257SManojkiran Eda
272b8cc3257SManojkiran Eda        Parameters:
273b8cc3257SManojkiran Eda            client: paramiko ssh client object
274b8cc3257SManojkiran Eda
275b8cc3257SManojkiran Eda    """
276b8cc3257SManojkiran Eda
277b8cc3257SManojkiran Eda    entity_association_pdr = {}
278b8cc3257SManojkiran Eda    state_sensor_pdr = {}
279b8cc3257SManojkiran Eda    state_effecter_pdr = {}
280b8cc3257SManojkiran Eda    state_effecter_pdr = {}
281b8cc3257SManojkiran Eda    numeric_pdr = {}
282b8cc3257SManojkiran Eda    fru_record_set_pdr = {}
283b8cc3257SManojkiran Eda    tl_pdr = {}
28491523137SBrad Bishop    for handle_number, my_dic in get_pdrs(client):
285b8cc3257SManojkiran Eda        sys.stdout.write("Fetching PDR's from BMC : %8d\r" % (handle_number))
286b8cc3257SManojkiran Eda        sys.stdout.flush()
287b8cc3257SManojkiran Eda        if my_dic["PDRType"] == "Entity Association PDR":
288b8cc3257SManojkiran Eda            entity_association_pdr[handle_number] = my_dic
289b8cc3257SManojkiran Eda        if my_dic["PDRType"] == "State Sensor PDR":
290b8cc3257SManojkiran Eda            state_sensor_pdr[handle_number] = my_dic
291b8cc3257SManojkiran Eda        if my_dic["PDRType"] == "State Effecter PDR":
292b8cc3257SManojkiran Eda            state_effecter_pdr[handle_number] = my_dic
293b8cc3257SManojkiran Eda        if my_dic["PDRType"] == "FRU Record Set PDR":
294b8cc3257SManojkiran Eda            fru_record_set_pdr[handle_number] = my_dic
295b8cc3257SManojkiran Eda        if my_dic["PDRType"] == "Terminus Locator PDR":
296b8cc3257SManojkiran Eda            tl_pdr[handle_number] = my_dic
297b8cc3257SManojkiran Eda        if my_dic["PDRType"] == "Numeric Effecter PDR":
298b8cc3257SManojkiran Eda            numeric_pdr[handle_number] = my_dic
299b8cc3257SManojkiran Eda    client.close()
300b8cc3257SManojkiran Eda
301b8cc3257SManojkiran Eda    total_pdrs = len(entity_association_pdr.keys()) + len(tl_pdr.keys()) + \
302b8cc3257SManojkiran Eda        len(state_effecter_pdr.keys()) + len(numeric_pdr.keys()) + \
303b8cc3257SManojkiran Eda        len(state_sensor_pdr.keys()) + len(fru_record_set_pdr.keys())
304b8cc3257SManojkiran Eda    print("\nSuccessfully fetched " + str(total_pdrs) + " PDR\'s")
305b8cc3257SManojkiran Eda    print("Number of FRU Record PDR's : ", len(fru_record_set_pdr.keys()))
306b8cc3257SManojkiran Eda    print("Number of TerminusLocator PDR's : ", len(tl_pdr.keys()))
307b8cc3257SManojkiran Eda    print("Number of State Sensor PDR's : ", len(state_sensor_pdr.keys()))
308b8cc3257SManojkiran Eda    print("Number of State Effecter PDR's : ", len(state_effecter_pdr.keys()))
309b8cc3257SManojkiran Eda    print("Number of Numeric Effecter PDR's : ", len(numeric_pdr.keys()))
310b8cc3257SManojkiran Eda    print("Number of Entity Association PDR's : ",
311b8cc3257SManojkiran Eda          len(entity_association_pdr.keys()))
312b8cc3257SManojkiran Eda    return (entity_association_pdr, state_sensor_pdr,
313b8cc3257SManojkiran Eda            state_effecter_pdr, len(fru_record_set_pdr.keys()))
314b8cc3257SManojkiran Eda
315b8cc3257SManojkiran Eda
316b8cc3257SManojkiran Edadef main():
317b8cc3257SManojkiran Eda
318b8cc3257SManojkiran Eda    """ Create a summary table capturing the information of all the PDR's
319b8cc3257SManojkiran Eda        from the BMC & also create a diagram that captures the entity
320b8cc3257SManojkiran Eda        association hierarchy."""
321b8cc3257SManojkiran Eda
322b8cc3257SManojkiran Eda    parser = argparse.ArgumentParser(prog='pldm_visualise_pdrs.py')
323b8cc3257SManojkiran Eda    parser.add_argument('--bmc', type=str, required=True,
324b8cc3257SManojkiran Eda                        help="BMC IPAddress/BMC Hostname")
325e0c55c8aSBrad Bishop    parser.add_argument('--user', type=str, help="BMC username")
326b8cc3257SManojkiran Eda    parser.add_argument('--password', type=str, required=True,
327b8cc3257SManojkiran Eda                        help="BMC Password")
328b8cc3257SManojkiran Eda    parser.add_argument('--port', type=int, help="BMC SSH port",
329b8cc3257SManojkiran Eda                        default=22)
330b8cc3257SManojkiran Eda    args = parser.parse_args()
331b8cc3257SManojkiran Eda    client = connect_to_bmc(args.bmc, args.user, args.password, args.port)
332b8cc3257SManojkiran Eda    association_pdr, state_sensor_pdr, state_effecter_pdr, counter = \
333b8cc3257SManojkiran Eda        fetch_pdrs_from_bmc(client)
334b8cc3257SManojkiran Eda    draw_entity_associations(association_pdr, counter)
335b8cc3257SManojkiran Eda    prepare_summary_report(state_sensor_pdr, state_effecter_pdr)
336b8cc3257SManojkiran Eda
337b8cc3257SManojkiran Eda
338b8cc3257SManojkiran Edaif __name__ == "__main__":
339b8cc3257SManojkiran Eda    main()
340