""" uv-belichter-software - Some utilities for better customization and modularization Copyright (C) 2024 Benjamin Burkhardt This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ from gc import collect """ A small wrapper class as storing machine.Pin, LCD and machine.I2C objects in a file is not that cool :) Now only pin numbers and strings etc. are stored on the uC, and the complex objects are generated on the fly """ class Config: """ The initializer method - config_file: the path to the config file laying on the uC """ def __init__(self, config_file: str = "config.json"): self._attr_list = ["LOG_LEVEL", # there are three log levels: warn (0), info (1), debug (2) # this value defines which log messages to show # e.g. 2 means show [debug], [warn] and [info] messages "STARTUP_WELCOME_SHOW", # show the name and a startup message "STARTUP_PROJECT_NAME", # the name to show at startup "STARTUP_MESSAGE_STARTING", # the message to show at startup "STARTUP_MESSAGE_FINISHED", # the message to show at startup "STARTUP_WELCOME_CYCLES", # how often shall "Starting..." run over the screen "PIN_IN_BTN_1", # input of the first btn "PIN_IN_BTN_2", # input of the second btn "PIN_IN_SWITCH", # input of the switch "PIN_OUT_RELAIS", # where the relais is connected (for the UV lights) "PIN_SDA", # just some standard I2C serial data (SDA) output "PIN_SCL", # just some standard I2C serial clock (SCL) output "LCD_I2C_CH", # where the relais is connected (for the UV lights) "LCD_I2C_ADDR", # the i2c adress of the display "LCD_I2C_NUM_ROWS", # how many rows for character display has the display? "LCD_I2C_NUM_COLS", # and how many characters can it display per row? "LCD", # the actual lcd object (of the PCF8574T I2C_LCD class, see libraries) "TIMER_1_DURATION", # the duration of the first timer in seconds "TIMER_2_DURATION", # the duration of the second first timer in seconds "TIMER_3_DURATION"] # the duration of the third timer in seconds self._config_file = config_file self.load_config() def load_config(self): # prepare the class with open(self._config_file, "r") as f: from json import load self._config = load(f) del load collect() def save_config(self): with open(self._config_file, "w") as f: from json import dump dump(self._config, f, separators=(',\n', ': ')) del dump collect() def __getattr__(self, name): if name.startswith("_"): # make private attributes unaccessible raise AttributeError(f"'Access to the private attribute '{name}' of the object '{self.__class__.__name__}' is forbidden") elif name in self._attr_list: # valid attributes (only capital letters and -_ etc. are allowed) try: # now some if statements to check if the lcd or some pin object is asked if name.startswith("PIN_"): from machine import Pin if name.startswith("PIN_IN"): if self._config[name]["pull"].lower() == "down": p = Pin(self._config[name]["pin"], Pin.IN, Pin.PULL_DOWN) elif self._config[name]["pull"].lower() == "up": p = Pin(self._config[name]["pin"], Pin.IN, Pin.PULL_UP) else: p = Pin(self._config[name]["pin"], Pin.IN) elif name.startswith("PIN_OUT"): p = Pin(self._config[name], Pin.OUT) else: p = Pin(self._config[name]) del Pin collect() return p elif name == "LCD": try: return self._lcd except: from machine import I2C, Pin from PCF8574T import I2C_LCD self._lcd = I2C_LCD(I2C(self.LCD_I2C_CH, sda=self.PIN_SDA, scl=self.PIN_SCL, freq=400000), self.LCD_I2C_ADDR, self.LCD_I2C_NUM_ROWS, self.LCD_I2C_NUM_COLS) del I2C, Pin, I2C_LCD collect() return self._lcd return self._config[name] except KeyError: raise AttributeError(f"Attribute '{name}' does not exist in the config file '{self._config_file}'") else: raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") def __setattr__(self, name, value): if name.startswith("_"): # make private attributes settable as normal object.__setattr__(self, name, value) elif name in self._attr_list: # valid attributes (only capital letters and -_ etc. are allowed) try: self._config[name] = value self.save_config() except KeyError: raise AttributeError(f"Attribute '{name}' does not exist in the config file '{self._config_file}'") else: raise AttributeError(f"Can't set attribute '{name}' for a '{self.__class__.__name__}' object: forbidden") def __delattr__(self, name): raise AttributeError(f"You may not delete any attribute of the '{self.__class__.__name__}' object") """ Very simple logging function Overall log level can be specified in config.py """ def log(log_level: int, message: str): log_mapping = {0: "WARN", 1: "INFO", 2: "DEBUG"} log_level = int(log_level) # make sure log_level is an integer if log_level not in [0, 1, 2]: print(f"[LOGGER] Got a message of unknown log level ({log_level}). Original message is printed below.") print(f"{message}") elif Config().LOG_LEVEL >= log_level: # if log level is valid print(f"[{log_mapping[log_level]}] {message}")