From 3c97b96435cb62a6b99a3a408e941aaf5c43b41b Mon Sep 17 00:00:00 2001 From: BlueFox Date: Fri, 15 Nov 2024 15:31:37 +0100 Subject: [PATCH] Moved to json as the configuration format --- config.json | 18 +++++++++ config.py | 65 ------------------------------- input_tests.py | 9 +++-- main.py | 88 ++++++++++++++++++++++-------------------- utils.py | 101 ++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 168 insertions(+), 113 deletions(-) create mode 100644 config.json delete mode 100644 config.py diff --git a/config.json b/config.json new file mode 100644 index 0000000..35007ff --- /dev/null +++ b/config.json @@ -0,0 +1,18 @@ +{ +"LOG_LEVEL": 2, +"STARTUP_WELCOME_SHOW": true, +"STARTUP_PROJECT_NAME":" UV-Belichter ", +"STARTUP_MESSAGE_STARTING": "Starting...", +"STARTUP_MESSAGE_FINISHED": " Started! ", +"STARTUP_WELCOME_CYCLES": 1, +"PIN_IN_BTN_1": {"pin": 15, "pull": "down"}, +"PIN_IN_BTN_2": {"pin": 14, "pull": "down"}, +"PIN_IN_SWITCH": {"pin": 13, "pull": "down"}, +"PIN_OUT_RELAIS": 21, +"PIN_SDA": 8, +"PIN_SCL": 9, +"LCD_I2C_CH": 0, +"LCD_I2C_ADDR": 39, +"LCD_I2C_NUM_ROWS": 2, +"LCD_I2C_NUM_COLS": 16, +} \ No newline at end of file diff --git a/config.py b/config.py deleted file mode 100644 index e1a870e..0000000 --- a/config.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -uv-belichter-software - Configuration file -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 . -""" - - -""" ---------------------------- ------ LOGGING SECTION ----- ---------------------------- -""" -LOG_LEVEL = 2 # 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 SECTION ----- ---------------------------- -""" -STARTUP_PROJECT_NAME = " UV-Belichter " # the name to show at startup -STARTUP_MESSAGE_STARTING = "Starting..." # the message to show at startup -STARTUP_MESSAGE_FINISHED = " Started! " # the message to show at startup -STARTUP_WELCOME_SHOW = True # show the name and a startup message -STARTUP_WELCOME_CYCLES = 1 # how often shall "Starting..." run over the screen - - -""" --------------------------- ------ PINOUT SECTION ----- --------------------------- -""" -from machine import Pin - -BTN_1 = Pin(15, Pin.IN, Pin.PULL_DOWN) # input of the first btn -BTN_2 = Pin(14, Pin.IN, Pin.PULL_DOWN) # input of the second btn -SWITCH = Pin(13, Pin.IN, Pin.PULL_DOWN) # input of switch -LCD_SDA = Pin(8) # just some standard I2C serial data (SDA) outputs (on I2C channel 0 on Pi Pico) -LCD_SCL = Pin(9) # just some standard I2C serial clock (SCL) outputs (on I2C channel 0 on Pi Pico) -#LCD_SDA = Pin(16) # another pinout (soldered on the original project's circuit board) -#LCD_SCL = Pin(17) # another pinout (soldered on the original project's circuit board) -RELAIS = Pin(21, Pin.OUT) # where the relais is connected (for the UV lights) - - -""" ------------------------ ------ LCD SECTION ----- ------------------------ -""" -from machine import I2C, Pin -from lib.PCF8574T import I2C_LCD - -LCD_I2C_ADDR = 0x27 # the i2c adress of the display (yours might be different to this one) -LCD_I2C_NUM_ROWS = 2 # how many rows for character display has the display? -LCD_I2C_NUM_COLS = 16 # and how many characters can it display per row? -LCD = I2C_LCD(I2C(0, sda=LCD_SDA, scl=LCD_SCL, freq=400000), - LCD_I2C_ADDR, - LCD_I2C_NUM_ROWS, - LCD_I2C_NUM_COLS) \ No newline at end of file diff --git a/input_tests.py b/input_tests.py index 1450fad..bf38ba3 100644 --- a/input_tests.py +++ b/input_tests.py @@ -9,13 +9,14 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY You should have received a copy of the GNU General Public License along with this program. If not, see . """ -import config as cfg +from utils import Config from time import sleep +cfg = Config() def run(endless_loop: bool = True, serial_output: bool = True): while endless_loop: - if cfg.BTN_1.value() and cfg.BTN_2.value() and cfg.SWITCH.value(): + if cfg.PIN_IN_BTN_1.value() and cfg.PIN_IN_BTN_2.value() and cfg.PIN_IN_SWITCH.value(): cfg.LCD.move_to(0,0) cfg.LCD.putstr("In: Y1 | G1 | S1 Exiting! ") if serial_output: @@ -23,9 +24,9 @@ def run(endless_loop: bool = True, serial_output: bool = True): sleep(0.2) break cfg.LCD.move_to(0,0) - cfg.LCD.putstr(f"In: Y{cfg.BTN_1.value()} | G{cfg.BTN_2.value()} | S{cfg.SWITCH.value()}Push all to exit") + cfg.LCD.putstr(f"In: Y{cfg.PIN_IN_BTN_1.value()} | G{cfg.PIN_IN_BTN_2.value()} | S{cfg.PIN_IN_SWITCH.value()}Push all to exit") if serial_output: - print(f"Y_BTN: {cfg.BTN_1.value()}; G_BTN: {cfg.BTN_2.value()}; Lever: {cfg.SWITCH.value()}") + print(f"Y_BTN: {cfg.PIN_IN_BTN_1.value()}; G_BTN: {cfg.PIN_IN_BTN_2.value()}; Lever: {cfg.PIN_IN_SWITCH.value()}") sleep(0.05) if __name__ == "__main__": diff --git a/main.py b/main.py index e6d5d51..665fc74 100644 --- a/main.py +++ b/main.py @@ -14,83 +14,87 @@ import utils from lcdMenu import lcdMenu from WelcomeScreen import WelcomeScreen from time import sleep -import gc # garbage collector for better memory performance +from gc import collect # garbage collector for better memory performance +config = utils.Config() +btn_mapping = {"ok_btn": config.PIN_IN_BTN_1, "next_btn": config.PIN_IN_BTN_2} # the btn mapping for all lcdMenus # extra functions to access the garbage collector def manual(): - utils.cfg.LCD.clear() - set_value = utils.cfg.RELAIS.value() - utils.cfg.LCD.putstr(f"---- MANUAL ---- State: {set_value} ") + config.LCD.clear() + set_value = config.PIN_OUT_RELAIS.value() + config.LCD.putstr(f"---- MANUAL ---- State: {set_value} ") while True: - if set_value != utils.cfg.SWITCH.value(): - utils.cfg.RELAIS.value(utils.cfg.SWITCH.value()) - set_value = utils.cfg.RELAIS.value() - utils.cfg.LCD.putstr(f"---- MANUAL ---- State: {set_value} ") - if utils.cfg.BTN_1.value() == 1 or utils.cfg.BTN_2.value() == 1: + if set_value != config.PIN_IN_SWITCH.value(): + config.PIN_OUT_RELAIS.value(config.PIN_IN_SWITCH.value()) + set_value = config.PIN_OUT_RELAIS.value() + config.LCD.putstr(f"---- MANUAL ---- State: {set_value} ") + if config.PIN_IN_BTN_1.value() == 1 or config.PIN_IN_BTN_2.value() == 1: return True # exit on press of these buttons def timer(): # display WIP - utils.cfg.LCD.clear() - utils.cfg.LCD.putstr(" Still work-in-progress") + config.LCD.clear() + config.LCD.putstr(" Still work-in-progress") sleep(3) return True # disable the "Quitting" message from lcdMenu def uv_on(): - utils.cfg.RELAIS.value(1) - utils.cfg.LCD.clear() - utils.cfg.LCD.putstr("------ UV ------ turned on ") + config.RELAIS.value(1) + config.LCD.clear() + config.LCD.putstr("------ UV ------ turned on ") sleep(1) return True # disable the "Quitting" message from lcdMenu def uv_off(): - utils.cfg.RELAIS.value(0) - utils.cfg.LCD.clear() - utils.cfg.LCD.putstr("------ UV ------ turned off ") + config.RELAIS.value(0) + config.LCD.clear() + config.LCD.putstr("------ UV ------ turned off ") sleep(1) return True # disable the "Quitting" message from lcdMenu def lcd_big_hello(): - import lcd_big_hello - lcd_big_hello.run() - gc.collect() + import lcd_big_hello as lbh + lbh.run() + del lbh + collect() return True def input_tests(): import input_tests as input_tests input_tests.run(serial_output=False) - gc.collect() + collect() return True def settings(): # display WIP - utils.cfg.LCD.clear() - utils.cfg.LCD.putstr(" Still work-in-progress") + config.LCD.clear() + config.LCD.putstr(" Still work-in-progress") sleep(3) return True +def run_demo_menu(): + demo_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="DEMOS") + demo_programs = [("LCD Demo", lcd_big_hello), + ("Input tests", input_tests), + ("Exit", demo_menu.stop)] + demo_menu.setup(demo_programs) # give it the callback list + ret = demo_menu.run() + del demo_menu, demo_programs + collect() + return ret - -if utils.cfg.STARTUP_WELCOME_SHOW: - ws = WelcomeScreen(utils.cfg.LCD, - interrupt_pins=[utils.cfg.BTN_1, utils.cfg.BTN_2, utils.cfg.SWITCH], - subtitle=utils.cfg.STARTUP_PROJECT_NAME, - starting_msg=utils.cfg.STARTUP_MESSAGE_STARTING, - started_msg=utils.cfg.STARTUP_MESSAGE_FINISHED) - ws.show(cycles=utils.cfg.STARTUP_WELCOME_CYCLES) +if config.STARTUP_WELCOME_SHOW: + ws = WelcomeScreen(config.LCD, + interrupt_pins=[config.PIN_IN_BTN_1, config.PIN_IN_BTN_2, config.PIN_IN_SWITCH], + subtitle=config.STARTUP_PROJECT_NAME, + starting_msg=config.STARTUP_MESSAGE_STARTING, + started_msg=config.STARTUP_MESSAGE_FINISHED) + ws.show(cycles=config.STARTUP_WELCOME_CYCLES) del ws - gc.collect() + collect() # create the menus -btn_mapping = {"ok_btn": utils.cfg.BTN_1, "next_btn": utils.cfg.BTN_2} # the btn mapping for all menus - -demo_menu = lcdMenu(utils.cfg.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="DEMOS") -demo_programs = [("LCD Demo", lcd_big_hello), - ("Input tests", input_tests), - ("Exit", demo_menu.stop)] -demo_menu.setup(demo_programs) # give it the callback list - -main_menu = lcdMenu(utils.cfg.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="PROGRAMS") +main_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="PROGRAMS") main_programs = [("Timer", timer), ("Manual", manual), ("UV off", uv_off), ("UV on", uv_on), - ("Demos", demo_menu.run), + ("Demos", run_demo_menu), ("Settings", settings)] main_menu.setup(main_programs) # give it the callback list diff --git a/utils.py b/utils.py index 2ff585d..ad5b76c 100644 --- a/utils.py +++ b/utils.py @@ -9,9 +9,106 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY You should have received a copy of the GNU General Public License along with this program. If not, see . """ +from gc import collect -import config as cfg -from time import sleep +""" +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) + 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) + 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): + #print(f"Someone tried to edit my poor attributes! Affected: '{name}' should be set to '{value}'") + object.__setattr__(self, name, value) + + def __delattr__(self, name): + raise AttributeError(f"You may not delete any attribute of the '{self.__class__.__name__}' object") + +cfg = Config() """