Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
a97dd88a67 | |||
92ebbd2ef8 | |||
68a588de58 | |||
91a58cca61 | |||
002b01d572 | |||
aba5927221 | |||
a6b531c394 | |||
72bc662cb8 | |||
e1075f8d84 | |||
908a5e39be | |||
5ce8e592c3 | |||
ddde9480ff | |||
c7a8cda7d1 | |||
7adcdb1624 | |||
f0d6b49d6e | |||
a4adaac9b4 |
24
README.md
24
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 **16x2 display** (maybe 20x4 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,12 +25,12 @@ A **relais** is used for switching all the LEDs.
|
||||
| Relais control pin | GPIO21 |
|
||||
|
||||
|
||||
## Installation
|
||||
## Software installation
|
||||
|
||||
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.json](config.json) file in JSON format just have a look around there. Some hints for editing this file:
|
||||
|
||||
@ -40,12 +42,14 @@ All the configuration can be done in the [config.json](config.json) file in JSON
|
||||
|
||||
- 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) | `2` |
|
||||
| `"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..."` |
|
||||
@ -59,8 +63,8 @@ All the configuration can be done in the [config.json](config.json) file in JSON
|
||||
| `"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` |
|
||||
| `"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) |
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"LOG_LEVEL": 2,
|
||||
"LOG_LEVEL": 1,
|
||||
"STARTUP_WELCOME_SHOW": true,
|
||||
"STARTUP_PROJECT_NAME":" UV-Belichter ",
|
||||
"STARTUP_MESSAGE_STARTING": "Starting...",
|
||||
|
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)
|
194
main.py
194
main.py
@ -13,157 +13,50 @@ 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, time_ns
|
||||
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():
|
||||
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
|
||||
def timer():
|
||||
timer_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="TIMERS")
|
||||
timer_values = [config.TIMER_1_DURATION, config.TIMER_2_DURATION, config.TIMER_3_DURATION]
|
||||
timer_splits = [lambda: divmod(round(timer_values[0]), 60), lambda: divmod(round(timer_values[1]), 60), lambda: divmod(round(timer_values[2]), 60)]
|
||||
|
||||
def run_timer(timer_number: int, _timer: int):
|
||||
interrupt_pin = config.PIN_IN_BTN_1
|
||||
reset_pin = config.PIN_IN_BTN_2
|
||||
start_stop_pin = config.PIN_IN_SWITCH
|
||||
original = _timer
|
||||
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:
|
||||
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 = original # 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"{timer_splits[0]()[0]:02d}:{timer_splits[0]()[1]:02d}", lambda: run_timer(1, timer_values[0])),
|
||||
(f"{timer_splits[1]()[0]:02d}:{timer_splits[1]()[1]:02d}", lambda: run_timer(2, timer_values[1])),
|
||||
(f"{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()
|
||||
del timer_menu, timer_programs
|
||||
collect()
|
||||
return True # disable the "Quitting" message from lcdMenu
|
||||
def uv_on():
|
||||
config.PIN_OUT_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():
|
||||
config.PIN_OUT_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 as lbh
|
||||
lbh.run(config.LCD)
|
||||
del lbh
|
||||
collect()
|
||||
return True
|
||||
def input_tests():
|
||||
import input_tests as input_tests
|
||||
input_tests.run(serial_output=False)
|
||||
collect()
|
||||
return True
|
||||
def settings():
|
||||
settings_menu = lcdMenu(config.LCD, btn_mapping, scroll_direction=True, cycle=True, hide_menu_name=False, name="SETTINGS")
|
||||
def swap_welcome():
|
||||
config.STARTUP_WELCOME_SHOW = not config.STARTUP_WELCOME_SHOW
|
||||
config.LCD.clear()
|
||||
config.LCD.putstr(f" Welcome Screen ")
|
||||
if config.STARTUP_WELCOME_SHOW:
|
||||
config.LCD.putstr("--- Enabled! ---")
|
||||
else:
|
||||
config.LCD.putstr("--- Disabled ---")
|
||||
sleep(1)
|
||||
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)} ^")
|
||||
while True:
|
||||
if config.PIN_IN_BTN_1.value() == 1:
|
||||
time_ns_when_pressed = time_ns()
|
||||
while config.PIN_IN_BTN_1.value() == 1:
|
||||
if (time_ns() - time_ns_when_pressed) > 1000000000: # if the time passed is longer than a second
|
||||
while config.PIN_IN_BTN_1.value() == 1:
|
||||
pass # wait till release
|
||||
config.STARTUP_WELCOME_CYCLES = current_cycles
|
||||
return True
|
||||
sleep(0.05)
|
||||
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 config.PIN_IN_BTN_2.value() == 1:
|
||||
time_ns_when_pressed = time_ns()
|
||||
while config.PIN_IN_BTN_2.value() == 1:
|
||||
if (time_ns() - time_ns_when_pressed) > 1000000000: # if the time passed is longer than a second
|
||||
while config.PIN_IN_BTN_2.value() == 1:
|
||||
pass # wait till release
|
||||
config.STARTUP_WELCOME_CYCLES = current_cycles
|
||||
return True
|
||||
sleep(0.05)
|
||||
current_cycles += 1
|
||||
option_down = [" ", "v"][current_cycles>1]
|
||||
config.LCD.putstr(f" Cycles \n{option_down} {str(current_cycles).center(12)} ^")
|
||||
|
||||
settings_programs = [("Show welcome", swap_welcome),
|
||||
("Welcome cycles", welcome_cycles),
|
||||
("Exit", settings_menu.stop)]
|
||||
settings_menu.setup(settings_programs) # give it the callback list
|
||||
settings_menu.run() # run the menu until it's closed
|
||||
del settings_menu, settings_programs
|
||||
collect()
|
||||
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
|
||||
|
||||
# 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():
|
||||
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():
|
||||
import programs.settings as s
|
||||
s.run(config, btn_mapping, utils.log, lcdMenu)
|
||||
del s
|
||||
collect()
|
||||
return True
|
||||
|
||||
# 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),
|
||||
("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],
|
||||
@ -174,16 +67,5 @@ if config.STARTUP_WELCOME_SHOW:
|
||||
del ws
|
||||
collect()
|
||||
|
||||
|
||||
# create the menus
|
||||
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", run_demo_menu),
|
||||
("Settings", settings)]
|
||||
main_menu.setup(main_programs) # give it the callback list
|
||||
|
||||
# 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)
|
2
utils.py
2
utils.py
@ -131,5 +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
|
||||
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