From 051702990ddbeb0a857c358980090f3bb9b11f92 Mon Sep 17 00:00:00 2001 From: Simon Hancock Date: Tue, 23 Jan 2024 17:39:36 +0000 Subject: [PATCH] Put unit knowledge in DFFormat class --- DFReader.py | 149 ++++++++++++++++++++++++++++------------------------ 1 file changed, 81 insertions(+), 68 deletions(-) diff --git a/DFReader.py b/DFReader.py index 7947a5a87..e126404ec 100644 --- a/DFReader.py +++ b/DFReader.py @@ -85,8 +85,7 @@ def __init__(self, type, name, flen, format, columns, oldfmt=None): self.format = format self.columns = columns.split(',') self.instance_field = None - self.unit_ids = None - self.mult_ids = None + self.units = None if self.columns == ['']: self.columns = [] @@ -125,32 +124,64 @@ def __init__(self, type, name, flen, format, columns, oldfmt=None): if self.msg_fmts[i] == 'a': self.a_indexes.append(i) + # If this format was alrady defined, copy over units and instance info if oldfmt is not None: - self.set_unit_ids(oldfmt.unit_ids) - self.set_mult_ids(oldfmt.mult_ids) - - def set_unit_ids(self, unit_ids): - '''set unit IDs string from FMTU''' + self.units = oldfmt.units + if oldfmt.instance_field is not None: + self.set_instance_field(self.colhash[oldfmt.instance_field]) + + def set_instance_field(self, instance_idx): + '''set up the instance field for this format''' + self.instance_field = self.columns[instance_idx] + # work out offset and length of instance field in message + pre_fmt = self.format[:instance_idx] + pre_sfmt = "" + for c in pre_fmt: + (s, mul, type) = FORMAT_TO_STRUCT[c] + pre_sfmt += s + self.instance_ofs = struct.calcsize(pre_sfmt) + (ifmt,) = self.format[instance_idx] + self.instance_len = struct.calcsize(ifmt) + + def set_unit_ids(self, unit_ids, unit_lookup): + '''apply unit IDs string from FMTU''' if unit_ids is None: return - self.unit_ids = unit_ids + # Does this unit string define an instance field? instance_idx = unit_ids.find('#') if instance_idx != -1: - self.instance_field = self.columns[instance_idx] - # work out offset and length of instance field in message - pre_fmt = self.format[:instance_idx] - pre_sfmt = "" - for c in pre_fmt: - (s, mul, type) = FORMAT_TO_STRUCT[c] - pre_sfmt += s - self.instance_ofs = struct.calcsize(pre_sfmt) - (ifmt,) = self.format[instance_idx] - self.instance_len = struct.calcsize(ifmt) - - - def set_mult_ids(self, mult_ids): - '''set mult IDs string from FMTU''' - self.mult_ids = mult_ids + self.set_instance_field(instance_idx) + # Build the units array from the IDs + self.units = [""]*len(self.columns) + for i in range(len(self.columns)): + if i < len(unit_ids): + if unit_ids[i] in unit_lookup: + self.units[i] = unit_lookup[unit_ids[i]] + + def set_mult_ids(self, mult_ids, mult_lookup): + '''apply mult IDs string from FMTU''' + # Update the units based on the multiplier + for i in range(len(self.units)): + # If the format has its own multiplier, ignore the unit one + # and just return the base unit, and if no unit is specified + # there is nothing to adjust + if self.msg_mults[i] is None and self.units[i] != "": + # Get the unit multiplier from the lookup table + if mult_ids[i] in mult_lookup: + unitmult = mult_lookup[mult_ids[i]] + # Combine the multipler and unit to derive the real unit + if unitmult in MULT_TO_PREFIX: + self.units[i] = MULT_TO_PREFIX[unitmult]+self.units[i] + else: + self.units[i] = "%.4g.%s" % (unitmult, self.units[i]) + + def get_unit(self, col): + '''Return the unit for the specified field''' + if self.units is None: + return "" + else: + idx = self.colhash[col] + return self.units[idx] def __str__(self): return ("DFFormat(%s,%s,%s,%s)" % @@ -260,15 +291,6 @@ def __str__(self): ret = ret[:-2] return ret + '}' - def get_unit(self,col): - if self.fmt.unit_ids is None and self.fmt.mult_ids is None: - return "" - i = self.fmt.colhash[col] - fmtmult = self.fmt.msg_mults[i] - unitid = self.fmt.unit_ids[i] - multid = self.fmt.mult_ids[i] - return self._parent.get_derived_unit(fmtmult,unitid,multid) - def dump_verbose(self, f): is_py3 = sys.version_info >= (3,0) timestamp = "%s.%03u" % ( @@ -290,14 +312,16 @@ def dump_verbose(self, f): except UnicodeDecodeError: f.write(" %s: %s" % (c, to_string(val))) # Append the unit to the output - unit = self.get_unit(c) - if unit.startswith("rad"): + unit = self.fmt.get_unit(c) + if unit == "": + # No unit specified - just output the newline + f.write("\n") + elif unit.startswith("rad"): # For rad or rad/s, add the degrees conversion too f.write(" %s (%s %s)\n" % (unit, math.degrees(val), unit.replace("rad","deg"))) - elif unit != "": - f.write(" %s\n" % (unit)) else: - f.write("\n") + # Append the unit + f.write(" %s\n" % (unit)) def get_msgbuf(self): '''create a binary message buffer for a message''' @@ -549,27 +573,6 @@ def __init__(self): self.unit_lookup = {} self.mult_lookup = {} - def get_derived_unit(self,fmtmult,unitid,multid): - # Get the base unit from the lookup table - if unitid in self.unit_lookup: - baseunit = self.unit_lookup[unitid] - else: - return "" - # If the format has its own multiplier, ignore the unit one - # and just return the base unit - if fmtmult is not None: - return baseunit - # Get the unit multiplier from the lookup table - if multid in self.mult_lookup: - unitmult = self.mult_lookup[multid] - else: - return baseunit - # Combine the multipler and unit to create derived unit - if unitmult in MULT_TO_PREFIX: - return MULT_TO_PREFIX[unitmult]+baseunit - else: - return "%.4g.%s" % (unitmult, baseunit) - def _rewind(self): '''reset state on rewind''' # be careful not to replace self.messages with a new hash; @@ -953,6 +956,8 @@ def init_arrays(self, progress_callback=None): if mfmt.name == 'MULT': mult_type = mfmt.type + # Handle FMTU messages by updating the DFFormat class with the + # unit/multiplier information if fmtu_type is not None and mtype == fmtu_type: fmt = self.formats[mtype] body = self.data_map[ofs+3:ofs+mlen] @@ -963,10 +968,11 @@ def init_arrays(self, progress_callback=None): if ftype in self.formats: fmt2 = self.formats[ftype] if 'UnitIds' in fmt.colhash: - fmt2.set_unit_ids(null_term(elements[fmt.colhash['UnitIds']])) + fmt2.set_unit_ids(null_term(elements[fmt.colhash['UnitIds']]), self.unit_lookup) if 'MultIds' in fmt.colhash: - fmt2.set_mult_ids(null_term(elements[fmt.colhash['MultIds']])) + fmt2.set_mult_ids(null_term(elements[fmt.colhash['MultIds']]), self.mult_lookup) + # Handle UNIT messages by updating the unit_lookup dictionary if unit_type is not None and mtype == unit_type: fmt = self.formats[mtype] body = self.data_map[ofs+3:ofs+mlen] @@ -975,14 +981,18 @@ def init_arrays(self, progress_callback=None): elements = list(struct.unpack(fmt.msg_struct, body)) self.unit_lookup[chr(elements[1])] = null_term(elements[2]) + # Handle MULT messages by updating the mult_lookup dictionary if mult_type is not None and mtype == mult_type: fmt = self.formats[mtype] body = self.data_map[ofs+3:ofs+mlen] if len(body)+3 < mlen: break elements = list(struct.unpack(fmt.msg_struct, body)) - # Round the multiplier to 7 significant figures to remove - # inaccuracies created by float/double conversions + # Even though the multiplier value is logged as a double, the + # values in log files look to be single-precision values that have + # been cast to a double. + # To ensure that the values saved here can be used to index the + # MULT_TO_PREFIX table, we round them to 7 significant decimal digits mult = float("%.7g" % (elements[2])) self.mult_lookup[chr(elements[1])] = mult @@ -1150,8 +1160,8 @@ def _parse_next(self): MultIds = elements[2] if FmtType in self.formats: fmt = self.formats[FmtType] - fmt.set_unit_ids(UnitIds) - fmt.set_mult_ids(MultIds) + fmt.set_unit_ids(UnitIds, self.unit_lookup) + fmt.set_mult_ids(MultIds, self.mult_lookup) try: self._add_msg(m) @@ -1391,8 +1401,8 @@ def _parse_next(self): fmtid = getattr(m, 'FmtType', None) if fmtid is not None and fmtid in self.id_to_name: fmtu = self.formats[self.id_to_name[fmtid]] - fmtu.set_unit_ids(getattr(m, 'UnitIds', None)) - fmtu.set_mult_ids(getattr(m, 'MultIds', None)) + fmtu.set_unit_ids(getattr(m, 'UnitIds', None), self.unit_lookup) + fmtu.set_mult_ids(getattr(m, 'MultIds', None), self.mult_lookup) if m.get_type() == 'UNIT': unitid = getattr(m, 'Id', None) @@ -1402,8 +1412,11 @@ def _parse_next(self): if m.get_type() == 'MULT': multid = getattr(m, 'Id', None) mult = getattr(m, 'Mult', None) - # Round the multiplier to 7 significant figures to remove - # inaccuracies created by float/double conversions + # Even though the multiplier value is logged as a double, the + # values in log files look to be single-precision values that have + # been cast to a double. + # To ensure that the values saved here can be used to index the + # MULT_TO_PREFIX table, we round them to 7 significant decimal digits mult = float("%.7g" % (mult)) self.mult_lookup[chr(multid)] = mult