Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
a97dd88a67 | |||
92ebbd2ef8 | |||
68a588de58 | |||
91a58cca61 | |||
002b01d572 | |||
aba5927221 | |||
a6b531c394 | |||
72bc662cb8 | |||
e1075f8d84 | |||
908a5e39be | |||
5ce8e592c3 | |||
ddde9480ff | |||
c7a8cda7d1 | |||
7adcdb1624 | |||
f0d6b49d6e | |||
a4adaac9b4 | |||
78ced9e7ea | |||
eb31a92cea | |||
fd2a4117b3 | |||
864a833a43 | |||
678ebfe7aa | |||
9d395bd2b8 | |||
78933f6090 | |||
f48315f18e | |||
2aac36e15c | |||
aa3110d564 | |||
f03cc4a44c | |||
cbc5298cda | |||
98353475ea | |||
7443af2095 | |||
20331b81bc | |||
eef6f8e2cc | |||
3c97b96435 |
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "lib/lcdMenu"]
|
||||
path = lib/lcdMenu
|
||||
url = https://git.privacynerd.de/BlueFox/lcdMenu.git
|
98
README.md
98
README.md
@ -2,18 +2,20 @@
|
||||
|
||||
A collection of programs run on a Raspberry Pi Pico to control a uv exposure unit (mainly used for exposing PCBs)
|
||||
|
||||
![The front of the exposure unit](cover.png)
|
||||
|
||||
|
||||
## Hardware
|
||||
|
||||
This program is strongly customized to my needs, and my DIY exposure unit has **two buttons** and **one switch** to interact with the software (and a power switch FWIW). Also, a **16*2 display** (maybe 20*4 or others do also work, but these are not tested) can show information to the user.
|
||||
This software is strongly customized to my needs, and my DIY exposure unit has **two buttons** and **one switch** to interact with the software (and a power switch FWIW). Also, a **16x2 display** can show information to the user.
|
||||
A **relais** is used for switching all the LEDs.
|
||||
|
||||
| Device Pin | Pi Pico Pin |
|
||||
| ------------------ | ----------- |
|
||||
| BTN_1 Pin 1 | 3.3V |
|
||||
| BTN_1 Pin 2 | GPIO15 |
|
||||
| BTN_2 Pin 1 | 3.3V |
|
||||
| BTN_2 Pin 2 | GPIO14 |
|
||||
| BTN\_1 Pin 1 | 3.3V |
|
||||
| BTN\_1 Pin 2 | GPIO15 |
|
||||
| BTN\_2 Pin 1 | 3.3V |
|
||||
| BTN\_2 Pin | GPIO14 |
|
||||
| SWITCH Pin 1 | 3.3V |
|
||||
| SWITCH Pin 2 | GPIO13 |
|
||||
| LCD SDA | GPIO8 |
|
||||
@ -23,26 +25,98 @@ A **relais** is used for switching all the LEDs.
|
||||
| Relais control pin | GPIO21 |
|
||||
|
||||
|
||||
## Installation
|
||||
## Software installation
|
||||
|
||||
To install this, use [Thonny](https://thonny.org/), open all the files present in this repository and then save them onto a [Raspberry Pi Pico](https://www.raspberrypi.com/products/raspberry-pi-pico/) (or [Pico 2](https://www.raspberrypi.com/products/raspberry-pi-pico-2/), but it's not tested on this platform yet) running [MicroPython](https://micropython.org/). Then, given you followed above wiring, it should just be running! Then you can jump over the configuration section.
|
||||
To install this software on your Pi Pico, first clone the repository with `git clone --recurse-submodules` to populate the submodules also (some libraries are included as submodules). Then use [Thonny](https://thonny.org/), open all the files present in this repository and then save them onto a [Raspberry Pi Pico](https://www.raspberrypi.com/products/raspberry-pi-pico/) (or [Pico 2](https://www.raspberrypi.com/products/raspberry-pi-pico-2/), but it's not tested on this platform yet) running [MicroPython](https://micropython.org/). Then, given you followed above wiring, it should just be running! Then you can jump over the configuration section.
|
||||
|
||||
|
||||
## Configuration
|
||||
## First configuration
|
||||
|
||||
All the configuration can be done in the [config.py](config.py) file, just have a look around there. Some hints for editing this file:
|
||||
All the configuration can be done in the [config.json](config.json) file in JSON format just have a look around there. Some hints for editing this file:
|
||||
|
||||
- When editing the startup section strings, make sure the "STARTUP_PROJECT_NAME" and the "STARTUP_MESSAGE_FINISHED" values have the same length as your display can show (likely 16 characters). Otherwise it could be that some things won't be displayed correctly.
|
||||
- For a description of all of the attributes, see below
|
||||
|
||||
- When editing the startup section strings, make sure the "STARTUP\_PROJECT\_NAME" and the "STARTUP\_MESSAGE\_FINISHED" values have the same length as your display can show (likely 16 characters). Otherwise it could be that some things won't be displayed correctly.
|
||||
|
||||
- When changing Pins in the Pinout section, make sure to follow the pinout assignment of your Pi Pico board (e.g. the i2c sda and scl pins)
|
||||
|
||||
- If your display doesn't work properly - the first issue might be a wrong i2c address. To find the address of your display, just follow some of the tutorials on the internet on scanning for i2c devices (e.g. [here](https://randomnerdtutorials.com/raspberry-pi-pico-i2c-scanner-micropython/)).
|
||||
|
||||
- The most basic configuration changes can be made directly from the device, without the need of connecting it to a PC, essentially making it a kind-of standalone device once flashed!
|
||||
|
||||
|
||||
### Attribute table
|
||||
|
||||
| Attribute name (on top level in config.json) | Type | Description | Default |
|
||||
| -------------------------------------------- | ---- | ----------- | ------- |
|
||||
| `"LOG_LEVEL"` | int | defines up to which log level to show log messages in the serial console: warn (0), info (1), debug (2) | `1` |
|
||||
| `"STARTUP_WELCOME_SHOW"` | bool | show the startup screen? | `true` |
|
||||
| `"STARTUP_PROJECT_NAME"` | str | the name shown at the welcome/startup screen | `" UV-Belichter "` |
|
||||
| `"STARTUP_MESSAGE_STARTING"` | str | the message shown at startup when starting | `"Starting..."` |
|
||||
| `"STARTUP_PROJECT_FINISHED"` | str | the message shown at startup when finished | `" Started! "` |
|
||||
| `"STARTUP_WELCOME_CYCLES"` | int | how often the starting string shall go over the welcome screen | `1` |
|
||||
| `"PIN_IN_BTN_1"` | dict | the dict must contain the "pin" and "pull" keys with respective values | `{"pin": 15, "pull": "down"}` |
|
||||
| `"PIN_IN_BTN_2"` | dict | as above | `{"pin": 14, "pull": "down"}` |
|
||||
| `"PIN_IN_SWITCH"` | dict | as above | `{"pin": 13, "pull": "down"}` |
|
||||
| `"PIN_OUT_RELAIS"` | int | pin number where the relais is connected | `21` |
|
||||
| `"PIN_SDA"` | int | the pin number of the sda wire connected to the lcd | `8` |
|
||||
| `"PIN_SCL"` | int | the pin number of the scl wire connected to the lcd | `9` |
|
||||
| `"LCD_I2C_CH"` | int | the channel of the i2c bus used | `0` |
|
||||
| `"LCD_I2C_ADDR"` | int | the i2c address of the lcd; make sure to convert hexadecimal to decimal numbers | `38` |
|
||||
| `"LCD_I2C_NUM_ROWS"` | int | how many rows for character display has the display? | `2` |
|
||||
| `"LCD_I2C_NUM_COLS"` | int | and how many characters can it display per row? | `16` |
|
||||
| `"TIMER_1_DURATION"` | int | the timer duration of the first timer; IN SECONDS | `60` (1min) |
|
||||
| `"TIMER_2_DURATION"` | int | as above, but of the seconds timer; IN SECONDS | `2400` (40min) |
|
||||
| `"TIMER_3_DURATION"` | int | as above, but of the third timer; IN SECONDS | `2700` (45min) |
|
||||
|
||||
Note that this software has it's own small wrapper for the config file, e.g. to have instant access to an LCD object generated on the fly. These are all documented in the [utils.py](utils.py) file! When setting configuration options from your custom code, keep in mind that doing this via the `Config().<ATTR_NAME>` way just means writing the value directly to the file, while getting it goes through the wrapper to make e.g. the pin a machine.Pin object. But you just can't write a pin back into an attribute.
|
||||
|
||||
This will NOT work:
|
||||
|
||||
```python
|
||||
from utils import Config
|
||||
from machine import Pin
|
||||
cfg = Config()
|
||||
|
||||
btn1 = cfg.PIN_IN_BTN_1
|
||||
|
||||
# Now we don't like this setting anymore
|
||||
new_btn1 = Pin(10, Pin.IN, Pin.PULL_DOWN)
|
||||
cfg.PIN_IN_BTN_1 = new_btn1
|
||||
```
|
||||
|
||||
Instead, you have to do it that way:
|
||||
|
||||
```python
|
||||
from utils import Config
|
||||
from machine import Pin
|
||||
cfg = Config
|
||||
|
||||
btn1 = cfg.PIN_IN_BTN_1
|
||||
|
||||
# Now we don't like it so we write the new pin in our json format
|
||||
cfg.PIN_IN_BTN_1 = {"pin": 10, "pull": "down"}
|
||||
```
|
||||
|
||||
So, as the `Config` class uses python's magic function for getting and setting attributes, the process of changing some config is not completely intuitive, but when keeping that in mind, it's very handy!
|
||||
|
||||
|
||||
## Used libraries
|
||||
|
||||
- PCF8574T - a driver for the i2c multiplexer used to address the 2x16 lcd display
|
||||
- [ProgramChooserAdapted](https://git.privacynerd.de/BlueFox/ProgramChooser) - an adapted version of ProgramChooser (but I'm planning to merge my changes to the upstream Programchooser)
|
||||
- [PCF8574T](https://git.privacynerd.de/BlueFox/micropython-libraries/src/branch/main/PCF8574T) - a driver for the i2c multiplexer used to address the 2x16 lcd display
|
||||
- [WelcomeScreen](https://git.privacynerd.de/BlueFox/WelcomeScreen) - used to display a small welcome message in the beginning
|
||||
- [lcdMenu](https://git.privacynerd.de/BlueFox/lcdMenu) - used for the menus
|
||||
|
||||
|
||||
## Learning curve
|
||||
|
||||
Here are some of the websites I learnt a lot from while programming this project - mainly here for documentation reasons.
|
||||
|
||||
- [A handy guide into configuration files in json](https://bhave.sh/micropython-json-config/)
|
||||
- [The official micropython wiki page about the same topic](https://docs.micropython.org/en/latest/library/json.html)
|
||||
- [StackOverflow thread about how to format a json file (used by this project to make it a bit more readable)](https://stackoverflow.com/questions/16311562/python-json-without-whitespaces)
|
||||
- [A guide about magic functions for settings and getting attributes](https://staskoltsov.medium.com/python-magic-methods-to-get-set-attributes-716a12d0b106)
|
||||
- [The official guide into git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules)
|
||||
|
||||
|
||||
## License
|
||||
|
21
config.json
Normal file
21
config.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"LOG_LEVEL": 1,
|
||||
"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,
|
||||
"TIMER_1_DURATION": 60,
|
||||
"TIMER_2_DURATION": 2400,
|
||||
"TIMER_3_DURATION": 2700,
|
||||
}
|
65
config.py
65
config.py
@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
"""
|
||||
---------------------------
|
||||
----- 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)
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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__":
|
@ -14,26 +14,13 @@ You should have received a copy of the GNU General Public License along with thi
|
||||
@ Feature: Fades "HELLO" over two lines in and out
|
||||
"""
|
||||
|
||||
import machine
|
||||
import time
|
||||
import config
|
||||
|
||||
lcd = config.LCD
|
||||
|
||||
lcd.custom_char(0, bytearray([0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03])) # right
|
||||
lcd.custom_char(1, bytearray([0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18])) # left
|
||||
lcd.custom_char(2, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x1F])) # 2-down
|
||||
lcd.custom_char(3, bytearray([0x1F,0x1F,0x00,0x00,0x00,0x00,0x00,0x00])) # 2-up
|
||||
lcd.custom_char(4, bytearray([0x1F,0x1F,0x00,0x00,0x00,0x00,0x00,0x1F])) # e-up
|
||||
lcd.custom_char(5, bytearray([0x1F,0x00,0x00,0x00,0x00,0x00,0x1F,0x1F])) # e-down
|
||||
lcd.custom_char(6, bytearray([0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x1F])) # l-down
|
||||
lcd.custom_char(7, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18])) # l-right
|
||||
|
||||
line1 = str(chr(0) + chr(2) + chr(1) + chr(0) + chr(4) + " " + chr(1) + " " + chr(1) + " " + chr(0) + chr(3) + chr(1))
|
||||
line2 = str(chr(0) + chr(3) + chr(1) + chr(0) + chr(5) + " " + chr(6) + chr(7) + chr(6) + chr(7) + chr(0) + chr(2) + chr(1))
|
||||
|
||||
|
||||
def right2left(line1, line2, speed=0.3):
|
||||
def right2left(lcd, line1, line2, speed=0.3):
|
||||
padding = " " # 16 spaces
|
||||
line2 = padding + line2 + padding
|
||||
line1 = padding + line1 + padding
|
||||
@ -44,7 +31,7 @@ def right2left(line1, line2, speed=0.3):
|
||||
time.sleep(speed)
|
||||
|
||||
|
||||
def top2bottom(line1, line2, speed=0.2):
|
||||
def top2bottom(lcd, line1, line2, speed=0.2):
|
||||
lcd.clear()
|
||||
time.sleep(speed)
|
||||
lcd.putstr(line2)
|
||||
@ -61,7 +48,7 @@ def top2bottom(line1, line2, speed=0.2):
|
||||
lcd.clear()
|
||||
time.sleep(speed)
|
||||
|
||||
def showAll(waitAfter=0.5):
|
||||
def showAll(lcd, waitAfter=0.5):
|
||||
lcd.clear()
|
||||
lcd.putstr(line1)
|
||||
lcd.move_to(0,1)
|
||||
@ -69,11 +56,21 @@ def showAll(waitAfter=0.5):
|
||||
time.sleep(waitAfter)
|
||||
|
||||
|
||||
def run(): # for the ProgramChooser as callback
|
||||
right2left(line1, line2, 0.2)
|
||||
top2bottom(line1, line2, 0.2)
|
||||
showAll()
|
||||
|
||||
def run(lcd): # for the ProgramChooser as callback
|
||||
lcd.custom_char(0, bytearray([0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03])) # right
|
||||
lcd.custom_char(1, bytearray([0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18])) # left
|
||||
lcd.custom_char(2, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x1F])) # 2-down
|
||||
lcd.custom_char(3, bytearray([0x1F,0x1F,0x00,0x00,0x00,0x00,0x00,0x00])) # 2-up
|
||||
lcd.custom_char(4, bytearray([0x1F,0x1F,0x00,0x00,0x00,0x00,0x00,0x1F])) # e-up
|
||||
lcd.custom_char(5, bytearray([0x1F,0x00,0x00,0x00,0x00,0x00,0x1F,0x1F])) # e-down
|
||||
lcd.custom_char(6, bytearray([0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x1F])) # l-down
|
||||
lcd.custom_char(7, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18])) # l-right
|
||||
right2left(lcd, line1, line2, 0.2)
|
||||
top2bottom(lcd, line1, line2, 0.2)
|
||||
showAll(lcd)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run() # run once
|
||||
from utils import Config
|
||||
cfg = Config()
|
||||
run(cfg.LCD) # run once
|
64
demos/lcd_driver_demo.py
Normal file
64
demos/lcd_driver_demo.py
Normal file
@ -0,0 +1,64 @@
|
||||
"""
|
||||
An example "program" which can be used with the lcdMenu library on a 2x16 display (with some tweaks also on 4x20 displays)
|
||||
Copyright (C) 2024 Benjamin Burkhardt <bluefox@privacynerd.de>
|
||||
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
|
||||
"""
|
||||
@ Feature: Demonstrates the lcd driver's functionality
|
||||
"""
|
||||
|
||||
from time import sleep
|
||||
from utils import log
|
||||
|
||||
# lcd is an object of the I2C_LCD class (https://git.privacynerd.de/BlueFox/micropython-libraries/src/branch/main/PCF8574T)
|
||||
def run(lcd):
|
||||
# Show off basic functionality of the lcd driver
|
||||
log(2, "Running the lcd driver demo")
|
||||
|
||||
lcd.clear()
|
||||
lcd.putstr("Driver demo".center(16)+"running...".center(16))
|
||||
sleep(1)
|
||||
lcd.clear()
|
||||
lcd.putstr("Lorem ipsum dolor sit amet")
|
||||
sleep(1)
|
||||
lcd.show_cursor()
|
||||
sleep(1)
|
||||
lcd.hide_cursor()
|
||||
sleep(1)
|
||||
lcd.blink_cursor_on()
|
||||
sleep(1)
|
||||
lcd.blink_cursor_off()
|
||||
sleep(1)
|
||||
lcd.backlight_off()
|
||||
sleep(1)
|
||||
lcd.backlight_on()
|
||||
sleep(1)
|
||||
lcd.display_off()
|
||||
sleep(1)
|
||||
lcd.display_on()
|
||||
sleep(1)
|
||||
lcd.clear()
|
||||
sleep(1)
|
||||
|
||||
s = ""
|
||||
for x in range(32, 127):
|
||||
s += chr(x)
|
||||
while len(s) > 0:
|
||||
lcd.clear()
|
||||
lcd.move_to(0,0)
|
||||
lcd.putstr(s[:32])
|
||||
s = s[32:]
|
||||
sleep(1)
|
||||
lcd.clear()
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
import utils
|
||||
run(utils.Config().LCD)
|
1
lib/lcdMenu
Submodule
1
lib/lcdMenu
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 4b7e5723ca1c2d4b18e452561b2c5a025e489eb2
|
@ -1,241 +0,0 @@
|
||||
"""
|
||||
lcdMenu - A micropython library, which supports vertical and horizontal scrolling through menu items on both 2x16 and 4x20 LCDs
|
||||
Copyright (C) 2024 Benjamin Burkhardt <bluefox@privacynerd.de>
|
||||
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from time import sleep
|
||||
|
||||
|
||||
class lcdMenu:
|
||||
# lcd: I2C_LCD - an object of the I2C_LCD class (https://git.privacynerd.de/BlueFox/micropython-libraries/src/branch/main/PCF8574T)
|
||||
# buttons: dict - a dictionary with machine.Pin objects as items with following keys
|
||||
# - "prev_btn" - OPTIONAL - when pressed, select the previous menu item
|
||||
# - "next_btn" - REQUIRED - when pressed, select the next menu item
|
||||
# - "ok_btn" - REQUIRED - when pressed, call the callback function of the menu item
|
||||
# menu_items: list - a list (-> maintains order!) containing tuples with the following format: (<ENTRY_NAME>,<CALLBACK FUNCTION>)
|
||||
# scroll_direction: bool - if true, the scrolling direction is horizontal, if false, vertical
|
||||
# cycle: bool - if true, start again with the first menu entry after the last one (and show the last before the first)
|
||||
# hide_menu_name: bool - OPTIONAL - if true, hide the menu's name (won't work in combination with a vertical scrolling direction)
|
||||
# name: str - OPTIONAL - a string with the name of the menu (can be hidden under certain circumstances)
|
||||
# debounce_time: float - OPTIONAL - the debounce time used by the library to debounce button presses
|
||||
def __init__(self, lcd, buttons: dict, scroll_direction: bool, cycle: bool, hide_menu_name: bool = False, name: str = "CHOOSE", debounce_time: float = 0.15):
|
||||
# save the argument variables
|
||||
self.lcd = lcd
|
||||
if "prev_btn" in buttons.keys():
|
||||
self.prev_btn = buttons["prev_btn"]
|
||||
else:
|
||||
self.prev_btn = None
|
||||
self.next_btn = buttons["next_btn"]
|
||||
self.ok_btn = buttons["ok_btn"]
|
||||
self.scroll_direction = scroll_direction
|
||||
self.cycle = cycle
|
||||
self.hide_menu_name = hide_menu_name
|
||||
self.name = name
|
||||
self.debounce_time = debounce_time
|
||||
|
||||
self.current_selection = 0 # set a standard value (can/most of the time will be changed directly after __init__ by a call to self.setup())
|
||||
self.menu_items = [] # set a standard empty (can/most of the time will be changed directly after __init__ by a call to self.setup())
|
||||
|
||||
# variables that are very unlikely the user want's to set them (but can be set via <lcdMenuObject>.<attribute_name>, of course)
|
||||
self.start_execution_msg = "Selected..." # the string displayed when an menu item is selected
|
||||
self.end_execution_msg = "Closing..." # the string displayed when the callback function of a selected menu item returns self.end_execution_wait = 1 # the time (in seconds) to wait after the callback function of a selected menu item returns
|
||||
self.start_execution_wait = 0.5 # the time (in seconds) to wait before the callback function of a selected menu item is called
|
||||
self.end_execution_wait = 1 # the time (in seconds) to wait after the callback function of a selected menu item returns
|
||||
self.fill_char = '-' # the character used to fill up space (used only on 4x20 displays); MUST BE 1 character long
|
||||
|
||||
|
||||
# menu_items: list - a list (-> maintains order!) containing tuples with the following format: (<ENTRY_NAME>,<CALLBACK FUNCTION>)
|
||||
# start_selection: int - OPTIONAL - the index of the item selected by default (starting with 0) - DON'T USE NEGATIVE INDEXES
|
||||
def setup(self, menu_items: list, start_selection: int = 0):
|
||||
self.menu_items = menu_items
|
||||
self.current_selection = start_selection
|
||||
|
||||
|
||||
def show_selection(self):
|
||||
# some checks:
|
||||
# 1. if you scrolling vertically, I found no elegant way to hide the name (there just need's to be something up there!)
|
||||
if self.scroll_direction and self.hide_menu_name:
|
||||
raise TypeError("Hiding the menu name whilst having the scroll direction set to horizontal is impossible!")
|
||||
# 2. if there are no menu items to display...
|
||||
if len(self.menu_items) == 0:
|
||||
raise TypeError("Can't show empty menus! Maybe you forgot calling self.setup() after initializing me?")
|
||||
|
||||
# get some often used values into local variables
|
||||
selection_name = self.menu_items[self.current_selection][0]
|
||||
lw = self.lcd.num_columns
|
||||
|
||||
# fill the custom character fields in the displays memory
|
||||
self.lcd.custom_char(0, bytearray([0x04,0x0A,0x11,0x00,0x00,0x00,0x00,0x00])) # arrow up
|
||||
self.lcd.custom_char(1, bytearray([0x00,0x00,0x00,0x00,0x00,0x11,0x0A,0x04])) # arrow down
|
||||
self.lcd.custom_char(2, bytearray([0x04,0x0A,0x11,0x00,0x00,0x11,0x0A,0x04])) # arrow up and down
|
||||
self.lcd.custom_char(3, bytearray([0x11,0x0A,0x04,0x00,0x00,0x04,0x0A,0x11])) # no options
|
||||
self.lcd.custom_char(4, bytearray([0x08,0x08,0x08,0x0F,0x08,0x08,0x08,0x08])) # line with a fork (to show the current selection - v scrolling)
|
||||
self.lcd.custom_char(5, bytearray([0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08])) # line without a fork (to show unselected items - v scrolling)
|
||||
self.lcd.custom_char(6, bytearray([0x00,0x00,0x00,0x00,0x00,0x00,0x15,0x00])) # three dots in a row
|
||||
|
||||
# now show it off!
|
||||
# Horizontal scrolling:
|
||||
if self.scroll_direction:
|
||||
self.lcd.move_to(0,0)
|
||||
if self.lcd.num_lines == 4:
|
||||
self.lcd.putstr(f"{self.fill_char*lw}{' '*lw*2}{self.fill_char*lw}") # fill the first and last line with 'fill_char's
|
||||
self.lcd.move_to(0,1) # move to the second line for the starting message below (takes two lines)
|
||||
self.lcd.putstr(f"[{self.name[0:lw-2].center(lw-2)}]") # the menu name (cannot be hidden in this mode)
|
||||
self.lcd.putstr(f"<{selection_name[0:lw-2].center(lw-2)}>") # the current selected menu item's name
|
||||
# Vertical scrolling:
|
||||
else:
|
||||
lines_for_display = self.lcd.num_lines
|
||||
items_before_selection = self.current_selection # e.g. if current selection is the second -> 1; one item is before this
|
||||
items_after_selection = len(self.menu_items) - self.current_selection - 1
|
||||
self.lcd.move_to(0,0)
|
||||
if not self.hide_menu_name:
|
||||
self.lcd.putstr(f"[{self.name[0:lw-2].center(lw-2)}]") # print the menu name surrounded by sqare brackets []
|
||||
lines_for_display -= 1 # decrease the amount of lines available for displaying menu items by 1 (as it's used for the menu name)
|
||||
|
||||
# now we want to display the selections
|
||||
# where the currently selected item should be the uppermost if possible
|
||||
# only at the end of the item list, the current item has to be in a lower line...
|
||||
# ... to avoid emtpy lines at the end as this would seem unprofessional/unclean to me
|
||||
# but it adds an noticeable amount of extra work / a complex algorithm
|
||||
if items_after_selection < (lines_for_display - 1): # if there aren't enough items to fill the display after the current selection
|
||||
# maybe the following could be done with a crazy math formula - but I want to keep it simpler!
|
||||
# as there aren't enough menu items after the current selection to fill the display, we have to...
|
||||
# ... calculate where to place the current selection, how many items there are before it and how many after it
|
||||
menu_items_cut = self.menu_items[::-1][:lines_for_display][::-1] # cut the menu_items list to the relevant last n ones maintaining order (n = number of lines for display)
|
||||
current_pos_in_cut = -len(self.menu_items) + self.current_selection + lines_for_display # calculate the current index of the selection in the new cut
|
||||
# draw all the lines
|
||||
for i in range(lines_for_display):
|
||||
if i == current_pos_in_cut: # if drawing the currently selected item
|
||||
self.lcd.putstr(f"{chr(4)} {menu_items_cut[i][0][0:lw-4]}")
|
||||
self.lcd.putstr(" " * ((lw-4)-len(menu_items_cut[i][0][0:lw-4]))) # fit the line
|
||||
else:
|
||||
self.lcd.putstr(f"{chr(5)} {menu_items_cut[i][0][0:lw-4]}")
|
||||
self.lcd.putstr(" " * ((lw-4)-len(menu_items_cut[i][0][0:lw-4]))) # fit the line
|
||||
# now the arrow
|
||||
if i == 0: # if the first element is drawn, think about printing or not printing the up arrow
|
||||
if self.current_selection == 0 and not self.cycle: # first item selected and no cycling
|
||||
self.lcd.putstr(" ") # leave the line with spaces
|
||||
else:
|
||||
self.lcd.putstr(" " + chr(0))
|
||||
elif i == lines_for_display-1: # if the last element is drawn, print a down arrow?
|
||||
if self.current_selection == (len(self.menu_items)-1) and not self.cycle: # first item selected and no cycling
|
||||
self.lcd.putstr(" ") # leave the line with spaces
|
||||
else:
|
||||
self.lcd.putstr(" " + chr(1)) # arrow down
|
||||
|
||||
else: # there are enough items to fill the display after the current selection
|
||||
# the first line
|
||||
self.lcd.putstr(f"{chr(4)} {selection_name[0:lw-4]}")
|
||||
self.lcd.putstr(" " * ((lw-4)-len(selection_name[0:lw-4]))) # fill the line with spaces up to 2 before the end of the line
|
||||
|
||||
if len(self.menu_items) <= 1:
|
||||
self.lcd.putstr(" " + chr(3)) # no options icon (as there's only one menu item!)
|
||||
elif lines_for_display == 1: # if there is exactly one line to display the menu entries...
|
||||
if self.current_selection == 0 and not self.cycle: # ...and the first element is selected and cycling is disabled so you can't go back
|
||||
self.lcd.putstr(" "+chr(1))
|
||||
elif self.current_selection == (len(self.menu_items)-1) and not self.cycle: # ...as before but with the last element -> you can't go further
|
||||
self.lcd.putstr(" "+chr(0))
|
||||
else: # ...or anything else (cycling or in the middle -> you can go in both directions
|
||||
self.lcd.putstr(" "+chr(2))
|
||||
elif self.current_selection == 0 and not self.cycle: # first item selected and no cycling
|
||||
self.lcd.putstr(" ") # leave the line with spaces
|
||||
else:
|
||||
self.lcd.putstr(" " + chr(0))
|
||||
|
||||
# the other lines... (if existing!)
|
||||
for i in range(lines_for_display-1):
|
||||
self.lcd.putstr(f"{chr(5)} {self.menu_items[self.current_selection+i+1][0][0:lw-4]}")
|
||||
self.lcd.putstr(" " * ((lw-4)-len(self.menu_items[self.current_selection+i+1][0][0:lw-4]))) # fit the line
|
||||
# check if it's the last line being drawn...
|
||||
if (i+1) == (lines_for_display-1):
|
||||
if self.current_selection == (len(self.menu_items)-1) and not self.cycle: # last item selected and no cycling
|
||||
self.lcd.putstr(" ")
|
||||
else:
|
||||
self.lcd.putstr(" " + chr(1))
|
||||
else: # else: if it's not the last line, leave the last two columns of the line empty
|
||||
self.lcd.putstr(" ")
|
||||
|
||||
|
||||
|
||||
def previous_selection(self):
|
||||
self.current_selection -= 1
|
||||
if self.current_selection < 0: # after the last element:
|
||||
if self.cycle: # if cycling is enabled, set it to the index of the last element
|
||||
self.current_selection = len(self.menu_items)-1
|
||||
else: # else, go to first element
|
||||
self.current_selection = 0
|
||||
|
||||
|
||||
def next_selection(self):
|
||||
self.current_selection += 1
|
||||
if self.current_selection >= len(self.menu_items): # after the last element:
|
||||
if self.cycle: # if cycling is enabled, go to first element
|
||||
self.current_selection = 0
|
||||
else: # else, set it to the index of the last element
|
||||
self.current_selection = len(self.menu_items)-1
|
||||
|
||||
|
||||
def execute_selection(self):
|
||||
selection = self.menu_items[self.current_selection]
|
||||
lw = self.lcd.num_columns
|
||||
|
||||
# if the program executed had no display (so the user notices something happens!)
|
||||
self.lcd.move_to(0,0)
|
||||
if self.lcd.num_lines == 4:
|
||||
self.lcd.putstr(f"{self.fill_char*lw}{' '*lw*2}{self.fill_char*lw}") # fill the first and last line with 'fill_char's
|
||||
self.lcd.move_to(0,1) # move to the second line for the starting message below (takes two lines)
|
||||
self.lcd.putstr(f"[{selection[0][0:lw-2].center(lw-2)}]{self.start_execution_msg[0:lw].center(lw)}")
|
||||
sleep(self.start_execution_wait) # wait some time before execution (so that the text can be read)
|
||||
|
||||
# run the program
|
||||
return_value = selection[1]()
|
||||
|
||||
# show a exit when there's no specific return value
|
||||
if not return_value: # if the return value is None / nothing was returned -> show a closing message
|
||||
while self.ok_btn.value() == 1: time.sleep(self.debounce_time) # wait till ok_btn release (e.g. if the "program" is a simple send action)
|
||||
self.lcd.move_to(0,0)
|
||||
if self.lcd.num_lines == 4:
|
||||
self.lcd.putstr(f"{self.fill_char*lw}{' '*lw*2}{self.fill_char*lw}") # fill the first and last line with 'fill_char's
|
||||
self.lcd.move_to(0,1) # move to the second line for the starting message below (takes two lines)
|
||||
self.lcd.putstr(f"[{selection[0][0:lw].center(lw-2)}]{self.end_execution_msg[0:lw].center(lw)}")
|
||||
sleep(self.end_execution_wait)
|
||||
self.show_selection()
|
||||
else: # -> show no message and quit directly back into the lcdMenu
|
||||
self.show_selection()
|
||||
|
||||
|
||||
# listen for button presses (this method should be called in an endless loop, see method run)
|
||||
def loop(self):
|
||||
if self.prev_btn:
|
||||
if self.prev_btn.value() == 1:
|
||||
self.previous_selection()
|
||||
self.show_selection()
|
||||
while self.prev_btn.value() == 1: sleep(self.debounce_time) # wait till release
|
||||
if self.next_btn.value() == 1:
|
||||
self.next_selection()
|
||||
self.show_selection()
|
||||
while self.next_btn.value() == 1: sleep(self.debounce_time) # wait till release
|
||||
if self.ok_btn.value() == 1:
|
||||
while self.ok_btn.value() == 1: sleep(self.debounce_time) # wait till release
|
||||
self.execute_selection()
|
||||
|
||||
def stop(self): # here to act as a callback for a menu entry (if the user wants to ofc!)
|
||||
self.running = False
|
||||
return True
|
||||
|
||||
def run(self):
|
||||
# show the selection first
|
||||
self.show_selection()
|
||||
self.running = True
|
||||
|
||||
# then listen on button presses in a loop...
|
||||
while self.running:
|
||||
self.loop()
|
||||
|
||||
return True # to prevent a "Closing menu ..." in submenu-situations
|
115
main.py
115
main.py
@ -13,86 +13,59 @@ You should have received a copy of the GNU General Public License along with thi
|
||||
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 timers():
|
||||
import programs.timers as t
|
||||
t.run(config, btn_mapping, utils.log, lcdMenu)
|
||||
del t
|
||||
collect()
|
||||
return True
|
||||
def manual():
|
||||
utils.cfg.LCD.clear()
|
||||
set_value = utils.cfg.RELAIS.value()
|
||||
utils.cfg.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:
|
||||
return True # exit on press of these buttons
|
||||
def timer():
|
||||
# display WIP
|
||||
utils.cfg.LCD.clear()
|
||||
utils.cfg.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 ")
|
||||
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 ")
|
||||
sleep(1)
|
||||
return True # disable the "Quitting" message from lcdMenu
|
||||
def lcd_big_hello():
|
||||
import lcd_big_hello
|
||||
lcd_big_hello.run()
|
||||
gc.collect()
|
||||
return True
|
||||
def input_tests():
|
||||
import input_tests as input_tests
|
||||
input_tests.run(serial_output=False)
|
||||
gc.collect()
|
||||
return True
|
||||
import programs.manual as m
|
||||
m.run(config)
|
||||
del m
|
||||
collect()
|
||||
return True
|
||||
def demos():
|
||||
import programs.demos as d
|
||||
d.run(config, btn_mapping, utils.log, lcdMenu)
|
||||
del d
|
||||
collect()
|
||||
return True
|
||||
def settings():
|
||||
# display WIP
|
||||
utils.cfg.LCD.clear()
|
||||
utils.cfg.LCD.putstr(" Still work-in-progress")
|
||||
sleep(3)
|
||||
return True
|
||||
|
||||
|
||||
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)
|
||||
del ws
|
||||
gc.collect()
|
||||
|
||||
import programs.settings as s
|
||||
s.run(config, btn_mapping, utils.log, lcdMenu)
|
||||
del s
|
||||
collect()
|
||||
return True
|
||||
|
||||
# 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_programs = [("Timer", timer),
|
||||
# create the main menu
|
||||
main_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="PROGRAMS")
|
||||
main_programs = [("Timers", timers),
|
||||
("Manual", manual),
|
||||
("UV off", uv_off),
|
||||
("UV on", uv_on),
|
||||
("Demos", demo_menu.run),
|
||||
("Demos", demos),
|
||||
("Settings", settings)]
|
||||
main_menu.setup(main_programs) # give it the callback list
|
||||
|
||||
|
||||
# -------
|
||||
|
||||
# run the welcome screen as defined in the config file
|
||||
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
|
||||
collect()
|
||||
|
||||
# and run the main menu (will be an endless loop)
|
||||
main_menu.run()
|
||||
|
52
programs/demos.py
Normal file
52
programs/demos.py
Normal file
@ -0,0 +1,52 @@
|
||||
"""
|
||||
uv-belichter-software - the DEMOS submenu
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from gc import collect
|
||||
|
||||
# All the arguments this method takes are there for one reason: reduce the amount of libraries loaded into the picos memory and thus improving performance
|
||||
# config: utils.Config object
|
||||
# btn_mapping: a dict containing the btn mapping for the menu operation (given to the lcdMenu object)
|
||||
# log: the utils.log function
|
||||
# lcdMenu: the lcdMenu class (not an object of that class!)
|
||||
def run(config, btn_mapping, log, lcdMenu):
|
||||
def lcd_driver_demo():
|
||||
import demos.lcd_driver_demo as ldd
|
||||
ldd.run(config.LCD)
|
||||
del ldd
|
||||
collect()
|
||||
return True
|
||||
def lcd_big_hello():
|
||||
import demos.lcd_big_hello as lbh
|
||||
lbh.run(config.LCD)
|
||||
del lbh
|
||||
collect()
|
||||
return True
|
||||
def input_tests():
|
||||
import demos.input_tests as it
|
||||
it.run(serial_output=False)
|
||||
del it
|
||||
collect()
|
||||
return True
|
||||
demo_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="DEMOS")
|
||||
demo_programs = [("LCD Demo", lcd_driver_demo),
|
||||
("Hello world", lcd_big_hello),
|
||||
("Input tests", input_tests),
|
||||
("Exit", demo_menu.stop)]
|
||||
demo_menu.setup(demo_programs) # give it the callback list
|
||||
demo_menu.run()
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
from utils import Config, log
|
||||
from lcdMenu import lcdMenu
|
||||
config = Config()
|
||||
btn_mapping = {"ok_btn": config.PIN_IN_BTN_1, "next_btn": config.PIN_IN_BTN_2} # the btn mapping for all lcdMenus
|
||||
run(Config(), btn_mapping, log, lcdMenu)
|
28
programs/manual.py
Normal file
28
programs/manual.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""
|
||||
uv-belichter-software - the MANUAL program (started from the main menu)
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
def run(config):
|
||||
config.LCD.clear()
|
||||
set_value = config.PIN_OUT_RELAIS.value()
|
||||
config.LCD.putstr(f"---- MANUAL ---- State: {set_value} ")
|
||||
while True:
|
||||
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; True to disable the Quitting message from lcdMenu
|
||||
|
||||
if __name__ == "__main__":
|
||||
from utils import Config, log
|
||||
config = Config()
|
||||
btn_mapping = {"ok_btn": config.PIN_IN_BTN_1, "next_btn": config.PIN_IN_BTN_2} # the btn mapping for all lcdMenus
|
||||
run(Config(), btn_mapping, log)
|
254
programs/settings.py
Normal file
254
programs/settings.py
Normal file
@ -0,0 +1,254 @@
|
||||
"""
|
||||
uv-belichter-software - the SETTINGS submenu
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from time import sleep, time_ns
|
||||
|
||||
# All the arguments this method takes are there for one reason: reduce the amount of libraries loaded into the picos memory and thus improving performance
|
||||
# config: utils.Config object
|
||||
# btn_mapping: a dict containing the btn mapping for the menu operation (given to the lcdMenu object)
|
||||
# log: the utils.log function
|
||||
# lcdMenu: the lcdMenu class (not an object of that class!)
|
||||
def run(config, btn_mapping, log, lcdMenu):
|
||||
settings_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="SETTINGS")
|
||||
def toggle_show_welcome():
|
||||
current_value = config.STARTUP_WELCOME_SHOW
|
||||
config.LCD.clear()
|
||||
config.LCD.putstr(f"Currently {'on' if current_value else 'off'}".center(16))
|
||||
config.LCD.putstr("< keep change >")
|
||||
keep_btn = config.PIN_IN_BTN_1
|
||||
change_btn = config.PIN_IN_BTN_2
|
||||
while True:
|
||||
if keep_btn.value() == 1:
|
||||
config.LCD.move_to(0,1) # move to the second row
|
||||
config.LCD.putstr(f"Stay {'on' if current_value else 'off'}!".center(16))
|
||||
sleep(0.5)
|
||||
return True
|
||||
if change_btn.value() == 1:
|
||||
config.STARTUP_WELCOME_SHOW = not current_value
|
||||
config.LCD.move_to(0,1) # move to the second row
|
||||
config.LCD.putstr(f"Turned {'on' if not current_value else 'off'}!".center(16))
|
||||
sleep(0.5)
|
||||
return True
|
||||
def welcome_cycles():
|
||||
config.LCD.clear()
|
||||
current_cycles = config.STARTUP_WELCOME_CYCLES
|
||||
|
||||
option_down = [" ", "v"][current_cycles>1]
|
||||
config.LCD.putstr(f" Cycles \n{option_down} {str(current_cycles).center(12)} ^")
|
||||
btn_left = config.PIN_IN_BTN_1
|
||||
btn_right = config.PIN_IN_BTN_2
|
||||
while True:
|
||||
if btn_left.value() == 1:
|
||||
sleep(0.1) # this value is a good compromise between being able to press both buttons and a fast up/down speed
|
||||
if btn_right.value() == 1:
|
||||
config.STARTUP_WELCOME_CYCLES = current_cycles
|
||||
config.LCD.move_to(0,1) # move to the second row
|
||||
config.LCD.putstr(f"Saved!".center(16)) # show a little info that it is now set
|
||||
sleep(0.5)
|
||||
while btn_right.value() == 1 or btn_left.value() == 1: # wait till both btns are released
|
||||
pass
|
||||
return True
|
||||
|
||||
current_cycles -= 1
|
||||
if current_cycles < 1: current_cycles = 1
|
||||
option_down = [" ", "v"][current_cycles>1]
|
||||
config.LCD.putstr(f" Cycles \n{option_down} {str(current_cycles).center(12)} ^")
|
||||
if btn_right.value() == 1:
|
||||
sleep(0.1)
|
||||
if btn_left.value() == 1:
|
||||
config.STARTUP_WELCOME_CYCLES = current_cycles
|
||||
config.LCD.move_to(0,1) # move to the second row
|
||||
config.LCD.putstr(f"Saved!".center(16)) # show a little info that it is now set
|
||||
sleep(0.5)
|
||||
while btn_right.value() == 1 or btn_left.value() == 1: # wait till both btns are released
|
||||
pass
|
||||
return True
|
||||
current_cycles += 1
|
||||
option_down = [" ", "v"][current_cycles>1]
|
||||
config.LCD.putstr(f" Cycles \n{option_down} {str(current_cycles).center(12)} ^")
|
||||
|
||||
# with n being the number of the timer (starting at 0)
|
||||
def set_n_timer(n: int):
|
||||
# a small helper function to save the value of the n'th timer...
|
||||
# as you can't programatically access TIMER_n_DURATION
|
||||
def set_timer_helper(n, value):
|
||||
if n == 0:
|
||||
config.TIMER_1_DURATION = value
|
||||
elif n == 1:
|
||||
config.TIMER_2_DURATION = value
|
||||
elif n == 2:
|
||||
config.TIMER_3_DURATION = value
|
||||
else:
|
||||
log(0, "There are only 3 timers at all. Trying to set the timer number {n} failed.")
|
||||
|
||||
config.LCD.clear()
|
||||
timer_values = [config.TIMER_1_DURATION, config.TIMER_2_DURATION, config.TIMER_3_DURATION]
|
||||
current_timer = timer_values[n] # get the n'th timer
|
||||
current_timer_div = lambda: divmod(current_timer, 60)
|
||||
|
||||
|
||||
config.LCD.putstr(f"Timer {n+1}".center(16))
|
||||
config.LCD.putstr(f"{'v' if current_timer > 1 else ' '} " + f"{current_timer_div()[0]:02d}:{current_timer_div()[1]:02d}".center(12) + " ^")
|
||||
btn_left = config.PIN_IN_BTN_1
|
||||
btn_right = config.PIN_IN_BTN_2
|
||||
left_was_released = True
|
||||
right_was_released = True
|
||||
while True:
|
||||
if btn_left.value() == 1:
|
||||
if left_was_released:
|
||||
time_press_start = time_ns()
|
||||
left_was_released = False
|
||||
sleep(0.1) # this value is a good compromise between being able to press both buttons and a fast up/down speed
|
||||
if btn_right.value() == 1: # exit if both btns are pressed
|
||||
set_timer_helper(n, current_timer)
|
||||
config.LCD.move_to(0,1) # move to the second row
|
||||
config.LCD.putstr(f"Saved!".center(16)) # show a little info that it is now set
|
||||
sleep(0.5)
|
||||
while btn_right.value() == 1 or btn_left.value() == 1: # wait till both btns are released
|
||||
pass
|
||||
return True
|
||||
# define the step width
|
||||
time_now = time_ns()
|
||||
if (time_now - time_press_start) <= 1*(10**9): # max. 1 seconds pressed
|
||||
current_timer -= 1
|
||||
elif (time_now - time_press_start) <= 2*(10**9): # max. 2 seconds pressed
|
||||
current_timer -= 5
|
||||
elif (time_now - time_press_start) <= 3*(10**9): # max. 3 seconds pressed
|
||||
current_timer -= 10
|
||||
elif (time_now - time_press_start) <= 4*(10**9): # max. 4 seconds pressed
|
||||
current_timer -= 30
|
||||
elif (time_now - time_press_start) <= 5*(10**9): # max. 5 seconds pressed
|
||||
current_timer -= 60
|
||||
else: # longer than 5s pressed
|
||||
current_timer -= 300
|
||||
if current_timer < 1: current_timer = 5999
|
||||
config.LCD.move_to(0,1)
|
||||
config.LCD.putstr("v " + f"{current_timer_div()[0]:02d}:{current_timer_div()[1]:02d}".center(12) + " ^")
|
||||
else:
|
||||
left_was_released = True
|
||||
if btn_right.value() == 1:
|
||||
if right_was_released:
|
||||
time_press_start = time_ns()
|
||||
right_was_released = False
|
||||
sleep(0.1)
|
||||
if btn_left.value() == 1: # exit if both btns are pressed
|
||||
set_timer_helper(n, current_timer)
|
||||
config.LCD.move_to(0,1) # move to the second row
|
||||
config.LCD.putstr(f"Saved!".center(16)) # show a little info that it is now set
|
||||
sleep(0.5)
|
||||
while btn_right.value() == 1 or btn_left.value() == 1: # wait till both btns are released
|
||||
pass
|
||||
return True
|
||||
# define the step width
|
||||
time_now = time_ns()
|
||||
if (time_now - time_press_start) <= 1*(10**9): # max. 1 seconds pressed
|
||||
current_timer += 1
|
||||
elif (time_now - time_press_start) <= 2*(10**9): # max. 2 seconds pressed
|
||||
current_timer += 5
|
||||
elif (time_now - time_press_start) <= 3*(10**9): # max. 3 seconds pressed
|
||||
current_timer += 10
|
||||
elif (time_now - time_press_start) <= 4*(10**9): # max. 4 seconds pressed
|
||||
current_timer += 30
|
||||
elif (time_now - time_press_start) <= 5*(10**9): # max. 5 seconds pressed
|
||||
current_timer += 60
|
||||
else: # longer than 5s pressed
|
||||
current_timer += 300
|
||||
if current_timer > 5999: current_timer = 1
|
||||
config.LCD.move_to(0,1)
|
||||
config.LCD.putstr("v " + f"{current_timer_div()[0]:02d}:{current_timer_div()[1]:02d}".center(12) + " ^")
|
||||
else:
|
||||
right_was_released = True
|
||||
def set_log_level():
|
||||
config.LCD.clear()
|
||||
current_level = config.LOG_LEVEL
|
||||
log_levels = ["WARN", "INFO", "DEBUG"]
|
||||
|
||||
config.LCD.putstr(f"Log level".center(16))
|
||||
config.LCD.putstr("v " + f"{log_levels[current_level]} ({current_level})".center(12) + " ^") # show the log level and it's name in the second row
|
||||
btn_left = config.PIN_IN_BTN_1
|
||||
btn_right = config.PIN_IN_BTN_2
|
||||
while True:
|
||||
if btn_left.value() == 1:
|
||||
sleep(0.1) # this value is a good compromise between being able to press both buttons and a fast up/down speed
|
||||
if btn_right.value() == 1:
|
||||
config.LOG_LEVEL = current_level
|
||||
config.LCD.move_to(0,1) # move to the second row
|
||||
config.LCD.putstr(f"Saved!".center(16)) # show a little info that it is now set
|
||||
sleep(0.5)
|
||||
while btn_right.value() == 1 or btn_left.value() == 1: # wait till both btns are released
|
||||
pass
|
||||
return True
|
||||
|
||||
current_level -= 1
|
||||
if current_level < 0: current_level = 2
|
||||
config.LCD.move_to(0,0)
|
||||
config.LCD.putstr(f"Log level".center(16))
|
||||
config.LCD.putstr("v " + f"{log_levels[current_level]} ({current_level})".center(12) + " ^") # show the log level and it's name in the second row
|
||||
if btn_right.value() == 1:
|
||||
sleep(0.1)
|
||||
if btn_left.value() == 1:
|
||||
config.LOG_LEVEL = current_level
|
||||
config.LCD.move_to(0,1) # move to the second row
|
||||
config.LCD.putstr(f"Saved!".center(16)) # show a little info that it is now set
|
||||
sleep(0.5)
|
||||
while btn_right.value() == 1 or btn_left.value() == 1: # wait till both btns are released
|
||||
pass
|
||||
return True
|
||||
current_level += 1
|
||||
if current_level > 2: current_level = 0
|
||||
config.LCD.move_to(0,0)
|
||||
config.LCD.putstr(f"Log level".center(16))
|
||||
config.LCD.putstr("v " + f"{log_levels[current_level]} ({current_level})".center(12) + " ^") # show the log level and it's name in the second row
|
||||
|
||||
def reset(): # reset all user-settable configuration to the default values
|
||||
config.LCD.clear()
|
||||
config.LCD.putstr("Sure about that?")
|
||||
config.LCD.putstr("< no yes >")
|
||||
no_btn = config.PIN_IN_BTN_1
|
||||
yes_btn = config.PIN_IN_BTN_2
|
||||
while True:
|
||||
if no_btn.value() == 1:
|
||||
return None
|
||||
if yes_btn.value() == 1:
|
||||
config.LCD.putstr("Resetting...".center(16))
|
||||
config.LCD.putstr("Welcome Screen".center(16))
|
||||
config.STARTUP_WELCOME_SHOW = True
|
||||
config.STARTUP_WELCOME_CYCLES = 1
|
||||
sleep(0.5)
|
||||
config.LCD.move_to(0,1)
|
||||
config.LCD.putstr("Timers".center(16))
|
||||
config.TIMER_1_DURATION = 60
|
||||
config.TIMER_2_DURATION = 2400
|
||||
config.TIMER_3_DURATION = 2700
|
||||
sleep(0.5)
|
||||
config.LCD.move_to(0,1)
|
||||
config.LCD.putstr("Logging".center(16))
|
||||
config.LOG_LEVEL = 1
|
||||
sleep(0.5)
|
||||
return True
|
||||
|
||||
settings_programs = [("Show welcome", toggle_show_welcome),
|
||||
("Welcome cycles", welcome_cycles),
|
||||
("Timer 1", lambda: set_n_timer(0)),
|
||||
("Timer 2", lambda: set_n_timer(1)),
|
||||
("Timer 3", lambda: set_n_timer(2)),
|
||||
("Log level", set_log_level),
|
||||
("Reset", reset),
|
||||
("Exit", settings_menu.stop)]
|
||||
settings_menu.setup(settings_programs) # give it the callback list
|
||||
settings_menu.run() # run the menu until it's closed
|
||||
|
||||
if __name__ == "__main__":
|
||||
from utils import Config, log
|
||||
from lcdMenu import lcdMenu
|
||||
config = Config()
|
||||
btn_mapping = {"ok_btn": config.PIN_IN_BTN_1, "next_btn": config.PIN_IN_BTN_2} # the btn mapping for all lcdMenus
|
||||
run(Config(), btn_mapping, log, lcdMenu)
|
73
programs/timers.py
Normal file
73
programs/timers.py
Normal file
@ -0,0 +1,73 @@
|
||||
"""
|
||||
uv-belichter-software - the TIMER submenu
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from time import sleep, time_ns
|
||||
|
||||
# All the arguments this method takes are there for one reason: reduce the amount of libraries loaded into the picos memory and thus improving performance
|
||||
# config: utils.Config object
|
||||
# btn_mapping: a dict containing the btn mapping for the menu operation (given to the lcdMenu object)
|
||||
# log: the utils.log function
|
||||
# lcdMenu: the lcdMenu class (not an object of that class!)
|
||||
def run(config, btn_mapping, log, lcdMenu):
|
||||
timer_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="TIMERS")
|
||||
timer_values_original = [config.TIMER_1_DURATION, config.TIMER_2_DURATION, config.TIMER_3_DURATION]
|
||||
timer_values = timer_values_original.copy() # here, the current timers time is stored when interrupting via the interrupt_pin (see below)
|
||||
timer_splits = [lambda: divmod(round(timer_values[0]), 60), lambda: divmod(round(timer_values[1]), 60), lambda: divmod(round(timer_values[2]), 60)]
|
||||
interrupt_pin = config.PIN_IN_BTN_1 # the interrupt btn stops the timer, saves the current time and goes back to the menu
|
||||
reset_pin = config.PIN_IN_BTN_2 # the reset btn restores the default value
|
||||
start_stop_pin = config.PIN_IN_SWITCH # the start_stop switch starts/stops the timer
|
||||
|
||||
# timer_number is the number of the timer that will be run, starting at 1
|
||||
# the _timer variable is needed because otherwise python will throw crazy errors regarding "variable accessed before assignment"...
|
||||
# ...just see it as a copy of the timer_number-1'th elemnt of the timer_values list (see above)
|
||||
def run_timer(timer_number: int, _timer: int):
|
||||
config.LCD.clear()
|
||||
config.LCD.putstr(f"Timer {timer_number}".center(16))
|
||||
last_start_stop_value = start_stop_pin.value()
|
||||
config.PIN_OUT_RELAIS.value(last_start_stop_value)
|
||||
last_time_ns = time_ns()
|
||||
while True: # now run the timer (if the switch is high)
|
||||
config.LCD.move_to(0,1)
|
||||
_timer_div = divmod(round(_timer), 60)
|
||||
config.LCD.putstr(f"{_timer_div[0]:02d}:{_timer_div[1]:02d}".center(16))
|
||||
if interrupt_pin.value() == 1:
|
||||
timer_values[timer_number-1] = _timer # save the current state
|
||||
last_start_stop_value = 0 # turn the LEDs off!
|
||||
config.PIN_OUT_RELAIS.value(last_start_stop_value)
|
||||
return None
|
||||
if reset_pin.value() == 1:
|
||||
_timer = timer_values_original[timer_number-1] # reset the timers
|
||||
if _timer <= 0:
|
||||
config.PIN_OUT_RELAIS.off()
|
||||
return True
|
||||
sleep(0.05)
|
||||
if last_start_stop_value != (new_value := start_stop_pin.value()):
|
||||
last_start_stop_value = new_value
|
||||
config.PIN_OUT_RELAIS.value(new_value)
|
||||
last_time_ns = time_ns()
|
||||
if start_stop_pin.value() == 1:
|
||||
_timer -= (time_ns() - last_time_ns) / 1000**3
|
||||
last_time_ns = time_ns()
|
||||
|
||||
timer_programs = [(f"T1 - {timer_splits[0]()[0]:02d}:{timer_splits[0]()[1]:02d}", lambda: run_timer(1, timer_values[0])),
|
||||
(f"T2 - {timer_splits[1]()[0]:02d}:{timer_splits[1]()[1]:02d}", lambda: run_timer(2, timer_values[1])),
|
||||
(f"T3 - {timer_splits[2]()[0]:02d}:{timer_splits[2]()[1]:02d}", lambda: run_timer(3, timer_values[2])),
|
||||
("Exit", timer_menu.stop)]
|
||||
timer_menu.setup(timer_programs) # give it the callback list
|
||||
timer_menu.run()
|
||||
return True # disable the "Quitting" message from lcdMenu
|
||||
|
||||
if __name__ == "__main__":
|
||||
from utils import Config, log
|
||||
from lcdMenu import lcdMenu
|
||||
config = Config()
|
||||
btn_mapping = {"ok_btn": config.PIN_IN_BTN_1, "next_btn": config.PIN_IN_BTN_2} # the btn mapping for all lcdMenus
|
||||
run(Config(), btn_mapping, log, lcdMenu)
|
116
utils.py
116
utils.py
@ -9,9 +9,116 @@ 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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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)
|
||||
"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")
|
||||
|
||||
|
||||
"""
|
||||
@ -24,6 +131,5 @@ def log(log_level: int, message: str):
|
||||
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 cfg.LOG_LEVEL >= log_level: # if log level is valid
|
||||
print(f"[{log_mapping[log_level]}] {message}")
|
||||
|
||||
elif Config().LOG_LEVEL >= log_level: # if log level is valid
|
||||
print(f"[{log_mapping[log_level]}] {message}")
|
Loading…
Reference in New Issue
Block a user