Compare commits

...

8 Commits

5 changed files with 191 additions and 35 deletions

View File

@ -5,15 +5,15 @@ A collection of programs run on a Raspberry Pi Pico to control a uv exposure uni
## 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** (maybe 20x4 or others do also work, but these are not tested) 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 +23,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 +40,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 +61,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) |

View File

@ -1,5 +1,5 @@
{
"LOG_LEVEL": 2,
"LOG_LEVEL": 1,
"STARTUP_WELCOME_SHOW": true,
"STARTUP_PROJECT_NAME":" UV-Belichter ",
"STARTUP_MESSAGE_STARTING": "Starting...",

62
lcd_driver_demo.py Normal file
View File

@ -0,0 +1,62 @@
"""
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.putstr("Driver demo".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)

138
main.py
View File

@ -81,28 +81,27 @@ def timer():
del timer_menu, timer_programs
collect()
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
def toggle_show_welcome():
current_value = 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)
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
@ -117,7 +116,8 @@ def settings():
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"Now set to {current_cycles}".center(16)) # show a little info that it is now set
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
@ -131,7 +131,8 @@ def settings():
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"Now set to {current_cycles}".center(16)) # show a little info that it is now set
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
@ -175,6 +176,7 @@ def settings():
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
@ -206,6 +208,7 @@ def settings():
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
@ -228,12 +231,82 @@ def settings():
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", swap_welcome),
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
@ -241,8 +314,27 @@ def settings():
collect()
return True
def run_demo_menu():
def lcd_driver_demo():
import lcd_driver_demo as ldd
ldd.run(config.LCD)
del ldd
collect()
return True
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)
del input_tests
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_big_hello),
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

View File

@ -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}")