import configparser import sys from pathlib import Path from jinja2 import Environment, FileSystemLoader from .MsgData import MsgData # \brief Generates and maintains the per-message LeahiRt routing INI from loaded message data. # \details One section per message keyed by hex msgId (e.g. [0x0100]), carrying: # msg_id - the MSG_ID_* reference name, regenerated from the conf on every run. # handling - send_always | send_delta | drop; defaults to drop, preserved across runs. # topic - MQTT topic; empty by default, preserved across runs. # Call loadConf() to load message definitions, then loadIni() to merge in existing # handling/topic values, then write_ini() to write the result. class MsgIni(MsgData): DEFAULT_HANDLING = 'drop' VALID_HANDLING = ('send_always', 'send_delta', 'drop') # \brief Initializer def __init__(self): super().__init__() # \brief Load a .conf file and cache the data, enriching each entry with default INI fields. # \param[in] filename Filename of the .conf to load. # \param[in] clear If true, clear any previously loaded data before processing. # \return none def loadConf(self, filename, clear=True): super().loadConf(filename, clear) for msg in self.data.values(): msg['handling'] = self.DEFAULT_HANDLING msg['topic'] = '' # \brief Merge handling/topic values from an existing INI into the loaded message data. # \details Only handling and topic are read; msg_id is always regenerated from the conf. # Sections in the INI that are no longer in the conf are ignored (and warned if # they carried non-default values). Unknown handling values are reset to drop. # \param[in] filename Path to the existing INI file; no-op if the file does not exist. # \return none def loadIni(self, filename): path = Path(filename) if not path.is_file(): return cfg = configparser.ConfigParser(inline_comment_prefixes=(';',)) cfg.read(path, encoding='utf-8') # just use self.data.keys() conf_keys = set(MsgData.value_to_hex_string(v) for v in self.data.keys()) for section in cfg.sections(): try: msg_id_value = int(section, 16) except ValueError: print(f"WARNING: could not convert section message ID {section} to valid message ID, skipping") continue if msg_id_value not in self.data.keys(): handling = cfg.get(section, 'handling', fallback=self.DEFAULT_HANDLING).strip() topic = cfg.get(section, 'topic', fallback='').strip() if handling != self.DEFAULT_HANDLING or topic: print(f"WARNING: {MsgData.value_to_hex_string(msg_id_value)} no longer in Unhandled.conf, " f"dropping section with handling={handling} topic={topic or ''}") continue handling = cfg.get(section, 'handling', fallback=self.DEFAULT_HANDLING).strip() if handling not in self.VALID_HANDLING: print(f"WARNING: {MsgData.value_to_hex_string(msg_id_value)} has invalid handling \"{handling}\", " f"resetting to {self.DEFAULT_HANDLING}") handling = self.DEFAULT_HANDLING self.data[msg_id_value]['handling'] = handling self.data[msg_id_value]['topic'] = cfg.get(section, 'topic', fallback='').strip() # \brief Write the loaded message data to the INI file. # \param[in] filename Path to the INI file to create or update. # \return none def write_ini(self, filename): env = Environment(loader=FileSystemLoader(f'{Path(__file__).parent.absolute()}/templates'), keep_trailing_newline=True) template = env.get_template('MsgIni.jinja') render = template.render({'msg_ini': self}) with open(filename, mode='w', encoding='utf-8', newline='\n') as out_file: out_file.write(render) print(f"Wrote message INI {filename} with {len(self.data)} messages")