source: ReferenceDesigns/w3_802.11/python/wlan_exp/log/entry_types.py

Last change on this file was 6320, checked in by chunter, 9 months ago

1.8.0 release wlan-exp

File size: 56.9 KB
Line 
1# -*- coding: utf-8 -*-
2"""
3------------------------------------------------------------------------------
4Mango 802.11 Reference Design Experiments Framework - Log Entry Types
5------------------------------------------------------------------------------
6License:   Copyright 2019 Mango Communications, Inc. All rights reserved.
7           Use and distribution subject to terms in LICENSE.txt
8------------------------------------------------------------------------------
9
10This module defines each type of log entry that may exist in the event log of
11an 802.11 Reference Design Node.
12
13The log entry definitions in this file must match the corresponding
14definitions in the wlan_mac_entries.h header file in the C code
15running on the node.
16
17This module maintains a dictionary which contains a reference to each
18known log entry type. This dictionary is stored in the variable
19``wlan_exp_log_entry_types``. The :class:`WlanExpLogEntryType` constructor
20automatically adds each log entry type definition to this dictionary. Users
21may access the dictionary to view currently defined log entry types. But
22user code should not modify the dictionary contents directly.
23
24
25Custom Log Entry Types
26----------------------
27The :mod:`log_entries` module includes definitions for the log entry types
28implemented in the current 802.11 Reference Design C code.
29
30Log entry types defined here must match the corresponding entry definitions in
31the node C code.  Custom entries can be defined and added to the global
32dictionary by user scripts.
33
34Log entry type definitions are instances of the :class:`WlanExpLogEntryType`
35class. The :class:`WlanExpLogEntryType` constructor requires two arguments:
36``name`` and ``entry_type_id``.  Both the name and entry type ID **must** be
37unique relative to the existing entry types defined in :mod:`log_entries`.
38
39To define a custom log entry type::
40
41    import wlan_exp.log.entry_types as entry_types
42
43    #name and entry_type_id must not collide with existing log entry type definitions
44    my_entry_type = entry_types.WlanExpLogEntryType(name='MY_ENTRY', entry_type_id=999)
45    my_entry_type.append_field_defs([
46            ('timestamp',              'Q',      'uint64'),
47            ('field_A',                'H',      'uint16'),
48            ('field_B',                'H',      'uint16')])
49
50"""
51import sys, os
52from struct import pack, unpack, calcsize, error
53import wlan_exp.util as util
54
55# Fix to support Python 2.x and 3.x
56if sys.version[0]=="3": long=None
57
58
59# Event Log Constants
60#   Must match corresponding C definitions in wlan_mac_event_log.h
61WLAN_EXP_LOG_DELIM = 0xACED
62
63
64# Log Entry Type Constants
65#   Must match corresponding C definitions in wlan_mac_entries.h
66ENTRY_TYPE_NULL                   = 0
67ENTRY_TYPE_NODE_INFO              = 1
68ENTRY_TYPE_EXP_INFO               = 2
69
70ENTRY_TYPE_NODE_TEMPERATURE       = 4
71
72ENTRY_TYPE_TIME_INFO              = 6
73
74ENTRY_TYPE_RX_OFDM                = 10
75ENTRY_TYPE_RX_OFDM_LTG            = 11
76
77ENTRY_TYPE_RX_DSSS                = 15
78
79ENTRY_TYPE_TX_HIGH                = 20
80ENTRY_TYPE_TX_HIGH_LTG            = 21
81
82ENTRY_TYPE_TX_LOW                 = 25
83ENTRY_TYPE_TX_LOW_LTG             = 26
84
85# -----------------------------------------------------------------------------
86# Log Entry Type Container
87# -----------------------------------------------------------------------------
88
89log_entry_types          = dict()
90
91# -----------------------------------------------------------------------------
92# Log Entry Type Base Class
93# -----------------------------------------------------------------------------
94
95# Function whose docstring is updated every time a WlanExpLogEntryType is created
96#  Sphinx autodoc will read this function's docstring via the .__doc__ attribute
97#  The .__doc__ attribute of a Type (like WlanExpLogEntryType) cannot be modified at
98#  runtime, but the .__doc__ of a function can.
99def _log_entry_types_doc_container():
100    """Default docstring"""
101    pass
102
103
104class WlanExpLogEntryType(object):
105    """Class to define a log entry type."""
106    # _fields is a list of 3-tuples:
107    #     (field_name, field_fmt_struct, field_fmt_np)
108    _fields             = None
109
110    entry_type_id       = None # Unique integer ID for entry type
111    name                = None # Unique string name for entry type
112    description         = ''   # Description of log entry type, used for generating documentation
113
114    fields_np_dt        = None # numpy dtype object describing format
115    fields_fmt_struct   = None # List of field formats, in struct module format
116    _field_offsets      = None
117
118    gen_numpy_callbacks = None
119
120    consts              = None # Container for user-defined, entry-specific constants
121
122    def __init__(self, name=None, entry_type_id=None):
123       
124        self.name = name
125        self.entry_type_id = entry_type_id
126
127        # Check if this new WlanExpLogEntryType instannce should be added to the global log_entry_types dictionary
128        #  The log_entry_types dictionary is used to map entry type IDs (integers) and names (strings) by the log
129        #   processing utilities. The code below prints warnings if any IDs or names are duplicated. Only entry types
130        #   with not-None type IDs and names are added to the dictionary. Types with null IDs or names can still be used
131        #   as containers for common field sets shared by multiple entry types (i.e. tx_common for TX_HIGH and TX_HIGH_LTG)
132        if (name is not None) and (entry_type_id is not None):
133            if type(entry_type_id) is not int:
134                raise Exception("ERROR: Invalid entry_type_id {0} - WlanExpLogEntryType entry_type_id must be int".format(entry_type_id))
135
136            if(name in log_entry_types.keys()):
137                print("WARNING: replacing exisitng WlanExpLogEntryType with name {0}".format(name))
138
139            if(entry_type_id in log_entry_types.keys()):
140                print("WARNING: replacing exisitng WlanExpLogEntryType with ID {0}".format(entry_type_id))
141
142            log_entry_types[str(name)] = self
143            log_entry_types[entry_type_id] = self
144
145        # Initialize fields to empty lists
146        self._fields             = []
147
148        # Initialize unpack variables
149        self.fields_fmt_struct   = ''
150
151        # Initialize callbacks
152        self.gen_numpy_callbacks = []
153
154        # Initialize dictionary to contain constants specific to each entry type
155        self.consts              = util.consts_dict()
156
157        # Initialize variable that contains field names and byte offsets
158        self._field_offsets      = {}
159
160
161    # -------------------------------------------------------------------------
162    # Accessor methods for the WlanExpLogEntryType
163    # -------------------------------------------------------------------------
164    def get_field_names(self):
165        """Get the field names of the log entry.
166
167        Returns:
168            names (list of str):  List of string field names for the entry
169        """
170        return [f[0] for f in self._fields]
171
172    def get_field_struct_formats(self):
173        """Get the Python struct format of the log entry fields.
174
175        Returns:
176            formats (list of str):  List of Python struct formats for the fields in the log entry
177        """
178        return [f[1] for f in self._fields]
179
180    def get_field_defs(self):          return self._fields
181
182    def get_field_offsets(self):       return self._field_offsets
183
184    def get_entry_type_id(self):
185        """Get the ID for the entry type.
186
187        Returns:
188            ID (int):  Integer ID for the log entry
189        """
190        return self.entry_type_id
191
192    def append_field_defs(self, field_info):
193        """Adds fields to the definition of the log entry type.
194
195        Args:
196            field_info (list of tuple):  Each must be of the form
197                ``(field_name, field_type_struct, field_type_numpy)`` where:
198               
199                    * ``field_name``: Name of field as string
200                    * ``field_type_struct``: Field type as string, using formats specified by ``struct`` module
201                    * ``field_type_numpy``: Field type as string, using formats specified by numpy ``dtype``
202                    * ``field_desc``: String describing the field's meaning, used to generate wiki docs
203       
204        """
205        if type(field_info) is list:
206            self._fields.extend(field_info)
207        else:
208            self._fields.append(field_info)
209        self._update_field_defs()
210
211
212    def modify_field_def(self, name, struct_type, numpy_type, doc_str=None):
213        """Modifies fields of the definition of the log entry type.
214
215        Args:
216            name (str):              Name of field to modify
217            struct_type (str):       New Python struct type for the field
218            numpy_type (str):        New numpy type for the field
219            doc_str (str, optional): New documentation string
220        """
221        index = None
222
223        for idx, f in enumerate(self._fields):
224            if (f[0] == name):
225                index = idx
226
227        if index is None:
228            print("WARNING:  Field {0} not found in {1}.".format(name, self.name))
229            return
230
231        if doc_str is None:
232            field = (name, struct_type, numpy_type, self._fields[index][3])
233        else:
234            field = (name, struct_type, numpy_type, doc_str)
235
236        self._fields[index] = field
237
238
239    def add_gen_numpy_array_callback(self, callback):
240        """Add callback that is run after the numpy array is generated from
241        the entry type.
242
243        The callbacks will be executed in the order the are added to the log
244        entry
245       
246        Args:
247            callback (function):  Function to run after the numpy array is generated
248        """
249        if callable(callback):
250            self.gen_numpy_callbacks.append(callback)
251        else:
252            print("ERROR:  Callback must be callable function.")
253
254
255    # -------------------------------------------------------------------------
256    # Utility methods for the WlanExpLogEntryType
257    # -------------------------------------------------------------------------
258    def generate_numpy_array(self, log_data, byte_offsets):
259        """Generate a NumPy array from the log_bytes of the given WlanExpLogEntryType instance
260        at the given byte_offsets.
261        """
262        import numpy as np
263
264        index_iter = [log_data[o : o + self.fields_np_dt.itemsize] for o in byte_offsets]
265        # fromiter began throwing an error in numpy ~1.14.5:
266        #  ValueError: invalid literal for long() with base 10: `xxx`
267        #  where xxx are the first 3 bytes of the first entry of index_iter
268        #  I can't find the underlying change that triggered this, so
269        #   the explicit iteration below is the slower-but-functional workaround
270        #np_arr = np.fromiter(index_iter[2:], self.fields_np_dt, len(byte_offsets))
271
272        # Workaround: explicitly create the numpy array then populate it one
273        #  entry at a time using fromstring(), which is happy with the index_iter
274        #  list that somehow breaks fromiter()
275        np_arr = np.empty(dtype=self.fields_np_dt, shape=(len(byte_offsets),))
276        for ii in range(len(index_iter)):
277            np_arr[ii] = np.fromstring(index_iter[ii], self.fields_np_dt)
278
279        if self.gen_numpy_callbacks:
280            for callback in self.gen_numpy_callbacks:
281                np_arr = callback(np_arr)
282
283        return np_arr
284
285
286    def generate_entry_doc(self, fmt='wiki'):
287        """Generate wiki documentation."""
288        import textwrap
289        import wlan_exp.log.util as log_util
290
291        field_descs = list()
292        for f in self._fields:
293            # Field tuple is (name, struct_type, np_type, (optional)desc)
294            #   Construct new tuple of (name, np_type, desc ('' if not defined))
295            try:
296                field_descs.append( (f[0], f[2], f[3]))
297            except IndexError:
298                # Field missing description; use empty string
299                field_descs.append( (f[0], f[2], ''))
300
301        def doc_fields_table(field_list, fmt='wiki'):
302            """field_list must be iterable of 3-tuples:
303            0: Field name (string)
304            1: Field data type (string, preferably the numpy datatype string)
305            2: Field description (string)
306            """
307
308            doc_str = ''
309
310            if fmt == 'wiki':
311                doc_str += '{{{#!th align=center\nField Name\n}}}\n'
312                doc_str += '{{{#!th align=center\nData Type\n}}}\n'
313                doc_str += '{{{#!th align=center\nDescription\n}}}\n'
314                doc_str += '|----------------\n'
315
316                for fd in field_list:
317                    import re
318                    # Wiki-ify some string formats:
319                    #   Line breaks in descriptions must be explicit "[[BR]]"
320                    #   Braces with numeric contents need escape (![) to disable Trac interpretting as changeset number
321                    fd_desc = fd[2]
322                    fd_desc = re.sub(r'(\[[\d:,]+\])', '!\\1', fd_desc) # do this first, so other wiki tags inserted below aren't escaped
323                    #fd_desc = fd_desc.replace('\n', '[[BR]]')
324
325                    try:
326                        consts = self.consts[fd[0]]
327                        fd_desc += '\n\nConstants defined for this field:\n'
328                        fd_desc += '||= Name =||= Value =||\n'
329                        for c in consts.keys():
330                            #fd_desc += ' * {{{{{{\'{0}\': 0x{1:X}}}}}}}\n'.format(c, consts[c])
331                            fd_desc += '|| {{{{{{{0}}}}}}}  ||  {{{{{{0x{1:X}}}}}}}  ||\n'.format(c, consts[c])
332                    except:
333                        # Field didn't have constants defined
334                        pass
335
336                    doc_str += '{{{{{{#!td align=left\n{0}\n}}}}}}\n'.format(fd[0])
337                    doc_str += '{{{{{{#!td align=center\n{0}\n}}}}}}\n'.format(fd[1])
338                    doc_str += '{{{{{{#!td align=left\n{0}\n}}}}}}\n'.format(fd_desc)
339                    doc_str += '|----------------\n'
340
341            elif fmt == 'txt':
342                doc_str += 'Field Name\t\t\t| Data Type\t| Description\n'
343                doc_str += '---------------------------------------------------------------------------------------------------------------------\n'
344
345                for fd in field_descs:
346                    doc_str += '{0:30}\t| {1:10}\t| {2}\n'.format(fd[0], fd[1], fd[2])
347
348                doc_str += '---------------------------------------------------------------------------------------------------------------------\n'
349
350            elif fmt == 'rst':
351                import re
352
353                rst_table_row = lambda c0, c1, c2: '   * - {0}\n     - {1}\n     - {2}\n'.format(c0, c1, c2)
354
355                doc_str += '\n.. list-table::\n   :widths: 25 10 65\n   :header-rows: 1\n\n'
356                doc_str += rst_table_row('Field', 'Data Type', 'Description')
357
358                for fd in field_list:
359                    fd_desc = fd[2]
360
361                    # Apply some transformations for rst formatting
362                    #  Prefix any explicit line breaks with spaces to align the rst block quote
363                    fd_desc = re.sub('\n +', '\n\n       ', fd_desc) # do this first, so other wiki tags inserted below aren't escaped
364
365                    try:
366                        consts = self.consts[fd[0]]
367                        fd_desc += '\n\n       Constants defined for this field:\n\n'
368                        for c in consts.keys():
369                            fd_desc += '       * ``{0}``: ``0x{1:X}``\n'.format(c, consts[c])
370                    except:
371                        # Field didn't have constants defined
372                        pass
373
374                    doc_str += rst_table_row(fd[0], fd[1], fd_desc)
375
376            return doc_str
377
378        if fmt == 'wiki':
379            # Construct the Trac-wiki-style documentation string for this entry type
380            doc_str = '\n----\n\n'
381            doc_str += '=== Entry Type {0} ===\n'.format(self.name)
382            doc_str += self.description + '\n\n'
383            doc_str += 'Entry type ID: {0}\n\n'.format(self.entry_type_id)
384
385            doc_str += doc_fields_table(field_descs, fmt='wiki')
386
387        elif fmt == 'txt':
388            # Construct plain text version of documentation string for this entry type
389            doc_str = '---------------------------------------------------------------------------------------------------------------------\n'
390            doc_str += 'Entry Type {0}\n'.format(self.name)
391            doc_str += 'Entry type ID: {0}\n\n'.format(self.entry_type_id)
392            doc_str += textwrap.fill(self.description) + '\n\n'
393
394            doc_str += doc_fields_table(field_descs, fmt='txt')
395
396        elif fmt == 'rst':
397            # Construct the Rst documentation string for this entry type
398            doc_str = '\n\n'
399            doc_str += '``{0}``\n'.format(self.name)
400            doc_str += '{0}\n\n'.format('*'*(11+len(self.name)))
401
402            doc_str += self.description + '\n\n'
403            doc_str += 'Entry type ID: {0}\n\n'.format(self.entry_type_id)
404
405            doc_str += doc_fields_table(field_descs, fmt='rst')
406
407        # Check each post-numpy array generation callback to any documentation to include
408        #   The callbacks can define "virtual" fields - convenience fields appended to the numpy
409        #   array that are calculated from the "real" fields
410        for cb in self.gen_numpy_callbacks:
411            field_descs = []
412            try:
413                (cb_doc_str, cb_doc_fields) = cb(np_arr_orig=None, docs_only=True)
414
415                for f in cb_doc_fields:
416                    field_descs.append((f[0], f[1], f[2]))
417
418                doc_str += '\n\n'
419                doc_str += cb_doc_str + '\n\n'
420                doc_str += doc_fields_table(field_descs, fmt=fmt)
421
422            except (TypeError, IndexError):
423                # Callback didn't implement suitable 'docs_only' output; fail quietly and return
424                #   print('Error generating callback field docs for {0}\n'.format(cb))
425                pass
426
427        return doc_str
428
429
430    def _entry_as_string(self, buf):
431        """Generate a string representation of the entry from a buffer.
432       
433        This method should only be used for debugging log data parsing and log
434        index generation, not for general creation of text log files.
435
436        This method does not work correctly on RX_OFDM entries due to the way
437        the channel estimates are defined.  The channel_est, mac_payload_len,
438        and mac_payload will all be zeros.
439        """
440        entry_size = calcsize(self.fields_fmt_struct)
441        entry      = self.deserialize(buf[0:entry_size])[0]
442
443        str_out = self.name + ': '
444
445        for k in entry.keys():
446            s = entry[k]
447            if((type(s) is int) or (type(s) is long)):
448                str_out += "\n    {0:30s} = {1:20d} (0x{1:16x})".format(k, s)
449            elif(type(s) is str):
450                s = map(ord, list(entry[k]))
451                str_out += "\n    {0:30s} = [".format(k)
452                for x in s:
453                    str_out += "{0:d}, ".format(x)
454                str_out += "\b\b]"
455
456        str_out += "\n"
457
458        return str_out
459
460
461    def _entry_as_byte_array_string(self, buf):
462        """Generate a string representation of the entry from a buffer as an array of bytes. This
463        method should only be used for debugging log data parsing and log index generation, not
464        for general creation of text log files.
465        """
466        entry_size = calcsize(self.fields_fmt_struct)
467        entry      = self.deserialize(buf[0:entry_size])[0]
468
469        str_out = self.name + ': '
470
471        line_num = 0
472
473        for byte in range(entry_size):
474            if (byte % 16) == 0:
475                str_out  += "\n{0:>8d}: ".format(line_num)
476                line_num += 16
477            str_out += "0x{0:02x} ".format(ord(buf[byte]))
478
479        str_out += "\n"
480
481        print(entry)
482
483        return str_out
484
485
486    def deserialize(self, buf):
487        """Unpacks one or more raw log entries of the same type into a list of dictionaries
488
489        Args:
490            buf (bytearray): Array of raw log data containing 1 or more log entries of the same type.
491
492        Returns:
493            entries (List of dict):
494                Each dictionary in the list has one value per field in the log entry definition using
495                the field names as keys.
496        """
497        from collections import OrderedDict
498
499        ret_val    = []
500        buf_size   = len(buf)
501        entry_size = calcsize(self.fields_fmt_struct)
502        index      = 0
503
504        while (index < buf_size):
505            try:
506                dataTuple = unpack(self.fields_fmt_struct, buf[index:index+entry_size])
507                all_names = self.get_field_names()
508                all_fmts  = self.get_field_struct_formats()
509
510                # Filter out names for fields ignored during unpacking
511                names = [n for (n,f) in zip(all_names, all_fmts) if 'x' not in f]
512
513                # Use OrderedDict to preserve user-specified field order
514                ret_val.append(OrderedDict(zip(names, dataTuple)))
515
516            except error as err:
517                print("Error unpacking {0} buffer with len {1}: {2}".format(self.name, len(buf), err))
518
519            index += entry_size
520
521        return ret_val
522
523
524    def serialize(self, entries):
525        """Packs one or more list of dictionaries into a buffer of log entries of the same type
526
527        Args:
528            entry_list (dictionary):  Array of dictionaries for 1 or more log entries of the same type
529
530        Returns:
531            data (bytearray):  Bytearray of packed binary data
532        """
533        length     = 1
534        ret_val    = ""
535        entry_size = calcsize(self.fields_fmt_struct)
536
537        # Convert entries to a list if it is not already one
538        if type(entries) is not list:
539            entries = [entries]
540
541        # Pack each of the entries into a single data buffer
542        for entry in entries:
543            fields     = []
544            tmp_values = []
545            used_field = []
546
547            for field in self._fields:
548                if 'x' not in field[1]:
549                    fields.append(field[0])
550                    try:
551                        tmp_values.append(entry[field[0]])
552                        used_field.append(True)
553                    except KeyError:
554                        tmp_values.append(0)
555                        used_field.append(False)
556
557            if (False):
558                print("Serialize Entry:")
559                print(fields)
560                print(used_field)
561                print(tmp_values)
562
563            ret_val += pack(self.fields_fmt_struct, *tmp_values)
564
565        if (entry_size * length) != len(ret_val):
566            msg  = "WARNING: Sizes do not match.\n"
567            msg += "    Expected  {0} bytes".format(entry_size * length)
568            msg += "    Buffer is {0} bytes".format(len(ret_val))
569            print(msg)
570
571        return ret_val
572
573
574    # -------------------------------------------------------------------------
575    # Internal methods for the WlanExpLogEntryType
576    # -------------------------------------------------------------------------
577    def _update_field_defs(self):
578        """Internal method to update fields."""
579        import numpy as np
580
581        # Update the fields format used by struct unpack/calcsize
582        self.fields_fmt_struct = ' '.join(self.get_field_struct_formats())
583
584        # Update the numpy dtype definition
585        # fields_np_dt is a numpy dtype, built using a dictionary of names/formats/offsets:
586        #     {'names':[field_names], 'formats':[field_formats], 'offsets':[field_offsets]}
587        # We specify each field's byte offset explicitly. Byte offsets for fields in _fields
588        # are inferred, assuming tight C-struct-type packing (same assumption as struct.unpack)
589
590        get_np_size = (lambda f: np.dtype(f).itemsize)
591
592        # Compute the offset of each real field, inferred by the sizes of all previous fields
593        #   This loop must look at all real fields, even ignored/padding fields
594        sizes   = list(map(get_np_size, [f[2] for f in self._fields]))
595        offsets = [sum(sizes[0:i]) for i in range(len(sizes))]
596
597        np_fields = self._fields
598
599        # numpy processing ignores the same fields ignored by struct.unpack
600        # !!!BAD!!! Doing this filtering breaks HDF5 export
601        # offsets = [o for (o,f) in zip(offsets_all, self._fields) if 'x' not in f[1]]
602        # np_fields = [f for f in self._fields if 'x' not in f[1]]
603
604        names   =  [f[0] for f in np_fields]
605        formats =  [f[2] for f in np_fields]
606
607        self.fields_np_dt = np.dtype({'names':names, 'formats':formats, 'offsets':offsets})
608
609        # Update the field offsets
610        self._field_offsets = dict(zip(names, offsets))
611
612        # Check our definitions of struct vs numpy are in sync
613        struct_size = calcsize(self.fields_fmt_struct)
614        np_size     = self.fields_np_dt.itemsize
615
616        if (struct_size != np_size):
617            msg  = "WARNING:  Definitions of struct {0} do not match.\n".format(self.name)
618            msg += "    Struct size = {0}    Numpy size = {1}".format(struct_size, np_size)
619            print(msg)
620
621    def __eq__(self, other):
622        """WlanExpLogEntryType are equal if their names are equal."""
623        if type(other) is str:
624            return self.name == other
625        else:
626            return (isinstance(other, self.__class__) and (self.name == other.name))
627
628    def __lt__(self, other):
629        """WlanExpLogEntryType are less than if their names are less than."""
630        if type(other) is str:
631            return self.name < other
632        else:
633            return (isinstance(other, self.__class__) and (self.name < other.name))
634
635    def __gt__(self, other):
636        """WlanExpLogEntryType are greater than if their names are greater than."""
637        if type(other) is str:
638            return self.name > other
639        else:
640            return (isinstance(other, self.__class__) and (self.name > other.name))
641
642    def __ne__(self, other):
643        return not self.__eq__(other)
644
645    def __hash__(self):
646        return hash(self.name)
647
648    def __repr__(self):
649        return self.name
650
651# End class
652
653
654# -----------------------------------------------------------------------------
655# Log Entry Type Functions
656# -----------------------------------------------------------------------------
657
658def np_array_add_txrx_ltg_fields(np_arr_orig, docs_only=False):
659    """Add 'virtual' fields to TX/RX LTG packets."""
660    return np_array_add_fields(np_arr_orig, mac_addr=True, ltg=True, docs_only=docs_only)
661
662# End def
663
664
665def np_array_add_txrx_fields(np_arr_orig, docs_only=False):
666    """Add 'virtual' fields to TX/RX packets."""
667    return np_array_add_fields(np_arr_orig, mac_addr=True, ltg=False, docs_only=docs_only)
668
669# End def
670
671
672def np_array_add_fields(np_arr_orig, mac_addr=False, ltg=False, docs_only=False):
673    """Add 'virtual' fields to the numpy array.
674
675    This is an example of a gen_numpy_array_callback
676   
677    Args:
678        np_arr_orig (Numpy Array):  Numpy array to extend
679        mac_addr (bool, optional):  Add MAC address information to the numpy array?
680        ltg (bool, optional):       Add LTG information ot the numpy array?
681        docs_only (bool):           Only generate documentation strings for virtual fields?
682            If this is True, then only documentation strings are returned.
683
684    Returns:
685        np_array (Numpy Array):     Updated numpy array
686
687    Extend the default np_arr with convenience fields for:
688        * MAC header addresses
689        * LTG packet information
690
691    IMPORTANT: np_arr uses the original bytearray as its underlying data
692    We must operate on a copy to avoid clobbering log entries adjacent to the
693    Tx or Rx entries being extended.
694    """
695    import numpy as np
696
697    names   = ()
698    formats = ()
699    descs   = ()
700
701    # Add the MAC address fields
702    if mac_addr:
703        names   += ('addr1', 'addr2', 'addr3', 'mac_seq')
704        formats += ('uint64', 'uint64', 'uint64', 'uint16')
705        descs   += ('MAC Header Address 1', 'MAC Header Address 2', 'MAC Header Address 3', 'MAC Header Sequence Number')
706
707    # Add the LTG fields
708    if ltg:
709        names   += ('ltg_uniq_seq', 'ltg_flow_id')
710        formats += ('uint64', 'uint64')
711        descs   += ('Unique sequence number for LTG packet', 'LTG Flow ID, calculated as:\n  16LSB: LTG instance ID\n  48MSB: Destination MAC address')
712
713    # If there are no fields to add, just return the original array
714    if not names:
715        return np_arr_orig
716
717    if docs_only:
718        ret_str  = 'The following fields are populated when the log entry is part of a numpy array generated via the {{{generate_numpy_array}}} method. '
719        ret_str += 'These fields are calculated from the underlying bytes in the raw log entries and are stored in more convenient formats tha the raw '
720        ret_str += 'log fields. For example, these MAC address fields are 48-bit values stored in 64-bit integers. These integer addresses are much easier '
721        ret_str += 'to use when filtering Tx/Rx log entries using numpy and pandas.'
722
723        ret_list = zip(names, formats, descs)
724
725        return (ret_str, ret_list)
726
727    # Return if array is None to not raise an Attribute Exception
728    if np_arr_orig is None:
729        return np_arr_orig
730
731    # Create a new numpy dtype with additional fields
732    dt_new = extend_np_dt(np_arr_orig.dtype, {'names': names, 'formats': formats})
733
734    # Initialize the output array (same shape, new dtype)
735    np_arr_out = np.zeros(np_arr_orig.shape, dtype=dt_new)
736
737    # Copy data from the base numpy array into the output array
738    for f in np_arr_orig.dtype.names:
739        # TODO: maybe don't copy fields that are ignored in the struct format?
740        #   problem is non-TxRx entries would still have these fields in their numpy versions
741        np_arr_out[f] = np_arr_orig[f]
742
743    # Extract the MAC payload (the payload is at a minimum a 24-entry uint8 array)
744    mac_hdrs = np_arr_orig['mac_payload']
745
746    # Populate the MAC Address fields
747    if mac_addr:
748        # Helper array of powers of 2
749        #   this array arranges bytes such that they match other u64 representations of MAC addresses
750        #   elsewhere in the framework
751        addr_conv_arr = np.uint64(2)**np.array(range(40, -1, -8), dtype='uint64')
752
753        # Compute values for address-as-int fields using numpy's dot-product routine
754        #  MAC header offsets here select the 3 6-byte address fields and 1 3-byte sequence number
755        #  Each field is conditioned on the MAC payload length exceeding the bytes needed to compute the field
756        #   This check handles the case of logging code being changed to record short/no payloads or
757        #    for short packet Tx/Rx, such as ACKs, which do not contain all 3 address and sequence number fields
758
759        arr_filt = np_arr_out['mac_payload_len'] >= 10
760        np_arr_out['addr1'][arr_filt] = np.dot(addr_conv_arr, np.transpose(mac_hdrs[arr_filt,  4:10]))
761
762        arr_filt = np_arr_out['mac_payload_len'] >= 16
763        np_arr_out['addr2'][arr_filt] = np.dot(addr_conv_arr, np.transpose(mac_hdrs[arr_filt, 10:16]))
764
765        arr_filt = np_arr_out['mac_payload_len'] >= 22
766        np_arr_out['addr3'][arr_filt] = np.dot(addr_conv_arr, np.transpose(mac_hdrs[arr_filt, 16:22]))
767
768        arr_filt = np_arr_out['mac_payload_len'] >= 24
769        np_arr_out['mac_seq'][arr_filt] = np.dot(mac_hdrs[arr_filt, 22:24], [1, 256]) // 16
770
771    # Populate the LTG fields
772    if ltg:
773        # Helper array of powers of 2
774        #   this array arranges bytes such that they match other u64 representations of MAC addresses
775        #   elsewhere in the framework
776        uniq_seq_conv_arr = np.uint64(2)**np.array(range(0, 64, 8), dtype='uint64')
777        flow_id_conv_arr  = np.uint64(2)**np.array(range(0, 32, 8), dtype='uint64')
778
779        # Compute the LTG unique sequence number from the bytes in the LTG mac payload
780        np_arr_out['ltg_uniq_seq'] = np.dot(uniq_seq_conv_arr, np.transpose(mac_hdrs[:, 32:40]))
781
782        # Compute the LTG flow ID from the bytes in the LTG mac payload and the transmitting address (ie 'addr2') if present.
783        #     flow_id[63:16] = Transmitting address
784        #     flow_id[15: 0] = LTG ID from LTG mac payload
785        try:
786            np_arr_out['ltg_flow_id']  = (np_arr_out['addr2'] << 16) + (np.dot(flow_id_conv_arr, np.transpose(mac_hdrs[:, 40:44])) & 0xFFFF)
787        except KeyError:
788            np_arr_out['ltg_flow_id']  = np.dot(flow_id_conv_arr, np.transpose(mac_hdrs[:, 40:44]))
789
790    return np_arr_out
791
792# End def
793
794
795def extend_np_dt(dt_orig, new_fields=None):
796    """Extends a numpy dtype object with additional fields.
797
798    Args:
799        dt_orig (Numpy DataType):  Original Numpy data type
800        new_fields (dict):         Dictionary with keys 'names' and 'formats', same as when specifying
801            new dtype objects. The return dtype will *not* contain byte offset values for existing or
802            new fields, even if exisiting fields had specified offsets. Thus the original dtype should
803            be used to interpret raw data buffers before the extended dtype is used to add new fields.
804    """
805    import numpy as np
806    from collections import OrderedDict
807
808    if(type(dt_orig) is not np.dtype):
809        raise Exception("ERROR: extend_np_dt requires valid numpy dtype as input")
810    else:
811        # Use ordered dictionary to preserve original field order (not required, just convenient)
812        dt_ext = OrderedDict()
813
814        # Extract the names/formats/offsets dictionary for the base dtype
815        #   dt.fields returns dictionary with field names as keys and
816        #   values of (dtype, offset). The dtype objects in the first tuple field
817        #   can be used as values in the 'formats' entry when creating a new dtype
818        # This approach will preserve the types and dimensions of scalar and non-scalar fields
819        dt_ext['names'] = list(dt_orig.names)
820        dt_ext['formats'] = [dt_orig.fields[f][0] for f in dt_orig.names]
821
822        if(type(new_fields) is dict):
823            # Add new fields to the extended dtype
824            dt_ext['names'].extend(new_fields['names'])
825            dt_ext['formats'].extend(new_fields['formats'])
826        elif type(new_fields) is not None:
827            raise Exception("ERROR: new_fields argument must be dictionary with keys 'names' and 'formats'")
828
829        # Construct and return the new numpy dtype object
830        dt_new = np.dtype(dt_ext)
831
832        return dt_new
833
834# End def
835
836
837# -----------------------------------------------------------------------------
838# Log Entry Type Definitions
839# -----------------------------------------------------------------------------
840
841# The code below runs when this module is imported to define each log entry type implemented
842#  in the reference design. However the append_field_defs method depends on numpy, which is
843#  not avaiable on the warpproject.org server, so this code will break autodoc as-is
844# The server sets an environment variable BUILDING_DOCS_ON_SERVER when it runs post-svn-commit
845#  Skip all code below if this variable is set
846if not os.environ.get('BUILDING_DOCS_ON_SERVER', False):
847
848    # -----------------------------------------------------------------------------
849    # Common Log Entry Contants
850    # -----------------------------------------------------------------------------
851
852    # Packet Type/Sub-type values
853    #     Matches definition in wlan_mac_802_11_defs.h (802.11-2012 Table 8-1)
854    common_pkt_types = util.consts_dict({
855        # Management sub-types
856        'ASSOC_REQ'         :  0x00,
857        'ASSOC_RESP'        :  0x10,
858        'REASSOC_REQ'       :  0x20,
859        'REASSOC_RESP'      :  0x30,
860        'PROBE_REQ'         :  0x40,
861        'PROBE_RESP'        :  0x50,
862        'BEACON'            :  0x80,
863        'DISASSOC'          :  0xA0,
864        'AUTH'              :  0xB0,
865        'DEAUTH'            :  0xC0,
866        'ACTION'            :  0xD0,
867
868        # Control sub-types
869        'BLOCK_ACK_REQ'     :  0x84,
870        'BLOCK_ACK'         :  0x94,
871        'RTS'               :  0xB4,
872        'CTS'               :  0xC4,
873        'ACK'               :  0xD4,
874
875        # Data sub-types
876        'DATA'              :  0x08,       
877        'NULLDATA'          :  0x48,
878        'QOSDATA'           :  0x88,
879    })
880       
881
882    # -----------------------------------------------------------------------------
883    # NULL Log Entry Instance
884    # -----------------------------------------------------------------------------
885
886    # The NULL entry is used to "remove" an existing entry within the log.
887    #   By replacing the current entry type with the NULL entry type and zeroing
888    #   out all data following the header, this can effectively remove an entry
889    #   without changing the memory footprint of the log.  NULL entries will
890    #   be filtered and never show up in the raw_log_index.
891
892    entry_null = WlanExpLogEntryType(name='NULL', entry_type_id=ENTRY_TYPE_NULL)
893
894
895    # -----------------------------------------------------------------------------
896    # Virtual Log Entry Instances
897    # -----------------------------------------------------------------------------
898
899    ###########################################################################
900    # Rx Common
901    #
902    entry_rx_common = WlanExpLogEntryType(name='RX_COMMON_FIELDS', entry_type_id=None)
903
904    entry_rx_common.description  = 'These log entries will only be created for packets that are passed to the high-level MAC code in CPU High. If '
905    entry_rx_common.description += 'the low-level MAC filter drops the packet, it will not be logged. For full "monitor mode" ensure the low-level '
906    entry_rx_common.description += 'MAC filter is configured to pass all receptions up to CPU High.'
907
908    entry_rx_common.append_field_defs([
909        ('timestamp',              'Q',      'uint64',  'Value of MAC Time in microseconds at PHY RX_START'),
910        ('timestamp_frac',         'B',      'uint8',   'Fractional part of timestamp (units of 6.25ns)'),
911        ('phy_samp_rate',          'B',      'uint8',   'PHY sampling rate in MSps'),
912        ('length',                 'H',      'uint16',  'Length of payload in bytes'),
913        ('cfo_est',                'i',      'int32',   'Time-domain CFO estimate from Rx PHY; Fix32_31 value, CFO as fraction of sampling frequency'),
914        ('mcs',                    'B',      'uint8',   'MCS index, in [0:7]'),
915        ('phy_mode',               'B',      'uint8',   'PHY mode index, in [0:2]'),
916        ('ant_mode',               'B',      'uint8',   'Antenna mode: [1,2,3,4] for SISO Rx on RF [A,B,C,D]'),
917        ('power',                  'b',      'int8',    'Rx power in dBm'),
918        ('padding0',               'x',      'uint8',   ''),
919        ('pkt_type',               'B',      'uint8',   'Packet type, first frame control byte of 802.11 header'),
920        ('channel',                'B',      'uint8',   'Channel (center frequency) index'),
921        ('padding1',               'x',      'uint8',   ''),
922        ('rx_gain_index',          'B',      'uint8',   'Radio Rx gain index; larger values mean larger Rx gains, mapping to absolute dB is radio-dependent'),
923        ('padding2',               'B',      'uint8',   ''),
924        ('flags',                  'H',      'uint16',  '1-bit flags')])
925
926    entry_rx_common.consts = util.consts_dict({
927        'ant_mode'      : util.consts_dict({
928                'RF_A': 0x01,
929                'RF_B': 0x02,
930                'RF_C': 0x03,
931                'RF_D': 0x04,
932            }),
933        'pkt_type'      : common_pkt_types,
934        'phy_mode'      : util.phy_modes,
935        'flags'         : util.consts_dict({
936            'FCS_GOOD'                 : 0x0001,
937            'DUPLICATE'                : 0x0002,
938            'UNEXPECTED_RESPONSE'      : 0x0004,
939            'LTG_PYLD'                 : 0x0040,
940            'LTG'                      : 0x0080
941        })
942    })
943
944
945    ###########################################################################
946    # Tx CPU High Common
947    #
948    entry_tx_common = WlanExpLogEntryType(name='TX_HIGH_COMMON_FIELDS', entry_type_id=None)
949
950    entry_tx_common.description  = 'Tx events in CPU High, logged for each data and management frame created and enqueued in CPU High. See TX_LOW for log entries of '
951    entry_tx_common.description += 'actual Tx events, including re-transmissions. The time values in this log entry can be used to determine time in queue '
952    entry_tx_common.description += '(time_to_accept), time taken by CPU Low for all Tx attempts (time_to_done) and total time from creation to completion '
953    entry_tx_common.description += '(time_to_accept+time_to_done).'
954
955    entry_tx_common.append_field_defs([
956        ('timestamp',              'Q',      'uint64',  'Value of MAC Time in microseconds when packet was created, immediately before it was enqueued'),
957        ('time_to_accept',         'I',      'uint32',  'Time duration in microseconds between packet creation and acceptance by frame_transmit() in CPU Low'),
958        ('time_to_done',           'I',      'uint32',  'Time duration in microseconds between packet acceptance by CPU Low and completion of all transmissions by CPU Low'),
959        ('uniq_seq',               'Q',      'uint64',  'Unique sequence number for Tx packet; 12 LSB of this used for 802.11 MAC header sequence number'),
960        ('padding0',               'I',      'uint32',  ''),       
961        ('num_tx',                 'H',      'uint16',  'Number of Tx attempts that were made for this packet'),
962        ('length',                 'H',      'uint16',  'Length in bytes of MPDU; includes MAC header, payload and FCS'),
963        ('padding1',               'x',      'uint8',   ''),
964        ('pkt_type',               'B',      'uint8',   'Packet type, first frame control byte of 802.11 header'),
965        ('queue_id',               'H',      'uint16',  'Tx queue ID from which the packet was retrieved'),
966        ('queue_occupancy',        'H',      'uint16',  'Occupancy of the Tx queue immediately after this packet was enqueued'),
967        ('flags',                  'H',      'uint16',  '1-bit flags')])
968
969    entry_tx_common.consts = util.consts_dict({
970        'pkt_type'   : common_pkt_types,
971        'flags'      : util.consts_dict({
972            'SUCCESSFUL'     : 0x0001,
973            'LTG_PYLD'       : 0x0040,
974            'LTG'            : 0x0080
975        })
976    })
977
978
979    ###########################################################################
980    # Tx CPU Low Common
981    #
982    entry_tx_low_common = WlanExpLogEntryType(name='TX_LOW_COMMON_FIELDS', entry_type_id=None)
983
984    entry_tx_low_common.description  = 'Record of actual PHY transmission. At least one TX_LOW will be logged for every TX_HIGH entry. Multiple TX_LOW entries '
985    entry_tx_low_common.description += 'may be created for the same TX_HIGH entry if the low-level MAC re-transmitted the frame. Use the uniq_seq fields to match '
986    entry_tx_low_common.description += 'TX_HIGH and TX_LOW entries to find records common to the same MPDU.'
987
988    entry_tx_low_common.append_field_defs([
989        ('timestamp',              'Q',      'uint64',  'Value of MAC Time in microseconds when packet transmission actually started (PHY TX_START time)'),
990        ('uniq_seq',               'Q',      'uint64',  'Unique sequence number of original MPDU'),
991        ('mcs',                    'B',      'uint8',   'MCS index in [0:7]'),
992        ('phy_mode',               'B',      'uint8',   'PHY mode index, in [1:2]'),
993        ('ant_mode',               'B',      'uint8',   'PHY antenna mode in [0x10, 0x20, 0x30, 0x40]'),
994        ('tx_power',               'b',      'int8',    'Tx power in dBm'),
995        ('reserved0',              'B',      'uint8',   ''),
996        ('channel',                'B',      'uint8',   'Channel (center frequency) index'),
997        ('length',                 'H',      'uint16',  'Length in bytes of MPDU; includes MAC header, payload and FCS'),
998        ('num_slots',              'h',      'int16',   'Number of backoff slots allotted prior to this transmission; may not have been used for initial Tx (attempt_number==1); A value of -1 in this field means no backoff occured'),
999        ('cw',                     'H',      'uint16',  'Contention window value at time of this Tx'),
1000        ('pkt_type',               'B',      'uint8',   'Packet type, (first frame control byte of 802.11 header)'),
1001        ('flags',                  'B',      'uint8',   '1-bit Flags'),
1002        ('timestamp_frac',         'B',      'uint8',   'Fractional part of Tx timestamp (units of 6.25ns)'),
1003        ('phy_samp_rate',          'B',      'uint8',   'PHY Sampling Rate Mode'),
1004        ('attempt_number',         'H',      'uint16',   'Transmission index for this attempt, starting at 1 (1 = first Tx)'),
1005        ('reserved1',              'H',      'uint16',   '')])
1006
1007    entry_tx_low_common.consts = util.consts_dict({
1008        'ant_mode'      : util.consts_dict({
1009                'RF_A': 0x10,
1010                'RF_B': 0x20,
1011                'RF_C': 0x30,
1012                'RF_D': 0x40,
1013            }),
1014        'phy_mode'      : util.phy_modes,
1015        'pkt_type'      : common_pkt_types,
1016        'flags'         : util.consts_dict({
1017            'RECEIVED_RESPONSE'          : 0x01,
1018            'LTG'                        : 0x40,
1019            'LTG_PYLD'                   : 0x80
1020        })
1021    })
1022
1023
1024    # -----------------------------------------------------------------------------
1025    # Log Entry Type Instances
1026    # -----------------------------------------------------------------------------
1027
1028    ###########################################################################
1029    # Node Info
1030    #
1031    entry_node_info = WlanExpLogEntryType(name='NODE_INFO', entry_type_id=ENTRY_TYPE_NODE_INFO)
1032
1033    entry_node_info.description = 'Details about the node hardware and its configuration. Node info values are static after boot.'
1034
1035    entry_node_info.append_field_defs([
1036        ('timestamp',                           'Q',    'uint64',  'Value of MAC Time in microseconds when log entry created'),
1037        ('wlan_mac_addr',                       'Q',    'uint64',  'MAC address of station'),       
1038        ('high_sw_id',                          'B',    'uint8',   'ID of CPU_HIGH project'),
1039        ('low_sw_id',                           'B',    'uint8',   'ID of CPU_LOW project'),
1040        ('padding',                             'H',    'uint16',  'padding for alignment'),
1041        ('high_sw_config',                      'I',    'uint32',  'Configuration of CPU_HIGH'),
1042        ('low_sw_config',                       'I',    'uint32',  'Configuration of CPU_LOW'),
1043        ('node_id',                             'I',    'uint32',  'Node ID, as set during wlan_exp init'),
1044        ('platform_id',                         'I',    'uint32',  'Platform ID'),
1045        ('serial_num',                          'I',    'uint32',  'Node serial number'),       
1046        ('wlan_exp_version',                    'I',    'uint32',  'Version of wlan_exp'),
1047        ('max_tx_power_dbm',                    'h',    'int16',    'Maximum transmit power'),
1048        ('min_tx_power_dbm',                    'h',    'int16',    'Minimum transmit power'),
1049        ('cpu_high_compilation_date',           '12s',  '12S',     'CPU High Compilation Date string'),
1050        ('cpu_high_compilation_time',           '12s',  '12S',     'CPU High Compilation Time string'),
1051        ('cpu_low_compilation_date',            '12s',  '12S',     'CPU Low Compilation Date string'),
1052        ('cpu_low_compilation_time',            '12s',  '12S',     'CPU Low Compilation Time string')])
1053
1054    entry_node_info.consts = util.consts_dict({
1055        'high_sw_id': util.consts_dict({
1056            'AP':     0x01,
1057            'STA':    0x02,
1058            'IBSS':   0x03,
1059            }),
1060        'low_sw_id': util.consts_dict({
1061            'DCF':     0x01,
1062            'NOMAC':   0x02,
1063            })
1064        })
1065
1066    ###########################################################################
1067    # Experiment Info header - actual exp_info contains a "message" field that
1068    #   follows this header. Since the message is variable length it is not described
1069    #   in the fields list below. Full exp_info entries (header + message) must be extracted
1070    #   directly by a user script.
1071    #
1072    entry_exp_info_hdr = WlanExpLogEntryType(name='EXP_INFO', entry_type_id=ENTRY_TYPE_EXP_INFO)
1073
1074    entry_exp_info_hdr.description = 'Header for generic experiment info entries created by the user application. '
1075    entry_exp_info_hdr.description += 'The payload of the EXP_INFO entry is not described by the Python entry type. User '
1076    entry_exp_info_hdr.description += 'code must access the payload in the binary log data directly.'
1077
1078    entry_exp_info_hdr.append_field_defs([
1079        ('timestamp',              'Q',      'uint64',  'Value of MAC Time in microseconds when log entry created'),
1080        ('info_type',              'I',      'uint32',  'Exp info type - arbitrary value supplied by application'),
1081        ('msg_len',                'I',      'uint32',  'Message length in bytes'),])
1082
1083
1084    ###########################################################################
1085    # Time Info
1086    #
1087    entry_time_info = WlanExpLogEntryType(name='TIME_INFO', entry_type_id=ENTRY_TYPE_TIME_INFO)
1088
1089    entry_time_info.description  = 'Record of a time base event at the node. This log entry is used to enable parsing of log data '
1090    entry_time_info.description += 'recorded before and after changes to the node\'s microsecond MAC timer. This entry also allows a wlan_exp controler to '
1091    entry_time_info.description += 'write the current host time to the node log without affecting the node\'s MAC timer value. This enables adjustment '
1092    entry_time_info.description += 'of log entry timestamps to real timestamps in post-proessing.'
1093
1094    entry_time_info.append_field_defs([
1095        ('timestamp',              'Q',      'uint64', 'Value of MAC Time in microseconds when log entry created (before any time change is applied)'),
1096        ('time_id',                'I',      'uint32', 'Random ID value included in wlan_exp TIME_INFO command; used to find common entries across nodes'),
1097        ('reason',                 'I',      'uint32', 'Reason code for TIME_INFO log entry creation'),
1098        ('mac_timestamp',          'Q',      'uint64', 'New value of MAC Time in microseconds '),
1099        ('system_timestamp',       'Q',      'uint64', 'Value of System Time in microseconds'),
1100        ('host_timestamp',         'Q',      'uint64', 'Host time in microseconds-since-epoch; 0xFFFFFFFFFFFFFFFF if unknown')])
1101
1102    entry_time_info.consts = util.consts_dict({
1103        'reason'        : util.consts_dict({
1104            'SYSTEM'                   : 0x00000000,
1105            'WLAN_EXP_SET_TIME'        : 0x00000001,
1106            'WLAN_EXP_ADD_LOG'         : 0x00000002
1107        })
1108    })
1109
1110
1111    ###########################################################################
1112    # Temperature
1113    #
1114    entry_node_temperature = WlanExpLogEntryType(name='NODE_TEMPERATURE', entry_type_id=ENTRY_TYPE_NODE_TEMPERATURE)
1115
1116    entry_node_temperature.description  = 'Record of the FPGA system monitor die temperature. This entry is only created when directed by a wlan_exp '
1117    entry_node_temperature.description += 'command. Temperature values are stored as 32-bit unsigned integers. To convert to degrees Celcius, apply '
1118    entry_node_temperature.description += '(((float)temp_u32)/(65536.0*0.00198421639)) - 273.15'
1119
1120    entry_node_temperature.append_field_defs([
1121        ('timestamp',              'Q',      'uint64', 'Value of MAC Time in microseconds when log entry created'),
1122        ('temp_current',           'I',      'uint32', 'Current FPGA die temperature (deg C)'),
1123        ('temp_min',               'I',      'uint32', 'Minimum FPGA die temperature (deg C) since FPGA configuration or sysmon reset'),
1124        ('temp_max',               'I',      'uint32', 'Maximum FPGA die temperature (deg C) since FPGA configuration or sysmon reset')])
1125
1126
1127    ###########################################################################
1128    # Receive OFDM
1129    #
1130    entry_rx_ofdm = WlanExpLogEntryType(name='RX_OFDM', entry_type_id=ENTRY_TYPE_RX_OFDM)
1131
1132    entry_rx_ofdm.description  = 'Rx events from OFDM PHY. ' + entry_rx_common.description
1133
1134    entry_rx_ofdm.append_field_defs(entry_rx_common.get_field_defs())
1135    entry_rx_ofdm.append_field_defs([
1136        ('chan_est',               '256B',   '(64,2)i2',    'OFDM Rx channel estimates, packed as [(uint16)I (uint16)Q] values, one per subcarrier'),
1137        ('mac_payload_len',        'I',      'uint32',      'Length in bytes of MAC payload recorded in log for this packet'),
1138        ('mac_payload',            '24s',    '24uint8',     'First 24 bytes of MAC payload, typically the 802.11 MAC header')])
1139
1140    entry_rx_ofdm.add_gen_numpy_array_callback(np_array_add_txrx_fields)
1141
1142    entry_rx_ofdm.consts = entry_rx_common.consts.copy()
1143
1144
1145    ###########################################################################
1146    # Receive OFDM LTG packet
1147    #
1148    entry_rx_ofdm_ltg = WlanExpLogEntryType(name='RX_OFDM_LTG', entry_type_id=ENTRY_TYPE_RX_OFDM_LTG)
1149
1150    entry_rx_ofdm_ltg.description  = 'LTG ' + entry_rx_ofdm.description
1151
1152    entry_rx_ofdm_ltg.append_field_defs(entry_rx_common.get_field_defs())
1153    entry_rx_ofdm_ltg.append_field_defs([
1154        ('chan_est',               '256B',   '(64,2)i2',    'OFDM Rx channel estimates, packed as [(uint16)I (uint16)Q] values, one per subcarrier'),
1155        ('mac_payload_len',        'I',      'uint32',      'Length in bytes of MAC payload recorded in log for this packet'),
1156        ('mac_payload',            '44s',    '44uint8',     'First 44 bytes of MAC payload: the 802.11 MAC header, LLC header, Packet ID, LTG ID')])
1157
1158    entry_rx_ofdm_ltg.add_gen_numpy_array_callback(np_array_add_txrx_ltg_fields)
1159
1160    entry_rx_ofdm_ltg.consts = entry_rx_common.consts.copy()
1161
1162
1163    ###########################################################################
1164    # Receive DSSS
1165    #
1166    entry_rx_dsss = WlanExpLogEntryType(name='RX_DSSS', entry_type_id=ENTRY_TYPE_RX_DSSS)
1167
1168    entry_rx_dsss.description  = 'Rx events from DSSS PHY. ' + entry_rx_common.description
1169
1170    entry_rx_dsss.append_field_defs(entry_rx_common.get_field_defs())
1171    entry_rx_dsss.append_field_defs([
1172        ('mac_payload_len',        'I',      'uint32',      'Length in bytes of MAC payload recorded in log for this packet'),
1173        ('mac_payload',            '24s',    '24uint8',     'First 24 bytes of MAC payload, typically the 802.11 MAC header')])
1174
1175    entry_rx_dsss.add_gen_numpy_array_callback(np_array_add_txrx_fields)
1176
1177    entry_rx_dsss.consts = entry_rx_common.consts.copy()
1178
1179
1180    ###########################################################################
1181    # Transmit from CPU High
1182    #
1183    entry_tx_high = WlanExpLogEntryType(name='TX_HIGH', entry_type_id=ENTRY_TYPE_TX_HIGH)
1184
1185    entry_tx_high.description  = entry_tx_common.description
1186
1187    entry_tx_high.append_field_defs(entry_tx_common.get_field_defs())
1188    entry_tx_high.append_field_defs([
1189        ('mac_payload_len',        'I',      'uint32',      'Length in bytes of MAC payload recorded in log for this packet'),
1190        ('mac_payload',            '24s',    '24uint8',     'First 24 bytes of MAC payload, typically the 802.11 MAC header')])
1191
1192    entry_tx_high.add_gen_numpy_array_callback(np_array_add_txrx_fields)
1193
1194    entry_tx_high.consts = entry_tx_common.consts.copy()
1195
1196
1197    ###########################################################################
1198    # Transmit from CPU High LTG packet
1199    #
1200    entry_tx_high_ltg = WlanExpLogEntryType(name='TX_HIGH_LTG', entry_type_id=ENTRY_TYPE_TX_HIGH_LTG)
1201
1202    entry_tx_high_ltg.description  = entry_tx_common.description
1203
1204    entry_tx_high_ltg.append_field_defs(entry_tx_common.get_field_defs())
1205    entry_tx_high_ltg.append_field_defs([
1206        ('mac_payload_len',        'I',      'uint32',      'Length in bytes of MAC payload recorded in log for this packet'),
1207        ('mac_payload',            '44s',    '44uint8',     'First 44 bytes of MAC payload: the 802.11 MAC header, LLC header, Packet ID, LTG ID')])
1208
1209    entry_tx_high_ltg.add_gen_numpy_array_callback(np_array_add_txrx_ltg_fields)
1210
1211    entry_tx_high_ltg.consts = entry_tx_common.consts.copy()
1212
1213
1214    ###########################################################################
1215    # Transmit from CPU Low
1216    #
1217    entry_tx_low = WlanExpLogEntryType(name='TX_LOW', entry_type_id=ENTRY_TYPE_TX_LOW)
1218
1219    entry_tx_low.description  = entry_tx_low_common.description
1220
1221    entry_tx_low.append_field_defs(entry_tx_low_common.get_field_defs())
1222    entry_tx_low.append_field_defs([
1223        ('mac_payload_len',        'I',      'uint32',      'Length in bytes of MAC payload recorded in log for this packet'),
1224        ('mac_payload',            '24s',    '24uint8',     'First 24 bytes of MAC payload, typically the 802.11 MAC header')])
1225
1226    entry_tx_low.add_gen_numpy_array_callback(np_array_add_txrx_fields)
1227
1228    entry_tx_low.consts = entry_tx_low_common.consts.copy()
1229
1230
1231    ###########################################################################
1232    # Transmit from CPU Low LTG packet
1233    #
1234    entry_tx_low_ltg = WlanExpLogEntryType(name='TX_LOW_LTG', entry_type_id=ENTRY_TYPE_TX_LOW_LTG)
1235
1236    entry_tx_low_ltg.description  = entry_tx_low_common.description
1237
1238    entry_tx_low_ltg.append_field_defs(entry_tx_low_common.get_field_defs())
1239    entry_tx_low_ltg.append_field_defs([
1240        ('mac_payload_len',        'I',      'uint32',      'Length in bytes of MAC payload recorded in log for this packet'),
1241        ('mac_payload',            '44s',    '44uint8',     'First 44 bytes of MAC payload: the 802.11 MAC header, LLC header, Packet ID, LTG ID')])
1242
1243    entry_tx_low_ltg.add_gen_numpy_array_callback(np_array_add_txrx_ltg_fields)
1244
1245    entry_tx_low_ltg.consts = entry_tx_low_common.consts.copy()
1246
Note: See TracBrowser for help on using the repository browser.