From 7c0d2d114af5ee3949cc975b5f43f258be6e224c Mon Sep 17 00:00:00 2001 From: Blue Fox Date: Thu, 2 Nov 2023 18:01:51 +0100 Subject: [PATCH] Added local counter functionality via pin input --- counter.py | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++ lcd_driver.py | 1 - lcd_screen.py | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++ micronec.py | 22 ++++++++++++ welcome.py | 14 ++------ 5 files changed, 215 insertions(+), 13 deletions(-) create mode 100644 counter.py create mode 100644 lcd_screen.py create mode 100644 micronec.py diff --git a/counter.py b/counter.py new file mode 100644 index 0000000..ded0761 --- /dev/null +++ b/counter.py @@ -0,0 +1,96 @@ +from machine import Pin +from time import sleep +from utime import ticks_ms + +class Counter: + def __init__(self, name="C1", value = 0): # init_value may be smaller than 0 + self.value = int(value) + self.name = str(name) + + + def __str__(self): + return str(self.name); + + + def __int__(self): + return int(self.value); + + + def __add__(self, x): + counter = Counter(value=self.value+int(x)) + return counter + + + def __sub__(self, x): + counter = Counter(value=self.value-int(x)) + return counter + + def increment(self): + self.value += 1 + + +""" +CounterArray: + provides an easy-to-use interface for the counter class. +""" +class CounterArray: + def __init__(self, counter_number = 1, counter_names = ["Counter 1"]): + self._counters = {} + + if len(counter_names) < counter_number: + raise IndexError("counter_names list MUST contain names for all counters.") + elif len(counter_names) > counter_number: + print("[WARN] [CounterArray] More counter names are given than counters wanted. Ignoring the overflow.") + + for i in range(counter_number): + self._counters[i] = Counter(counter_names[i]) + + + def add_counter(self, counter: Counter): + self._counters[len(self._counters)] = counter + return True + + + def remove_counter(self, key): + self._counters.pop(key) + return True + + + def get_counter(self, key): + return self._counters[key] + + + def register_listener(self, pins: dict, changed_callback=lambda: None): + # Function: + # listens for btn presses on specified pins + # Parameter: pins + # should be a dict of the form {COUNTER_ID:PIN, ...} + # where COUNTER_ID is the key of the counter to increment in the + # internal _counters dict and PIN is the pin to listen on + # Parameter: changed_callback + # is called without parameters, every time a button was pressed + inputs = {} + + for counter_id, pin in pins.items(): + inputs[counter_id] = Pin(pin, Pin.IN, Pin.PULL_DOWN) + + + self.last_time = 0 + def press_handler(pin, inputs, _counters, counterarray, changed_callback): + for counter_id, p in inputs.items(): + if p == pin: # now we have the pin where the button got pressed + new_time = ticks_ms() + if (new_time - counterarray.last_time) > 150: + _counters[counter_id] += 1 + counterarray.last_time = new_time + changed_callback() + + + for counter_id, pin in inputs.items(): + try: + self._counters[counter_id] + pin.irq(trigger=Pin.IRQ_FALLING, handler=lambda p: press_handler(p, inputs, self._counters, self, changed_callback)) + except KeyError: + print("[ERROR] [Counter().register_listener] Error while registering counter-pin-pair (counter_id=" + str(counter_id) + ", pin=" + str(pin) + "). Ignoring this pair.") + + diff --git a/lcd_driver.py b/lcd_driver.py index 97d5751..1d7a062 100644 --- a/lcd_driver.py +++ b/lcd_driver.py @@ -26,7 +26,6 @@ SHIFT_DATA = 4 # P4-P7 class LCD_API: - # Implements the API for talking with HD44780 compatible character LCDs. # This class only knows what commands to send to the LCD, and not how to get # them to the LCD. diff --git a/lcd_screen.py b/lcd_screen.py new file mode 100644 index 0000000..eff64ac --- /dev/null +++ b/lcd_screen.py @@ -0,0 +1,95 @@ +from machine import I2C, Pin, Timer +import time + + +class CounterScreen: + def __init__(self, lcd, counterArray): + self.lcd = lcd + self.lcd.clear() + self.counterArray = counterArray + self._zfill = lambda s, length: '{:0>{w}}'.format(s, w=length) + self.last_line1 = "" + self.last_line2 = "" + self.too_many_counters_warning_shown = False + + def _too_many_counters_warning(self): + # Function: + # show a short message saying that not more that 5 counters can be displayed. + # WARNING: + # This is a INTERNAL class function, don't call from outside! + # Stops program flow for approx. 15s + + self.lcd.move_to(0,0) + self.lcd.putstr(" WARNING! ") + message = "Can't display more than 5 counters. Cutting the overflow. " + message = message + " +++ " + message + while message != " ": + self.lcd.move_to(0,1) + self.lcd.putstr(message[:16]) + message = message[1:] + time.sleep(0.07) + + self.lcd.clear() + self.too_many_counters_warning_shown = True + + def show_screen(self): + # Function: + # show a screen on the lcd, table layout: row 1 shows the short names of the counters, + # row 2 shows the values. + # WARNING: + # This function SHALL be called once at init of the class as it can stop execution for + # approx. 15s at first run! + + line1 = "" + line2 = "" + number_counters = len(self.counterArray._counters) + if number_counters > 5: + if not self.too_many_counters_warning_shown: + self._too_many_counters_warning() + number_counters = 5 + + + ## show different views depending on how many counters exist + if number_counters < 1: + line1 = " WARNING! " + line2 = " No counters. " + + elif number_counters == 1: + line1 = "* C1 " + line2 = "* " + str(list(self.counterArray._counters.items())[0][1].value) + + elif number_counters == 2: + v0 = str(list(self.counterArray._counters.items())[0][1].value) + v1 = str(list(self.counterArray._counters.items())[1][1].value) + line1 = "* C1 * C2 " + line2 = "* " + v0 + (6-len(v0))*" " + "* " + v1 + (6-len(v1)) * " " + + elif number_counters == 3: + v0 = str(list(self.counterArray._counters.items())[0][1].value) + v1 = str(list(self.counterArray._counters.items())[1][1].value) + v2 = str(list(self.counterArray._counters.items())[2][1].value) + line1 = "C1 * C2 * C3 " + line2 = v0 + (3-len(v0))*" " + " * " + v1 + (3-len(v1))*" " + " * " + v2 + (4-len(v2))*" " + + elif number_counters == 4: + line1 = "|C1 |C2 |C3 |C4 " + for i in range(4): + line2 += "|" + self._zfill(list(self.counterArray._counters.items())[i][1].value, 3)[:3] + + else: + line1 = "|C1|C2|C3|C4|C5|" + for i in range(5): + line2 += "|" + self._zfill(list(self.counterArray._counters.items())[i][1].value, 2)[:2] + line2 += "|" + + + # check if something changed, if so, redraw it + if line1 != self.last_line1: + self.lcd.putstr(line1[:16]) + self.last_line1 = line1 + self.lcd.move_to(0,1) + if line2 != self.last_line2: + self.lcd.putstr(line2[:16]) + self.last_line2 = line2 + + diff --git a/micronec.py b/micronec.py new file mode 100644 index 0000000..402b322 --- /dev/null +++ b/micronec.py @@ -0,0 +1,22 @@ +from lcd_driver import I2C_LCD +from machine import I2C, Pin +from welcome import WelcomeScreen +from counter import CounterArray +from lcd_screen import CounterScreen +import time + + +I2C_ADDR = 0x27 +I2C_NUM_ROWS = 2 +I2C_NUM_COLS = 16 + +_i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000) +lcd = I2C_LCD(_i2c, I2C_ADDR, I2C_NUM_ROWS, I2C_NUM_COLS) +counterArray = CounterArray(2, ["Counter 1", "Counter 2"]) +ws = WelcomeScreen(lcd) +cs = CounterScreen(lcd, counterArray) + +# Real program +ws.show_welcome(1) +cs.show_screen() +counterArray.register_listener({0:2,1:3}, cs.show_screen) diff --git a/welcome.py b/welcome.py index 80ac3ba..4588d62 100644 --- a/welcome.py +++ b/welcome.py @@ -1,18 +1,8 @@ -from lcd_driver import I2C_LCD -from machine import I2C, Pin, Timer import time -import machine - class WelcomeScreen: - def __init__(self): - self.I2C_ADDR = 0x27 - self.I2C_NUM_ROWS = 2 - self.I2C_NUM_COLS = 16 - - self._i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000) - self.lcd = I2C_LCD(self._i2c, self.I2C_ADDR, self.I2C_NUM_ROWS, self.I2C_NUM_COLS) - + def __init__(self, lcd): + self.lcd = lcd def show_welcome(self, loops=1): # loops says how often the Starting text goes through if loops < 1: loops = 1