diff options
Diffstat (limited to 'anzeige0')
-rw-r--r-- | anzeige0/Kconfig | 4 | ||||
-rw-r--r-- | anzeige0/boot.py | 2 | ||||
-rw-r--r-- | anzeige0/lcd_api.py | 195 | ||||
-rw-r--r-- | anzeige0/main.py | 174 | ||||
-rwxr-xr-x | anzeige0/make.sh | 5 | ||||
-rw-r--r-- | anzeige0/nodemcu_gpio_lcd.py | 168 |
6 files changed, 548 insertions, 0 deletions
diff --git a/anzeige0/Kconfig b/anzeige0/Kconfig new file mode 100644 index 0000000..c588b68 --- /dev/null +++ b/anzeige0/Kconfig @@ -0,0 +1,4 @@ +mainmenu "configuration" + +source ../scripts/Kconfig-wifi + diff --git a/anzeige0/boot.py b/anzeige0/boot.py new file mode 100644 index 0000000..0d30893 --- /dev/null +++ b/anzeige0/boot.py @@ -0,0 +1,2 @@ +config = dict(map(lambda x: (x[0], int(x[1]) if x[1].isdigit() else x[1][1:-1] if x[1].startswith('"') and x[1].endswith('"') else x[1]), map(lambda x: x.strip().split("=", 1), filter(lambda x: '=' in x, open(".config").readlines())))) + diff --git a/anzeige0/lcd_api.py b/anzeige0/lcd_api.py new file mode 100644 index 0000000..38b60da --- /dev/null +++ b/anzeige0/lcd_api.py @@ -0,0 +1,195 @@ +"""Provides an API for talking to HD44780 compatible character LCDs.""" + +import time + +class LcdApi: + """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. + + It is expected that a derived class will implement the hal_xxx functions. + """ + + # The following constant names were lifted from the avrlib lcd.h + # header file, however, I changed the definitions from bit numbers + # to bit masks. + # + # HD44780 LCD controller command set + + LCD_CLR = 0x01 # DB0: clear display + LCD_HOME = 0x02 # DB1: return to home position + + LCD_ENTRY_MODE = 0x04 # DB2: set entry mode + LCD_ENTRY_INC = 0x02 # --DB1: increment + LCD_ENTRY_SHIFT = 0x01 # --DB0: shift + + LCD_ON_CTRL = 0x08 # DB3: turn lcd/cursor on + LCD_ON_DISPLAY = 0x04 # --DB2: turn display on + LCD_ON_CURSOR = 0x02 # --DB1: turn cursor on + LCD_ON_BLINK = 0x01 # --DB0: blinking cursor + + LCD_MOVE = 0x10 # DB4: move cursor/display + LCD_MOVE_DISP = 0x08 # --DB3: move display (0-> move cursor) + LCD_MOVE_RIGHT = 0x04 # --DB2: move right (0-> left) + + LCD_FUNCTION = 0x20 # DB5: function set + LCD_FUNCTION_8BIT = 0x10 # --DB4: set 8BIT mode (0->4BIT mode) + LCD_FUNCTION_2LINES = 0x08 # --DB3: two lines (0->one line) + LCD_FUNCTION_10DOTS = 0x04 # --DB2: 5x10 font (0->5x7 font) + LCD_FUNCTION_RESET = 0x30 # See "Initializing by Instruction" section + + LCD_CGRAM = 0x40 # DB6: set CG RAM address + LCD_DDRAM = 0x80 # DB7: set DD RAM address + + LCD_RS_CMD = 0 + LCD_RS_DATA = 1 + + LCD_RW_WRITE = 0 + LCD_RW_READ = 1 + + def __init__(self, num_lines, num_columns): + self.num_lines = num_lines + if self.num_lines > 4: + self.num_lines = 4 + self.num_columns = num_columns + if self.num_columns > 40: + self.num_columns = 40 + self.cursor_x = 0 + self.cursor_y = 0 + self.backlight = True + self.display_off() + self.backlight_on() + self.clear() + self.hal_write_command(self.LCD_ENTRY_MODE | self.LCD_ENTRY_INC) + self.hide_cursor() + self.display_on() + + def clear(self): + """Clears the LCD display and moves the cursor to the top left + corner. + """ + self.hal_write_command(self.LCD_CLR) + self.hal_write_command(self.LCD_HOME) + self.cursor_x = 0 + self.cursor_y = 0 + + def show_cursor(self): + """Causes the cursor to be made visible.""" + self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY | + self.LCD_ON_CURSOR) + + def hide_cursor(self): + """Causes the cursor to be hidden.""" + self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY) + + def blink_cursor_on(self): + """Turns on the cursor, and makes it blink.""" + self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY | + self.LCD_ON_CURSOR | self.LCD_ON_BLINK) + + def blink_cursor_off(self): + """Turns on the cursor, and makes it no blink (i.e. be solid).""" + self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY | + self.LCD_ON_CURSOR) + + def display_on(self): + """Turns on (i.e. unblanks) the LCD.""" + self.hal_write_command(self.LCD_ON_CTRL | self.LCD_ON_DISPLAY) + + def display_off(self): + """Turns off (i.e. blanks) the LCD.""" + self.hal_write_command(self.LCD_ON_CTRL) + + def backlight_on(self): + """Turns the backlight on. + + This isn't really an LCD command, but some modules have backlight + controls, so this allows the hal to pass through the command. + """ + self.backlight = True + self.hal_backlight_on() + + def backlight_off(self): + """Turns the backlight off. + + This isn't really an LCD command, but some modules have backlight + controls, so this allows the hal to pass through the command. + """ + self.backlight = False + self.hal_backlight_off() + + def move_to(self, cursor_x, cursor_y): + """Moves the cursor position to the indicated position. The cursor + position is zero based (i.e. cursor_x == 0 indicates first column). + """ + self.cursor_x = cursor_x + self.cursor_y = cursor_y + addr = cursor_x & 0x3f + if cursor_y & 1: + addr += 0x40 # Lines 1 & 3 add 0x40 + if cursor_y & 2: + addr += 0x14 # Lines 2 & 3 add 0x14 + self.hal_write_command(self.LCD_DDRAM | addr) + + def putchar(self, char): + """Writes the indicated character to the LCD at the current cursor + position, and advances the cursor by one position. + """ + if char != '\n': + self.hal_write_data(ord(char)) + self.cursor_x += 1 + if self.cursor_x >= self.num_columns or char == '\n': + self.cursor_x = 0 + self.cursor_y += 1 + if self.cursor_y >= self.num_lines: + self.cursor_y = 0 + self.move_to(self.cursor_x, self.cursor_y) + + def putstr(self, string): + """Write the indicated string to the LCD at the current cursor + position and advances the cursor position appropriately. + """ + for char in string: + self.putchar(char) + + def custom_char(self, location, charmap): + """Write a character to one of the 8 CGRAM locations, available + as chr(0) through chr(7). + """ + location &= 0x7 + self.hal_write_command(self.LCD_CGRAM | (location << 3)) + time.sleep_us(40) + for i in range(8): + self.hal_write_data(charmap[i]) + time.sleep_us(40) + self.move_to(self.cursor_x, self.cursor_y) + + def hal_backlight_on(self): + """Allows the hal layer to turn the backlight on. + + If desired, a derived HAL class will implement this function. + """ + pass + + def hal_backlight_off(self): + """Allows the hal layer to turn the backlight off. + + If desired, a derived HAL class will implement this function. + """ + pass + + def hal_write_command(self, cmd): + """Write a command to the LCD. + + It is expected that a derived HAL class will implement this + function. + """ + raise NotImplementedError + + def hal_write_data(self, data): + """Write data to the LCD. + + It is expected that a derived HAL class will implement this + function. + """ + raise NotImplementedError diff --git a/anzeige0/main.py b/anzeige0/main.py new file mode 100644 index 0000000..c73e847 --- /dev/null +++ b/anzeige0/main.py @@ -0,0 +1,174 @@ +#!micropython +print("Start main.py") +import json +import time +import machine +import socket +import micropython as mp + +from nodemcu_gpio_lcd import GpioLcd +from machine import Pin, PWM +import network + +backlight = PWM(Pin(12), freq=500, duty=200) + +wlan = network.WLAN() + +lcd = GpioLcd(rs_pin=Pin(16), enable_pin=Pin(5), + d4_pin=Pin(4), d5_pin=Pin(0), d6_pin=Pin(2), d7_pin=Pin(14), + num_lines=2, num_columns=16) + +lcd.clear() +lcd.putstr("Verbinde mit:\n{}".format(wlan.config('essid'))) +while not wlan.isconnected(): + continue +lcd.clear() +lcd.putstr("Verbunden") + +HOST = b'www.localnet.cc' +HEADERS = b'Host: www.localnet.cc\r\nConnection: close\r\n\r\n' + +displays = [ + { # ################ ################ <- 16 chars + "name": "Zusammenfassung " + " ", + "text": "I: ____ E: ____ " + "W: ____ßS: ____ß", + "values" : [ + """SELECT sum("d4_count")*30 FROM "stromzaehler3" WHERE time > now() - 2m""", + """SELECT sum("d1_count")*30 FROM "stromzaehler3" WHERE time > now() - 2m""", + """SELECT last(value) FROM temp0 WHERE sensor = '28-8B-DC-E9-16-13-01-D5'""", + """SELECT last(value) FROM temp0 WHERE sensor = '28-7F-69-16-17-13-01-A6'""", + ] + }, + { # ################ ################ <- 16 chars + "name": "Stromz\xe1hler " + " Heute ", + "text": "\xf6 Import ____Wh" + "\xf6 Export ____Wh", + "values" : [ + "SELECT last(sum) FROM (SELECT sum(d4_count) FROM stromzaehler3 WHERE time > now() - 1d GROUP BY time(1d) tz('Europe/Berlin'))", + "SELECT last(sum) FROM (SELECT sum(d1_count) FROM stromzaehler3 WHERE time > now() - 1d GROUP BY time(1d) tz('Europe/Berlin'))", + ], + }, + { # ################ ################ <- 16 chars + "name": "Heizung Solar " + " ", + "text": "Von Dach: ____ß " + "Zum Dach: ____ß ", + "values" : [ + "SELECT last(value) FROM temp0 WHERE sensor = '28-7F-69-16-17-13-01-A6'", + "SELECT last(value) FROM temp0 WHERE sensor = '28-99-38-EB-16-13-01-22'", + ] + }, + { # ################ ################ <- 16 chars + "name": "BFT Rheinau " + " ", + "text": "S95-E5: __.___ " + "Diesel: __.___ ", + "values" : [ + """SELECT last(value) FROM "tankerkoenig.SP95-E5" WHERE "name" = 'Rheinau-Freistett - BFT - Freistett'""", + """SELECT last(value) FROM "tankerkoenig.Diesel" WHERE "name" = 'Rheinau-Freistett - BFT - Freistett'""", + ]}, + ] + +def query_one(db, select): + select_esc = select.replace(" ", "%20") + select_esc = select_esc.replace("\"", "%22") + addr = socket.getaddrinfo(HOST, 80)[0][-1] + s = socket.socket() + s.connect(addr) + s.send(b'GET /grafana/api/datasources/proxy/3/query?db={}&q={}&epoch=ms\r\n'.format(db, select_esc)) + s.send(HEADERS) + data = json.load(s) + s.close() + return data['results'][0]['series'][0]['values'][0][1] + +current_display = 0 +def update(): + global UPDATE_RUN, current_display, display, lcd + + lcd.move_to(0, 0) + text = displays[current_display]["text"] + + s = "" + cur = "" + pos = 0 + i = 0 + while i<len(text): + c = text[i] + cur_added = False + if c == '_' or ( len(cur) > 0 and c == '.' ): + cur += c + cur_added = True + + if len(cur) > 0 and (not cur_added or i == len(text) - 1): + value_query = displays[current_display]["values"][pos] + v = query_one("data", value_query) + if isinstance(v, float): + if "." in cur: + precision = len(cur) - cur.index(".") - 1 + else: + precision = 0 + fmt_string = "{{: >{}.{}f}}".format(len(cur), precision) + elif isinstance(v, int): + fmt_string = "{{: >{}d}}".format(len(cur)) + else: + raise Exception("Wrong type: {}".format(type(v))) + s += fmt_string.format(v) + lcd.putstr(s) + s = "" + cur = "" + pos += 1 + + if not cur_added: + s += c + + if not cur_added and i == len(text)-1: + lcd.putstr(s) + s = "" + + i += 1 + + lcd.putstr(s) + +last_update = 0 + +def schedule_update(timer=None, force=False): + global last_update + def ex(none): + try: + update() + except Exception as e: + lcd.clear() + msg = "Error: {}".format(str(e)[0:(32-7)]) + lcd.putstr(msg) + raise e + + now = time.ticks_ms() + if force: + last_update = now + ex(None) + elif now - last_update > 5000: + last_update = now + mp.schedule(ex, None) + +timer = machine.Timer(-1) + +def switch_display(none): + global current_display, displays + + current_display += 1 + current_display %= len(displays) + lcd.move_to(0,0) + lcd.putstr(displays[current_display]['name']) + + # trigger update in 1s + last_update = time.ticks_ms() - 4000 + +last_switch = 0 +def switch_pressed(pin): + global last_switch + now = time.ticks_ms() + if pin.value() == 0 and now - last_switch > 200: + mp.schedule(switch_display, None) + last_switch = now + +switch = machine.Pin(13, machine.Pin.IN) +switch.irq(switch_pressed) + +timer.init(period=500, mode=machine.Timer.PERIODIC, callback=schedule_update) + +print("main.py finished") diff --git a/anzeige0/make.sh b/anzeige0/make.sh new file mode 100755 index 0000000..ef68b45 --- /dev/null +++ b/anzeige0/make.sh @@ -0,0 +1,5 @@ +#!/bin/sh +PY_SOURCES="boot.py main.py .config lcd_api.py nodemcu_gpio_lcd.py" + +. ${0%/*}/../scripts/make.inc.sh + diff --git a/anzeige0/nodemcu_gpio_lcd.py b/anzeige0/nodemcu_gpio_lcd.py new file mode 100644 index 0000000..0bb839c --- /dev/null +++ b/anzeige0/nodemcu_gpio_lcd.py @@ -0,0 +1,168 @@ +"""Implements a HD44780 character LCD connected via NodeMCU GPIO pins.""" + +from lcd_api import LcdApi +from machine import Pin +from utime import sleep_ms, sleep_us + + +class GpioLcd(LcdApi): + """Implements a HD44780 character LCD connected via NodeMCU GPIO pins.""" + + def __init__(self, rs_pin, enable_pin, d0_pin=None, d1_pin=None, + d2_pin=None, d3_pin=None, d4_pin=None, d5_pin=None, + d6_pin=None, d7_pin=None, rw_pin=None, backlight_pin=None, + num_lines=2, num_columns=16): + """Constructs the GpioLcd object. All of the arguments must be Pin + objects which describe which pin the given line from the LCD is + connected to. + + When used in 4-bit mode, only D4, D5, D6, and D7 are physically + connected to the LCD panel. This function allows you call it like + GpioLcd(rs, enable, D4, D5, D6, D7) and it will interpret that as + if you had actually called: + GpioLcd(rs, enable, d4=D4, d5=D5, d6=D6, d7=D7) + + The enable 8-bit mode, you need pass d0 through d7. + + The rw pin isn't used by this library, but if you specify it, then + it will be set low. + """ + self.rs_pin = rs_pin + self.enable_pin = enable_pin + self.rw_pin = rw_pin + self.backlight_pin = backlight_pin + self._4bit = True + if d4_pin and d5_pin and d6_pin and d7_pin: + self.d0_pin = d0_pin + self.d1_pin = d1_pin + self.d2_pin = d2_pin + self.d3_pin = d3_pin + self.d4_pin = d4_pin + self.d5_pin = d5_pin + self.d6_pin = d6_pin + self.d7_pin = d7_pin + if self.d0_pin and self.d1_pin and self.d2_pin and self.d3_pin: + self._4bit = False + else: + # This is really 4-bit mode, and the 4 data pins were just + # passed as the first 4 arguments, so we switch things around. + self.d0_pin = None + self.d1_pin = None + self.d2_pin = None + self.d3_pin = None + self.d4_pin = d0_pin + self.d5_pin = d1_pin + self.d6_pin = d2_pin + self.d7_pin = d3_pin + self.rs_pin.init(Pin.OUT) + self.rs_pin.value(0) + if self.rw_pin: + self.rw_pin.init(Pin.OUT) + self.rw_pin.value(0) + self.enable_pin.init(Pin.OUT) + self.enable_pin.value(0) + self.d4_pin.init(Pin.OUT) + self.d5_pin.init(Pin.OUT) + self.d6_pin.init(Pin.OUT) + self.d7_pin.init(Pin.OUT) + self.d4_pin.value(0) + self.d5_pin.value(0) + self.d6_pin.value(0) + self.d7_pin.value(0) + if not self._4bit: + self.d0_pin.init(Pin.OUT) + self.d1_pin.init(Pin.OUT) + self.d2_pin.init(Pin.OUT) + self.d3_pin.init(Pin.OUT) + self.d0_pin.value(0) + self.d1_pin.value(0) + self.d2_pin.value(0) + self.d3_pin.value(0) + if self.backlight_pin is not None: + self.backlight_pin.init(Pin.OUT) + self.backlight_pin.value(0) + + # See about splitting this into begin + + sleep_ms(20) # Allow LCD time to powerup + # Send reset 3 times + self.hal_write_init_nibble(self.LCD_FUNCTION_RESET) + sleep_ms(5) # need to delay at least 4.1 msec + self.hal_write_init_nibble(self.LCD_FUNCTION_RESET) + sleep_ms(1) + self.hal_write_init_nibble(self.LCD_FUNCTION_RESET) + sleep_ms(1) + cmd = self.LCD_FUNCTION + if not self._4bit: + cmd |= self.LCD_FUNCTION_8BIT + self.hal_write_init_nibble(cmd) + sleep_ms(1) + LcdApi.__init__(self, num_lines, num_columns) + if num_lines > 1: + cmd |= self.LCD_FUNCTION_2LINES + self.hal_write_command(cmd) + + def hal_pulse_enable(self): + """Pulse the enable line high, and then low again.""" + self.enable_pin.value(0) + sleep_us(1) + self.enable_pin.value(1) + sleep_us(1) # Enable pulse needs to be > 450 nsec + self.enable_pin.value(0) + sleep_us(100) # Commands need > 37us to settle + + def hal_write_init_nibble(self, nibble): + """Writes an initialization nibble to the LCD. + + This particular function is only used during initialization. + """ + self.hal_write_4bits(nibble >> 4) + + def hal_backlight_on(self): + """Allows the hal layer to turn the backlight on.""" + if self.backlight_pin: + self.backlight_pin.value(1) + + def hal_backlight_off(self): + """Allows the hal layer to turn the backlight off.""" + if self.backlight_pin: + self.backlight_pin.value(0) + + def hal_write_command(self, cmd): + """Writes a command to the LCD. + + Data is latched on the falling edge of E. + """ + self.rs_pin.value(0) + self.hal_write_8bits(cmd) + if cmd <= 3: + # The home and clear commands require a worst + # case delay of 4.1 msec + sleep_ms(5) + + def hal_write_data(self, data): + """Write data to the LCD.""" + self.rs_pin.value(1) + self.hal_write_8bits(data) + + def hal_write_8bits(self, value): + """Writes 8 bits of data to the LCD.""" + if self.rw_pin: + self.rw_pin.value(0) + if self._4bit: + self.hal_write_4bits(value >> 4) + self.hal_write_4bits(value) + else: + self.d3_pin.value(value & 0x08) + self.d2_pin.value(value & 0x04) + self.d1_pin.value(value & 0x02) + self.d0_pin.value(value & 0x01) + self.hal_write_4bits(value >> 4) + + def hal_write_4bits(self, nibble): + """Writes 4 bits of data to the LCD.""" + self.d7_pin.value(nibble & 0x08) + self.d6_pin.value(nibble & 0x04) + self.d5_pin.value(nibble & 0x02) + self.d4_pin.value(nibble & 0x01) + self.hal_pulse_enable() |