commit abadccf6121adee9282a0bb4a4582518d924c846 Author: Fred Boniface Date: Tue Nov 19 20:19:14 2024 +0000 Initial diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7fd33be --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2024 Frederick Boniface + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Pico_ePaper_2_13_v4.py b/Pico_ePaper_2_13_v4.py new file mode 100644 index 0000000..6e97da7 --- /dev/null +++ b/Pico_ePaper_2_13_v4.py @@ -0,0 +1,720 @@ +# ***************************************************************************** +# * | File : Pico_ePaper-2.13_V3.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V1.0 +# * | Date : 2021-11-01 +# # | Info : python demo +# ----------------------------------------------------------------------------- +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documnetation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +from machine import Pin, SPI +import framebuf +import utime + + +EPD_WIDTH = 122 +EPD_HEIGHT = 250 + +RST_PIN = 12 +DC_PIN = 8 +CS_PIN = 9 +BUSY_PIN = 13 + +class EPD_2in13_V4_Portrait(framebuf.FrameBuffer): + def __init__(self): + self.reset_pin = Pin(RST_PIN, Pin.OUT) + + self.busy_pin = Pin(BUSY_PIN, Pin.IN, Pin.PULL_UP) + self.cs_pin = Pin(CS_PIN, Pin.OUT) + if EPD_WIDTH % 8 == 0: + self.width = EPD_WIDTH + else : + self.width = (EPD_WIDTH // 8) * 8 + 8 + self.height = EPD_HEIGHT + + self.spi = SPI(1) + self.spi.init(baudrate=4000_000) + self.dc_pin = Pin(DC_PIN, Pin.OUT) + + self.buffer = bytearray(self.height * self.width // 8) + super().__init__(self.buffer, self.width, self.height, framebuf.MONO_HLSB) + self.init() + + ''' + function :Change the pin state + parameter: + pin : pin + value : state + ''' + def digital_write(self, pin, value): + pin.value(value) + + ''' + function : Read the pin state + parameter: + pin : pin + ''' + def digital_read(self, pin): + return pin.value() + + ''' + function : The time delay function + parameter: + delaytime : ms + ''' + def delay_ms(self, delaytime): + utime.sleep(delaytime / 1000.0) + + ''' + function : Write data to SPI + parameter: + data : data + ''' + def spi_writebyte(self, data): + self.spi.write(bytearray(data)) + + ''' + function :Hardware reset + parameter: + ''' + def reset(self): + self.digital_write(self.reset_pin, 1) + self.delay_ms(20) + self.digital_write(self.reset_pin, 0) + self.delay_ms(2) + self.digital_write(self.reset_pin, 1) + self.delay_ms(20) + + ''' + function :send command + parameter: + command : Command register + ''' + def send_command(self, command): + self.digital_write(self.dc_pin, 0) + self.digital_write(self.cs_pin, 0) + self.spi_writebyte([command]) + self.digital_write(self.cs_pin, 1) + + ''' + function :send data + parameter: + data : Write data + ''' + def send_data(self, data): + self.digital_write(self.dc_pin, 1) + self.digital_write(self.cs_pin, 0) + self.spi_writebyte([data]) + self.digital_write(self.cs_pin, 1) + + def send_data1(self, buf): + self.digital_write(self.dc_pin, 1) + self.digital_write(self.cs_pin, 0) + self.spi.write(bytearray(buf)) + self.digital_write(self.cs_pin, 1) + + ''' + function :Wait until the busy_pin goes LOW + parameter: + ''' + def ReadBusy(self): + print('busy') + self.delay_ms(10) + while(self.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy + self.delay_ms(10) + print('busy release') + + ''' + function : Turn On Display + parameter: + ''' + def TurnOnDisplay(self): + self.send_command(0x22) # Display Update Control + self.send_data(0xf7) + self.send_command(0x20) # Activate Display Update Sequence + self.ReadBusy() + + ''' + function : Turn On Display Fast + parameter: + ''' + def TurnOnDisplay_Fast(self): + self.send_command(0x22) # Display Update Control + self.send_data(0xC7) # fast:0x0c, quality:0x0f, 0xcf + self.send_command(0x20) # Activate Display Update Sequence + self.ReadBusy() + + ''' + function : Turn On Display Part + parameter: + ''' + def TurnOnDisplayPart(self): + self.send_command(0x22) # Display Update Control + self.send_data(0xff) # fast:0x0c, quality:0x0f, 0xcf + self.send_command(0x20) # Activate Display Update Sequence + self.ReadBusy() + + ''' + function : Setting the display window + parameter: + Xstart : X-axis starting position + Ystart : Y-axis starting position + Xend : End position of X-axis + Yend : End position of Y-axis + ''' + def SetWindows(self, Xstart, Ystart, Xend, Yend): + self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION + self.send_data((Xstart >> 3) & 0xFF) + self.send_data((Xend >> 3) & 0xFF) + + self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION + self.send_data(Ystart & 0xFF) + self.send_data((Ystart >> 8) & 0xFF) + self.send_data(Yend & 0xFF) + self.send_data((Yend >> 8) & 0xFF) + + ''' + function : Set Cursor + parameter: + Xstart : X-axis starting position + Ystart : Y-axis starting position + ''' + def SetCursor(self, Xstart, Ystart): + self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER + self.send_data(Xstart & 0xFF) + + self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER + self.send_data(Ystart & 0xFF) + self.send_data((Ystart >> 8) & 0xFF) + + ''' + function : Initialize the e-Paper register + parameter: + ''' + def init(self): + print('init') + self.reset() + self.delay_ms(100) + + self.ReadBusy() + self.send_command(0x12) # SWRESET + self.ReadBusy() + + self.send_command(0x01) # Driver output control + self.send_data(0xf9) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x11) #data entry mode + self.send_data(0x03) + + self.SetWindows(0, 0, self.width-1, self.height-1) + self.SetCursor(0, 0) + + self.send_command(0x3C) # BorderWaveform + self.send_data(0x05) + + self.send_command(0x21) # Display update control + self.send_data(0x00) + self.send_data(0x80) + + self.send_command(0x18) # Read built-in temperature sensor + self.send_data(0x80) + + self.ReadBusy() + + ''' + function : Initialize the e-Paper fast register + parameter: + ''' + def init_fast(self): + print('init_fast') + self.reset() + self.delay_ms(100) + + self.send_command(0x12) #SWRESET + self.ReadBusy() + + self.send_command(0x18) # Read built-in temperature sensor + self.send_command(0x80) + + self.send_command(0x11) # data entry mode + self.send_data(0x03) + + self.SetWindow(0, 0, self.width-1, self.height-1) + self.SetCursor(0, 0) + + self.send_command(0x22) # Load temperature value + self.send_data(0xB1) + self.send_command(0x20) + self.ReadBusy() + + self.send_command(0x1A) # Write to temperature register + self.send_data(0x64) + self.send_data(0x00) + + self.send_command(0x22) # Load temperature value + self.send_data(0x91) + self.send_command(0x20) + self.ReadBusy() + + return 0 + + ''' + function : Clear screen + parameter: + ''' + def Clear(self): + self.send_command(0x24) + self.send_data1([0xff] * self.height * int(self.width / 8)) + + self.TurnOnDisplay() + + ''' + function : Sends the image buffer in RAM to e-Paper and displays + parameter: + image : Image data + ''' + def display(self, image): + self.send_command(0x24) + self.send_data1(image) + self.TurnOnDisplay() + + def display_fast(self, image): + self.send_command(0x24) + self.send_data2(image) + self.TurnOnDisplay_Fast() + + ''' + function : Refresh a base image + parameter: + image : Image data + ''' + def Display_Base(self, image): + self.send_command(0x24) + self.send_data1(image) + + self.send_command(0x26) + self.send_data1(image) + + self.TurnOnDisplay() + + ''' + function : Sends the image buffer in RAM to e-Paper and partial refresh + parameter: + image : Image data + ''' + def displayPartial(self, image): + self.reset() + + self.send_command(0x3C) # BorderWavefrom + self.send_data(0x80) + + self.send_command(0x01) # Driver output control + self.send_data(0xF9) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x11) # data entry mode + self.send_data(0x03) + + self.SetWindows(0, 0, self.width-1, self.height-1) + self.SetCursor(0, 0) + + self.send_command(0x24) # WRITE_RAM + self.send_data1(image) + self.TurnOnDisplayPart() + + ''' + function : Enter sleep mode + parameter: + ''' + def sleep(self): + self.send_command(0x10) #enter deep sleep + self.send_data(0x01) + self.delay_ms(100) + + +class EPD_2in13_V4_Landscape(framebuf.FrameBuffer): + def __init__(self): + self.reset_pin = Pin(RST_PIN, Pin.OUT) + + self.busy_pin = Pin(BUSY_PIN, Pin.IN, Pin.PULL_UP) + self.cs_pin = Pin(CS_PIN, Pin.OUT) + if EPD_WIDTH % 8 == 0: + self.width = EPD_WIDTH + else : + self.width = (EPD_WIDTH // 8) * 8 + 8 + + self.height = EPD_HEIGHT + + self.spi = SPI(1) + self.spi.init(baudrate=4000_000) + self.dc_pin = Pin(DC_PIN, Pin.OUT) + + self.buffer = bytearray(self.height * self.width // 8) + super().__init__(self.buffer, self.height, self.width, framebuf.MONO_VLSB) + self.init() + + def digital_write(self, pin, value): + pin.value(value) + + def digital_read(self, pin): + return pin.value() + + def delay_ms(self, delaytime): + utime.sleep(delaytime / 1000.0) + + def spi_writebyte(self, data): + self.spi.write(bytearray(data)) + + def reset(self): + self.digital_write(self.reset_pin, 1) + self.delay_ms(20) + self.digital_write(self.reset_pin, 0) + self.delay_ms(2) + self.digital_write(self.reset_pin, 1) + self.delay_ms(20) + + def send_command(self, command): + self.digital_write(self.dc_pin, 0) + self.digital_write(self.cs_pin, 0) + self.spi_writebyte([command]) + self.digital_write(self.cs_pin, 1) + + def send_data(self, data): + self.digital_write(self.dc_pin, 1) + self.digital_write(self.cs_pin, 0) + self.spi_writebyte([data]) + self.digital_write(self.cs_pin, 1) + + def send_data1(self, buf): + self.digital_write(self.dc_pin, 1) + self.digital_write(self.cs_pin, 0) + self.spi.write(bytearray(buf)) + self.digital_write(self.cs_pin, 1) + + def ReadBusy(self): + print('busy') + self.delay_ms(10) + while(self.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy + self.delay_ms(10) + print('busy release') + + ''' + function : Turn On Display + parameter: + ''' + def TurnOnDisplay(self): + self.send_command(0x22) # Display Update Control + self.send_data(0xf7) + self.send_command(0x20) # Activate Display Update Sequence + self.ReadBusy() + + ''' + function : Turn On Display Fast + parameter: + ''' + def TurnOnDisplay_Fast(self): + self.send_command(0x22) # Display Update Control + self.send_data(0xC7) # fast:0x0c, quality:0x0f, 0xcf + self.send_command(0x20) # Activate Display Update Sequence + self.ReadBusy() + + ''' + function : Turn On Display Part + parameter: + ''' + def TurnOnDisplayPart(self): + self.send_command(0x22) # Display Update Control + self.send_data(0xff) # fast:0x0c, quality:0x0f, 0xcf + self.send_command(0x20) # Activate Display Update Sequence + self.ReadBusy() + + ''' + function : Setting the display window + parameter: + Xstart : X-axis starting position + Ystart : Y-axis starting position + Xend : End position of X-axis + Yend : End position of Y-axis + ''' + def SetWindows(self, Xstart, Ystart, Xend, Yend): + self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION + self.send_data((Xstart >> 3) & 0xFF) + self.send_data((Xend >> 3) & 0xFF) + + self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION + self.send_data(Ystart & 0xFF) + self.send_data((Ystart >> 8) & 0xFF) + self.send_data(Yend & 0xFF) + self.send_data((Yend >> 8) & 0xFF) + + ''' + function : Set Cursor + parameter: + Xstart : X-axis starting position + Ystart : Y-axis starting position + ''' + def SetCursor(self, Xstart, Ystart): + self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER + self.send_data(Xstart & 0xFF) + + self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER + self.send_data(Ystart & 0xFF) + self.send_data((Ystart >> 8) & 0xFF) + + ''' + function : Initialize the e-Paper register + parameter: + ''' + def init(self): + print('init') + self.reset() + self.delay_ms(100) + + self.ReadBusy() + self.send_command(0x12) # SWRESET + self.ReadBusy() + + self.send_command(0x01) # Driver output control + self.send_data(0xf9) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x11) #data entry mode + self.send_data(0x07) + + self.SetWindows(0, 0, self.width-1, self.height-1) + self.SetCursor(0, 0) + + self.send_command(0x3C) # BorderWaveform + self.send_data(0x05) + + self.send_command(0x21) # Display update control + self.send_data(0x00) + self.send_data(0x80) + + self.send_command(0x18) # Read built-in temperature sensor + self.send_data(0x80) + + self.ReadBusy() + + ''' + function : Initialize the e-Paper fast register + parameter: + ''' + def init_fast(self): + print('init_fast') + self.reset() + self.delay_ms(100) + + self.send_command(0x12) #SWRESET + self.ReadBusy() + + self.send_command(0x18) # Read built-in temperature sensor + self.send_command(0x80) + + self.send_command(0x11) # data entry mode + self.send_data(0x07) + + self.SetWindow(0, 0, self.width-1, self.height-1) + self.SetCursor(0, 0) + + self.send_command(0x22) # Load temperature value + self.send_data(0xB1) + self.send_command(0x20) + self.ReadBusy() + + self.send_command(0x1A) # Write to temperature register + self.send_data(0x64) + self.send_data(0x00) + + self.send_command(0x22) # Load temperature value + self.send_data(0x91) + self.send_command(0x20) + self.ReadBusy() + + return 0 + + ''' + function : Clear screen + parameter: + ''' + def Clear(self): + self.send_command(0x24) + self.send_data1([0xff] * self.height * int(self.width / 8)) + + self.TurnOnDisplay() + + ''' + function : Sends the image buffer in RAM to e-Paper and displays + parameter: + image : Image data + ''' + def display(self, image): + self.send_command(0x24) + for j in range(int(self.width / 8) - 1, -1, -1): + for i in range(0, self.height): + self.send_data(image[i + j * self.height]) + self.TurnOnDisplay() + + def display_fast(self, image): + self.send_command(0x24) + for j in range(int(self.width / 8) - 1, -1, -1): + for i in range(0, self.height): + self.send_data(image[i + j * self.height]) + self.TurnOnDisplay_Fast() + + ''' + function : Refresh a base image + parameter: + image : Image data + ''' + def Display_Base(self, image): + self.send_command(0x24) + for j in range(int(self.width / 8) - 1, -1, -1): + for i in range(0, self.height): + self.send_data(image[i + j * self.height]) + + self.send_command(0x26) + for j in range(int(self.width / 8) - 1, -1, -1): + for i in range(0, self.height): + self.send_data(image[i + j * self.height]) + + self.TurnOnDisplay() + + ''' + function : Sends the image buffer in RAM to e-Paper and partial refresh + parameter: + image : Image data + ''' + def displayPartial(self, image): + self.reset() + + self.send_command(0x3C) # BorderWavefrom + self.send_data(0x80) + + self.send_command(0x01) # Driver output control + self.send_data(0xF9) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x11) # data entry mode + self.send_data(0x07) + + self.SetWindows(0, 0, self.width-1, self.height-1) + self.SetCursor(0, 0) + + self.send_command(0x24) # WRITE_RAM + for j in range(int(self.width / 8) - 1, -1, -1): + for i in range(0, self.height): + self.send_data(image[i + j * self.height]) + self.TurnOnDisplayPart() + + ''' + function : Enter sleep mode + parameter: + ''' + def sleep(self): + self.send_command(0x10) #enter deep sleep + self.send_data(0x01) + self.delay_ms(100) + + +if __name__=='__main__': + epd = EPD_2in13_V4_Landscape() + epd.Clear() + + epd.fill(0xff) + epd.text("Waveshare", 0, 10, 0x00) + epd.text("ePaper-2.13_V4", 0, 20, 0x00) + epd.text("Raspberry Pico", 0, 30, 0x00) + epd.text("Hello World", 0, 40, 0x00) + epd.display(epd.buffer) + epd.delay_ms(2000) + + epd.vline(5, 55, 60, 0x00) + epd.vline(100, 55, 60, 0x00) + epd.hline(5, 55, 95, 0x00) + epd.hline(5, 115, 95, 0x00) + epd.line(5, 55, 100, 115, 0x00) + epd.line(100, 55, 5, 115, 0x00) + epd.display(epd.buffer) + epd.delay_ms(2000) + + epd.rect(130, 10, 40, 80, 0x00) + epd.fill_rect(190, 10, 40, 80, 0x00) + epd.Display_Base(epd.buffer) + epd.delay_ms(2000) + + epd.init() + for i in range(0, 10): + epd.fill_rect(175, 105, 10, 10, 0xff) + epd.text(str(i), 177, 106, 0x00) + epd.displayPartial(epd.buffer) + + print("sleep") + epd.init() + epd.Clear() + epd.delay_ms(2000) + epd.sleep() + + + + epd = EPD_2in13_V4_Portrait() + epd.Clear() + + epd.fill(0xff) + epd.text("Waveshare", 0, 10, 0x00) + epd.text("ePaper-2.13_V4", 0, 30, 0x00) + epd.text("Raspberry Pico", 0, 50, 0x00) + epd.text("Hello World", 0, 70, 0x00) + epd.display(epd.buffer) + epd.delay_ms(2000) + + epd.vline(10, 90, 60, 0x00) + epd.vline(90, 90, 60, 0x00) + epd.hline(10, 90, 80, 0x00) + epd.hline(10, 150, 80, 0x00) + epd.line(10, 90, 90, 150, 0x00) + epd.line(90, 90, 10, 150, 0x00) + epd.display(epd.buffer) + epd.delay_ms(2000) + + epd.rect(10, 180, 50, 40, 0x00) + epd.fill_rect(60, 180, 50, 40, 0x00) + epd.Display_Base(epd.buffer) + epd.delay_ms(2000) + + epd.init() + for i in range(0, 10): + epd.fill_rect(40, 230, 40, 10, 0xff) + epd.text(str(i), 60, 230, 0x00) + epd.displayPartial(epd.buffer) + + print("sleep") + epd.init() + epd.Clear() + epd.delay_ms(2000) + epd.sleep() \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..01c1afd --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# PicoWidget + +PicoWidget is a basic program written for a Raspberry Pi Pico W paired with a Waveshare 2.13" (v4) ePaper display. Note that the program is written for the black/white display which supports partial updates. + +## Use + +Install the MicroPython u2f file on to your Pico, then use Thonny or VSCode to upload the `.py` files and create an `ico` directory and upload the `bin` files there. + +Adjust the values in `config.py`: + - WIFI_SSID = Your WiFi SSID + - WIFI_PASS = Your WiFi Password + - LATLONG = (lat,long). + +Then just power the Pico and you'll have tour very own PicoWidget. + +## License + +This project incorporates files from `ezFBfont` which is licensed under the MIT license. + +This project incorporates font data from `ezFBfont` which was originally sourced from `Frederic Cambus` under the BSD-3-Clause license. + +Files sourced from other projects all contain licensing details within the files. + +All other files are released under the MIT license. \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..5e3e4e0 --- /dev/null +++ b/config.py @@ -0,0 +1,5 @@ +WIFI_SSID = "" +WIFI_PASS = "" + +WEATHER_URL = "https://api.open-meteo.com/" +LATLONG = (0.00,0.00) \ No newline at end of file diff --git a/ezFBfont.py b/ezFBfont.py new file mode 100644 index 0000000..edad5dc --- /dev/null +++ b/ezFBfont.py @@ -0,0 +1,179 @@ +# ezFBfont.py() : a simple string writer for small mono displays and user selected fonts. +# See MARQUEE.md for documentation + +# Extensively re-worked from the 'writer' class by +# Peter Hinch: +# https://github.com/peterhinch/micropython-font-to-py +# - Released under the MIT License (MIT). See LICENSE. +# - Copyright (c) 2019-2021 Peter Hinch + +import framebuf + +# Basic string writing class +class ezFBfont(): + + def __init__(self, device, + font, + fg = 1, + bg = 0, + tkey = -1, + halign = 'left', + valign = 'top', + vgap = 0, + hgap = 0, + split = '\n', + cswap = False, + verbose = False): + + self._device = device + self._font = font + self.name = self._font.__name__ + + # font and color; only monochrome HLSB fonts are supported + self._font_format = framebuf.MONO_HLSB + self._font_colors = 2 + self._palette_format = framebuf.RGB565 # support up to 65536 colors when blitting + # byte order for 16bit colors + self._cswap = cswap + # inform + if verbose: + fstr = '{} : initialised: height: {}, {} width: {}, baseline: {}' + print(fstr.format(self.name, self._font.height(), + 'fixed' if self._font.monospaced() else 'max', + self._font.max_width(), self._font.baseline())) + # apply init color and alignment as default + self.set_default(fg, bg, tkey, halign, valign, hgap, vgap, split, verbose) + + def _check_halign(self, h): + if h not in ('left','center','right'): + raise ValueError('Unknown horizontal alignment: ' + h) + return h + + def _check_valign(self, v): + if v not in ('top','center','baseline','bottom'): + raise ValueError('Unknown vertical alignment: ' + v) + return v + + def _line_size(self, string): + x = 0 + for char in string: + _, _, char_width = self._font.get_ch(char) + x += char_width + self.hgap if char_width > 0 else 0 + x = x - self.hgap if x != 0 else x # remove any trailing hgap + return x, self._font.height() + + def _swap_bytes(self, color): + # flip the left and right bytes in a 16 bit color word if required + return ((color & 255) << 8) + (color >> 8) if self._cswap else color + + def _put_char(self, char, x, y, fg, bg, tkey): + # fetch the glyph + glyph, char_height, char_width = self._font.get_ch(char) + if glyph is None: + return None, None # Nothing to write + # buffers + palette_buf = bytearray(self._font_colors * 2) + buf = bytearray(glyph) + # assemble color map + palette = framebuf.FrameBuffer(palette_buf, self._font_colors, 1, self._palette_format) + palette.pixel(0, 0, self._swap_bytes(bg)) + palette.pixel(self._font_colors -1, 0, self._swap_bytes(fg)) + # fetch and blit the glyph + charbuf = framebuf.FrameBuffer(buf, char_width, char_height, self._font_format) + self._device.blit(charbuf, x, y, tkey, palette) + return char_width, char_height + + def set_default(self, fg=None, bg=None, tkey=None, + halign=None, valign=None, hgap=None, vgap=None, split=None, verbose=None): + # Sets the default value for all supplied arguments + self.fg = self.fg if fg is None else fg + self.bg = self.bg if bg is None else bg + self.tkey = self.tkey if tkey is None else tkey + self.halign = self.halign if halign is None else self._check_halign(halign) + self.valign = self.valign if valign is None else self._check_valign(valign) + self.hgap = self.hgap if hgap is None else hgap + self.vgap = self.vgap if vgap is None else vgap + self.split = self.split if split is None else split + self._verbose = self._verbose if verbose is None else verbose + if self._verbose: + fstr = '{} = fg: {}, bg: {}, tkey: {}, halign: {}, valign: {}, hgap: {}, vgap: {}, split: {}' + print(fstr.format(self.name, self.fg, self.bg, self.tkey, + self.halign, self.valign, self.hgap, self.vgap, repr(split))) + + def size(self, string): + if len(string) == 0: + return 0, 0 + lines = string.split(self.split) + w = 0 + for line in lines: + x, _ = self._line_size(line) + w = max(w, x) # record the widest line + h = (len(lines) * (self._font.height() + self.vgap)) - self.vgap + return w, h + + def rect(self, string, x, y, halign=None, valign=None): + if len(string) == 0: + return x, y, 0, 0 + # apply alignment overrides + halign = self.halign if halign is None else self._check_halign(halign) + valign = self.valign if valign is None else self._check_valign(valign) + # get the x,y size of the rendered string + wide, high = self.size(string) + # apply alignment + xmin = x + if halign == 'center': + xmin = int(x - (wide / 2)) + elif halign == 'right': + xmin = x - wide + ymin = y + if valign == 'baseline': + ymin = y - self._font.baseline() + elif valign == 'center': + ymin = int(y - (high / 2)) + elif valign == 'bottom': + ymin = y - high + # return the result + return xmin,ymin,wide,high + + def write(self, string, x, y, fg=None, bg=None, tkey=None, + halign=None, valign=None): + if len(string) == 0: + return True + all_chars = True + # Argument overrides + fg = self.fg if fg is None else fg + bg = self.bg if bg is None else bg + tkey = self.tkey if tkey is None else tkey + halign = self.halign if halign is None else self._check_halign(halign) + valign = self.valign if valign is None else self._check_valign(valign) + # Break the string into lines + lines = string.split(self.split) + # vertical alignment + high = (len(lines) * (self._font.height() + self.vgap)) - self.vgap + ypos = y + if valign == 'baseline': + ypos = y - self._font.baseline() + 1 + elif valign == 'center': + ypos = int(y - (high / 2)) + elif valign == 'bottom': + ypos = y - high + for line in lines: + wide, high = self._line_size(line) + # horizontal alignment + if halign == 'left': + xpos = x + elif halign == 'right': + xpos = x - wide + else: + xpos = int(x - (wide / 2)) + # write the line + for char in line: + cx, _ = self._put_char(char, xpos, ypos, fg, bg, tkey) + if cx is None: + if self._verbose: + print('{}: missing char: {} (0x{:02X})'.format(self.name, repr(char), ord(char))) + all_chars = False + else: + xpos += cx + self.hgap + ypos += high + self.vgap + return all_chars \ No newline at end of file diff --git a/ezFBfont_spleen_16x32_ascii_30.py b/ezFBfont_spleen_16x32_ascii_30.py new file mode 100644 index 0000000..9bb5750 --- /dev/null +++ b/ezFBfont_spleen_16x32_ascii_30.py @@ -0,0 +1,171 @@ +''' + ezFBfont_spleen_16x32_ascii_30 : generated as part of the microPyEZfonts repository + https://github.com/easytarget/microPyEZfonts + + This font definition can be used with the "ezFBfont" class provided there. + It can also be used with the "writer" class from Peter Hinches micropython + font-to-py tool: https://github.com/peterhinch/micropython-font-to-py + + Original spleen_16x32.bdf font file was sourced from the U8G2 project: + https://github.com/olikraus/u8g2 + +''' +# Code generated by bdf2dict.py +# Font: spleen_16x32 +# Cmd: ['bdf2dict.py'], ['Latin-1-bdf-sources/spleen-16x32.bdf', '_', './ascii-char.set'] +# Date: 2024-07-31 14:57:34 + +# Customised by Fred Boniface 2024-11-18 +''' + Original Copyright, Comments and Notices from source: + + COPYRIGHT /* + COPYRIGHT * Spleen 16x32 1.9.1 + COPYRIGHT * Copyright (c) 2018-2022, Frederic Cambus + COPYRIGHT * https://www.cambus.net/ + COPYRIGHT * + COPYRIGHT * Created: 2018-08-12 + COPYRIGHT * Last Updated: 2020-10-10 + COPYRIGHT * + COPYRIGHT * Spleen is released under the BSD 2-Clause license. + COPYRIGHT * See LICENSE file for details. + COPYRIGHT * + COPYRIGHT * SPDX-License-Identifier: BSD-2-Clause + COPYRIGHT */ + + COMMENT "Copyright (c) 2018-2022, Frederic Cambus" +''' +version = '0.33' +name = '-misc-spleen-medium-r-normal--32-320-72-72-c-160-iso10646-1' +family = 'spleen' +weight = 'medium' +size = 32 + +def height(): + return 30 + +def baseline(): + return 24 + +def max_width(): + return 16 + +def hmap(): + return True + +def reverse(): + return False + +def monospaced(): + return True + +def min_ch(): + return 32 + +def max_ch(): + return 176 + +_g = { + 32:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 33:b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x01\x80\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 34:b'\x00\x00\x00\x00\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 35:b'\x00\x00\x00\x00\x00\x00\x00\x00\x18\x18\x18\x18\x18\x18\x18\x18\x7f\xfe\x7f\xfe\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x7f\xfe\x7f\xfe\x18\x18\x18\x18\x18\x18\x18\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 36:b'\x00\x00\x00\x00\x01\x80\x01\x80\x0f\xfc\x1f\xfc9\x801\x801\x801\x801\x809\x80\x1f\xf0\x0f\xf8\x01\x9c\x01\x8c\x01\x8c\x01\x8c\x01\x8c\x01\x8c\x01\x8c\x01\x9c?\xf8?\xf0\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00', + 37:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x0e\x0c\x1b\x18\x1b\x18\x1b0\x0e0\x00`\x00`\x00\xc0\x00\xc0\x01\x80\x01\x80\x03\x00\x03\x00\x068\x06l\x0cl\x0cl\x188\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 38:b'\x00\x00\x00\x00\x00\x00\x00\x00\x07\xe0\x0f\xf0\x1c8\x18\x18\x18\x18\x18\x18\x18\x18\x1c8\x0f\xf0\x07\xe0\x1f\x80?\xc0p\xec`|`8`0`8p|?\xee\x1f\xc6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 39:b'\x00\x00\x00\x00\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 40:b'\x00\x00\x00<\x00\xfc\x01\xe0\x03\x80\x07\x00\x06\x00\x0e\x00\x0c\x00\x1c\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x1c\x00\x0c\x00\x0e\x00\x06\x00\x07\x00\x03\x80\x01\xe0\x00\xfc\x00<\x00\x00\x00\x00\x00\x00', + 41:b'\x00\x00<\x00?\x00\x07\x80\x01\xc0\x00\xe0\x00`\x00p\x000\x008\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x008\x000\x00p\x00`\x00\xe0\x01\xc0\x07\x80?\x00<\x00\x00\x00\x00\x00\x00\x00', + 42:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x18\x1c8\x0ep\x07\xe0\x03\xc0\x03\xc0\x7f\xfe\x7f\xfe\x03\xc0\x03\xc0\x07\xe0\x0ep\x1c8\x18\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 43:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x80\x01\x80\x01\x80\x01\x80\x1f\xf8\x1f\xf8\x01\x80\x01\x80\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 44:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x80\x01\x80\x01\x80\x03\x80\x07\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 45:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xfc?\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 46:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x80\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 47:b'\x00\x00\x00\x06\x00\x06\x00\x0c\x00\x0c\x00\x18\x00\x18\x000\x000\x00`\x00`\x00\xc0\x00\xc0\x01\x80\x01\x80\x03\x00\x03\x00\x06\x00\x06\x00\x0c\x00\x0c\x00\x18\x00\x18\x000\x000\x00`\x00`\x00\x00\x00\x00\x00\x00\x00', + 48:b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf0\x1f\xf88\x1c0\x0c0\x0c0\x1c0<0|0\xec1\xcc3\x8c7\x0c>\x0c<\x0c8\x0c0\x0c0\x0c8\x1c\x1f\xf8\x0f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 49:b'\x00\x00\x00\x00\x00\x00\x00\x00\x03\x80\x07\x80\r\x80\x19\x80\x11\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x1f\xf8\x1f\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 50:b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf0\x1f\xf88\x1c0\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x18\x000\x00`\x00\xc0\x01\x80\x03\x00\x06\x00\x0c\x00\x18\x000\x0c0\x0c?\xfc?\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 51:b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf0\x1f\xf88\x1c0\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x18\x07\xf0\x07\xf0\x00\x18\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c0\x0c8\x1c\x1f\xf8\x0f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 52:b'\x00\x00\x00\x00\x00\x00\x00\x000\x000\x000\x000\x000000000000000000?\xfc?\xfc\x000\x000\x000\x000\x000\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 53:b'\x00\x00\x00\x00\x00\x00\x00\x00?\xfc?\xfc0\x0c0\x0c0\x000\x000\x000\x00?\xf0?\xf8\x00\x1c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c0\x0c8\x1c\x1f\xf8\x0f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 54:b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf0\x1f\xf88\x1c0\x0c0\x000\x000\x000\x00?\xf0?\xf80\x1c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c8\x1c\x1f\xf8\x0f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 55:b'\x00\x00\x00\x00\x00\x00\x00\x00?\xfc?\xfc0\x0c0\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x18\x000\x00`\x00\xc0\x01\x80\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 56:b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf0\x1f\xf88\x1c0\x0c0\x0c0\x0c0\x0c\x18\x18\x0f\xf0\x0f\xf0\x18\x180\x0c0\x0c0\x0c0\x0c0\x0c0\x0c8\x1c\x1f\xf8\x0f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 57:b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf0\x1f\xf88\x1c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c8\x0c\x1f\xfc\x0f\xfc\x00\x0c\x00\x0c\x00\x0c\x00\x0c0\x0c8\x1c\x1f\xf8\x0f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 58:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x80\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x80\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 59:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x80\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x80\x01\x80\x01\x80\x03\x80\x07\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 60:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\x008\x00p\x00\xe0\x01\xc0\x03\x80\x07\x00\x0e\x00\x1c\x008\x008\x00\x1c\x00\x0e\x00\x07\x00\x03\x80\x01\xc0\x00\xe0\x00p\x008\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 61:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xfc?\xfc\x00\x00\x00\x00\x00\x00\x00\x00?\xfc?\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 62:b'\x00\x00\x00\x00\x00\x00\x00\x008\x00\x1c\x00\x0e\x00\x07\x00\x03\x80\x01\xc0\x00\xe0\x00p\x008\x00\x1c\x00\x1c\x008\x00p\x00\xe0\x01\xc0\x03\x80\x07\x00\x0e\x00\x1c\x008\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 63:b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf0\x1f\xf88\x1c0\x0c\x00\x0c\x00\x0c\x00\x18\x000\x00`\x00\xc0\x00\xc0\x01\x80\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x01\x80\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 64:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf0\x1f\xf88\x1c0\x0c0\x0c1\xcc1\xcc1\xcc1\xcc1\xcc1\xcc1\xfc1\xfc0\x000\x008\x00\x1f\xf8\x0f\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 65:b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf0\x1f\xf88\x1c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c?\xfc?\xfc0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 66:b'\x00\x00\x00\x00\x00\x00\x00\x00?\xf0?\xf80\x1c0\x0c0\x0c0\x0c0\x0c0\x18?\xf0?\xf00\x180\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x1c?\xf8?\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 67:b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xfc\x1f\xfc8\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x008\x00\x1f\xfc\x0f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 68:b'\x00\x00\x00\x00\x00\x00\x00\x00?\xf0?\xf80\x1c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x1c?\xf8?\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 69:b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xfc\x1f\xfc8\x000\x000\x000\x000\x000\x000\x00?\xe0?\xe00\x000\x000\x000\x000\x000\x008\x00\x1f\xfc\x0f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 70:b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xfc\x1f\xfc8\x000\x000\x000\x000\x000\x000\x00?\xe0?\xe00\x000\x000\x000\x000\x000\x000\x000\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 71:b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xfc\x1f\xfc8\x000\x000\x000\x000\x000\x000\x000\xfc0\xfc0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c8\x0c\x1f\xfc\x0f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 72:b'\x00\x00\x00\x00\x00\x00\x00\x000\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c?\xfc?\xfc0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 73:b'\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xf8\x1f\xf8\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x1f\xf8\x1f\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 74:b'\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xf8\x1f\xf8\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x03\x80\x7f\x00~\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 75:b'\x00\x00\x00\x00\x00\x00\x00\x000\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x18000`?\xc0?\xc00`000\x180\x0c0\x0c0\x0c0\x0c0\x0c0\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 76:b'\x00\x00\x00\x00\x00\x00\x00\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x008\x00\x1f\xfc\x0f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 77:b'\x00\x00\x00\x00\x00\x00\x00\x000\x0c8\x1c<<>|7\xec3\xcc1\x8c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 78:b'\x00\x00\x00\x00\x00\x00\x00\x000\x0c0\x0c8\x0c8\x0c<\x0c<\x0c6\x0c6\x0c3\x0c3\x0c1\x8c1\x8c0\xcc0\xcc0l0l0<0<0\x1c0\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 79:b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf0\x1f\xf88\x1c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c8\x1c\x1f\xf8\x0f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 80:b'\x00\x00\x00\x00\x00\x00\x00\x00?\xf0?\xf80\x1c0\x0c0\x0c0\x0c0\x0c0\x0c0\x1c?\xf8?\xf00\x000\x000\x000\x000\x000\x000\x000\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 81:b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf0\x1f\xf88\x1c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c1\x8c1\x8c0\xcc8\xdc\x1f\xf8\x0f\xf0\x000\x000\x00\x18\x00\x18\x00\x00\x00\x00', + 82:b'\x00\x00\x00\x00\x00\x00\x00\x00?\xf0?\xf80\x1c0\x0c0\x0c0\x0c0\x0c0\x0c0\x18?\xf0?\xf00\x180\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 83:b'\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xfc\x1f\xfc8\x000\x000\x000\x000\x000\x008\x00\x1f\xf0\x0f\xf8\x00\x1c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x1c?\xf8?\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 84:b'\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xfe\x7f\xfe\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 85:b'\x00\x00\x00\x00\x00\x00\x00\x000\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c8\x0c\x1f\xfc\x0f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 86:b'\x00\x00\x00\x00\x00\x00\x00\x000\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c8\x1c\x1c8\x0ep\x07\xe0\x03\xc0\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 87:b'\x00\x00\x00\x00\x00\x00\x00\x000\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c1\x8c3\xcc7\xec>|<<8\x1c0\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 88:b'\x00\x00\x00\x00\x00\x00\x00\x000\x0c0\x0c0\x0c0\x0c0\x0c0\x0c8\x1c\x1c8\x0ep\x07\xe0\x07\xe0\x0ep\x1c88\x1c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 89:b'\x00\x00\x00\x00\x00\x00\x00\x000\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c8\x0c\x1f\xfc\x0f\xfc\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x1c?\xf8?\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 90:b'\x00\x00\x00\x00\x00\x00\x00\x00?\xfc?\xfc\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x18\x000\x00`\x00\xc0\x01\x80\x03\x00\x06\x00\x0c\x00\x18\x000\x000\x000\x00?\xfc?\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 91:b'\x0f\xfc\x0f\xfc\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0f\xfc\x0f\xfc\x00\x00\x00\x00', + 92:b'\x00\x00`\x00`\x000\x000\x00\x18\x00\x18\x00\x0c\x00\x0c\x00\x06\x00\x06\x00\x03\x00\x03\x00\x01\x80\x01\x80\x00\xc0\x00\xc0\x00`\x00`\x000\x000\x00\x18\x00\x18\x00\x0c\x00\x0c\x00\x06\x00\x06\x00\x00\x00\x00\x00\x00', + 93:b'?\xf0?\xf0\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000\x000?\xf0?\xf0\x00\x00\x00\x00', + 94:b'\x00\x00\x00\x00\x01\x80\x03\xc0\x07\xe0\x0ep\x1c88\x1cp\x0e`\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 95:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xfe\x7f\xfe\x00\x00\x00\x00', + 96:b'\x00\x00\x00\x00\x06\x00\x07\x00\x03\x80\x01\xc0\x00\xe0\x00`\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 97:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xf0\x1f\xf8\x00\x1c\x00\x0c\x00\x0c\x0f\xfc\x1f\xfc8\x0c0\x0c0\x0c0\x0c8\x0c\x1f\xfc\x0f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 98:b'\x00\x00\x00\x00\x00\x00\x00\x000\x000\x000\x000\x000\x000\x00?\xf0?\xf80\x1c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x1c?\xf8?\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 99:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xfc\x1f\xfc8\x000\x000\x000\x000\x000\x000\x000\x000\x008\x00\x1f\xfc\x0f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 100:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x0f\xfc\x1f\xfc8\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c8\x0c\x1f\xfc\x0f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 101:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xfc\x1f\xfc8\x0c0\x0c0\x0c0\x0c?\xfc?\xfc0\x000\x000\x008\x00\x1f\xfc\x0f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 102:b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\xf8\x03\xf8\x07\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x1f\xe0\x1f\xe0\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 103:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xfc\x1f\xfc8\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c8\x1c\x1f\xf8\x0f\xf0\x00\x18\x00\x0c\x00\x0c\x00\x1c?\xf8?\xf0', + 104:b'\x00\x00\x00\x00\x00\x00\x00\x000\x000\x000\x000\x000\x000\x00?\xf0?\xf80\x1c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 105:b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\x80\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x07\x80\x07\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\xe0\x01\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 106:b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\x80\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x03\x80\x1f\x00\x1e\x00', + 107:b'\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x180\x18p\x18\xe0\x19\xc0\x1b\x80\x1f\x00\x1f\x00\x1b\x80\x19\xc0\x18\xe0\x18p\x188\x18\x1c\x18\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 108:b'\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x07\x00\x03\xf8\x01\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 109:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>p>x1\x9c1\x8c1\x8c1\x8c1\x8c1\x8c1\x8c1\x8c0\x0c0\x0c0\x0c0\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 110:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xf0?\xf80\x1c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 111:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xf0\x1f\xf88\x1c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c8\x1c\x1f\xf8\x0f\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 112:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xf0?\xf80\x1c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x1c?\xf8?\xf00\x000\x000\x000\x000\x000\x00', + 113:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xfc\x1f\xfc8\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c8\x0c\x1f\xfc\x0f\xfc\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c\x00\x0c', + 114:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xfc\x1f\xfc8\x0c0\x0c0\x000\x000\x000\x000\x000\x000\x000\x000\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 115:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0f\xfc\x1f\xfc8\x000\x000\x008\x00\x1f\xf0\x0f\xf8\x00\x1c\x00\x0c\x00\x0c\x00\x1c?\xf8?\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 116:b'\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x1f\xe0\x1f\xe0\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x06\x00\x07\x00\x03\xf8\x01\xf8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 117:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c8\x0c\x1f\xfc\x0f\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 118:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c8\x1c\x1c8\x0ep\x07\xe0\x03\xc0\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 119:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x0c0\x0c0\x0c0\x0c1\x8c1\x8c1\x8c1\x8c1\x8c1\x8c1\x8c9\x8c\x1e|\x0e|\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 120:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x0c8\x1c\x1c8\x0ep\x07\xe0\x03\xc0\x03\xc0\x07\xe0\x0ep\x1c8\x18\x188\x1c0\x0c0\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 121:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c0\x0c8\x0c\x1f\xfc\x0f\xfc\x00\x0c\x00\x0c\x00\x0c\x00\x1c?\xf8?\xf0', + 122:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xfc?\xfc\x00\x0c\x00\x18\x000\x00`\x00\xc0\x01\x80\x03\x00\x06\x00\x0c\x00\x18\x00?\xfc?\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 123:b'\x00\xfc\x01\xfc\x03\x80\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x07\x00>\x00<\x00<\x00>\x00\x07\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x00\x03\x80\x01\xfc\x00\xfc\x00\x00\x00\x00', + 124:b'\x00\x00\x00\x00\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x00\x00\x00\x00\x00\x00\x00\x00', + 125:b'?\x00?\x80\x01\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xe0\x00|\x00<\x00<\x00|\x00\xe0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x00\xc0\x01\xc0?\x80?\x00\x00\x00\x00\x00', + 126:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x0c\x1f\x0c;\x9c1\xf80\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 176:b'\x00\x00\x00\x00\x03\xc0\x07\xe0\x0ep\x0c0\x0c0\x0c0\x0ep\x07\xe0\x03\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', +} + +def get_ch(ch): + c = ord(ch) + if c not in _g.keys(): + return None, 0, 0 + return memoryview(_g[c]), 30, 16 diff --git a/ezFBfont_spleen_8x16_ascii_14.py b/ezFBfont_spleen_8x16_ascii_14.py new file mode 100644 index 0000000..495efaa --- /dev/null +++ b/ezFBfont_spleen_8x16_ascii_14.py @@ -0,0 +1,171 @@ +''' + ezFBfont_spleen_8x16_ascii_14 : generated as part of the microPyEZfonts repository + https://github.com/easytarget/microPyEZfonts + + This font definition can be used with the "ezFBfont" class provided there. + It can also be used with the "writer" class from Peter Hinches micropython + font-to-py tool: https://github.com/peterhinch/micropython-font-to-py + + Original spleen_8x16.bdf font file was sourced from the U8G2 project: + https://github.com/olikraus/u8g2 + +''' +# Code generated by bdf2dict.py +# Font: spleen_8x16 +# Cmd: ['bdf2dict.py'], ['Latin-1-bdf-sources/spleen-8x16.bdf', '_', './ascii-char.set'] +# Date: 2024-07-31 14:57:38 + +# Customised by Fred Boniface 2024-11-18 +''' + Original Copyright, Comments and Notices from source: + + COPYRIGHT /* + COPYRIGHT * Spleen 8x16 1.9.1 + COPYRIGHT * Copyright (c) 2018-2022, Frederic Cambus + COPYRIGHT * https://www.cambus.net/ + COPYRIGHT * + COPYRIGHT * Created: 2018-08-11 + COPYRIGHT * Last Updated: 2020-10-10 + COPYRIGHT * + COPYRIGHT * Spleen is released under the BSD 2-Clause license. + COPYRIGHT * See LICENSE file for details. + COPYRIGHT * + COPYRIGHT * SPDX-License-Identifier: BSD-2-Clause + COPYRIGHT */ + + COMMENT "Copyright (c) 2018-2022, Frederic Cambus" +''' +version = '0.33' +name = '-misc-spleen-medium-r-normal--16-160-72-72-c-80-iso10646-1' +family = 'spleen' +weight = 'medium' +size = 16 + +def height(): + return 14 + +def baseline(): + return 11 + +def max_width(): + return 8 + +def hmap(): + return True + +def reverse(): + return False + +def monospaced(): + return True + +def min_ch(): + return 32 + +def max_ch(): + return 176 + +_g = { + 32:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 33:b'\x00\x18\x18\x18\x18\x18\x18\x18\x00\x18\x18\x00\x00\x00', + 34:b'ffff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 35:b'\x00ll\xfellll\xfell\x00\x00\x00', + 36:b'\x10~\xd0\xd0\xd0|\x16\x16\x16\x16\xfc\x10\x00\x00', + 37:b'\x00\x06fl\x0c\x18\x1806f`\x00\x00\x00', + 38:b'\x008lll8p\xda\xcc\xccz\x00\x00\x00', + 39:b'\x18\x18\x18\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 40:b'\x0e\x1800````00\x18\x0e\x00\x00', + 41:b'p\x18\x0c\x0c\x06\x06\x06\x06\x0c\x0c\x18p\x00\x00', + 42:b'\x00\x00\x00f<\x18\xff\x180000000000>\x00\x00', + 92:b'\xc0\xc0``00\x18\x18\x0c\x0c\x06\x06\x00\x00', + 93:b'|\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c|\x00\x00', + 94:b'\x108l\xc6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 95:b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe', + 96:b'0\x18\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + 97:b'\x00\x00\x00\x00|\x06~\xc6\xc6\xc6~\x00\x00\x00', + 98:b'\x00\xc0\xc0\xc0\xfc\xc6\xc6\xc6\xc6\xc6\xfc\x00\x00\x00', + 99:b'\x00\x00\x00\x00~\xc0\xc0\xc0\xc0\xc0~\x00\x00\x00', + 100:b'\x00\x06\x06\x06~\xc6\xc6\xc6\xc6\xc6~\x00\x00\x00', + 101:b'\x00\x00\x00\x00~\xc6\xc6\xfe\xc0\xc0~\x00\x00\x00', + 102:b'\x00\x1e000|00000\x00\x00\x00', + 103:b'\x00\x00\x00\x00~\xc6\xc6\xc6\xc6\xc6|\x06\x06\xfc', + 104:b'\x00\xc0\xc0\xc0\xfc\xc6\xc6\xc6\xc6\xc6\xc6\x00\x00\x00', + 105:b'\x00\x18\x18\x008\x18\x18\x18\x18\x18\x1c\x00\x00\x00', + 106:b'\x00\x18\x18\x00\x18\x18\x18\x18\x18\x18\x18\x18\x18p', + 107:b'\x00\xc0\xc0\xc0\xcc\xd8\xf0\xf0\xd8\xcc\xc6\x00\x00\x00', + 108:b'\x00000000000\x1c\x00\x00\x00', + 109:b'\x00\x00\x00\x00\xec\xd6\xd6\xd6\xd6\xc6\xc6\x00\x00\x00', + 110:b'\x00\x00\x00\x00\xfc\xc6\xc6\xc6\xc6\xc6\xc6\x00\x00\x00', + 111:b'\x00\x00\x00\x00|\xc6\xc6\xc6\xc6\xc6|\x00\x00\x00', + 112:b'\x00\x00\x00\x00\xfc\xc6\xc6\xc6\xc6\xc6\xfc\xc0\xc0\xc0', + 113:b'\x00\x00\x00\x00~\xc6\xc6\xc6\xc6\xc6~\x06\x06\x06', + 114:b'\x00\x00\x00\x00~\xc6\xc0\xc0\xc0\xc0\xc0\x00\x00\x00', + 115:b'\x00\x00\x00\x00~\xc0\xc0|\x06\x06\xfc\x00\x00\x00', + 116:b'\x00000|00000\x1e\x00\x00\x00', + 117:b'\x00\x00\x00\x00\xc6\xc6\xc6\xc6\xc6\xc6~\x00\x00\x00', + 118:b'\x00\x00\x00\x00\xc6\xc6\xc6\xc6l8\x10\x00\x00\x00', + 119:b'\x00\x00\x00\x00\xc6\xc6\xd6\xd6\xd6\xd6n\x00\x00\x00', + 120:b'\x00\x00\x00\x00\xc6l88l\xc6\xc6\x00\x00\x00', + 121:b'\x00\x00\x00\x00\xc6\xc6\xc6\xc6\xc6\xc6~\x06\x06\xfc', + 122:b'\x00\x00\x00\x00\xfe\x06\x0c\x180`\xfe\x00\x00\x00', + 123:b'\x0e\x18\x18\x18\x18pp\x18\x18\x18\x18\x0e\x00\x00', + 124:b'\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x18\x00\x00', + 125:b'p\x18\x18\x18\x18\x0e\x0e\x18\x18\x18\x18p\x00\x00', + 126:b'\x00\x00\x00\x00\x002~L\x00\x00\x00\x00\x00\x00', + 176:b'8ll8\x00\x00\x00\x00\x00\x00\x00\x00', +} + +def get_ch(ch): + c = ord(ch) + if c not in _g.keys(): + return None, 0, 0 + return memoryview(_g[c]), 14, 8 diff --git a/ico/weather-cloudy.bin b/ico/weather-cloudy.bin new file mode 100644 index 0000000..b7357e8 Binary files /dev/null and b/ico/weather-cloudy.bin differ diff --git a/ico/weather-fog.bin b/ico/weather-fog.bin new file mode 100644 index 0000000..5d5c6a6 Binary files /dev/null and b/ico/weather-fog.bin differ diff --git a/ico/weather-hail.bin b/ico/weather-hail.bin new file mode 100644 index 0000000..e6d909e Binary files /dev/null and b/ico/weather-hail.bin differ diff --git a/ico/weather-hazy.bin b/ico/weather-hazy.bin new file mode 100644 index 0000000..08f0a3c Binary files /dev/null and b/ico/weather-hazy.bin differ diff --git a/ico/weather-lightning-rainy.bin b/ico/weather-lightning-rainy.bin new file mode 100644 index 0000000..5330281 Binary files /dev/null and b/ico/weather-lightning-rainy.bin differ diff --git a/ico/weather-lightning.bin b/ico/weather-lightning.bin new file mode 100644 index 0000000..19488db Binary files /dev/null and b/ico/weather-lightning.bin differ diff --git a/ico/weather-night-partly-cloudy.bin b/ico/weather-night-partly-cloudy.bin new file mode 100644 index 0000000..733a750 Binary files /dev/null and b/ico/weather-night-partly-cloudy.bin differ diff --git a/ico/weather-night.bin b/ico/weather-night.bin new file mode 100644 index 0000000..fad8d93 Binary files /dev/null and b/ico/weather-night.bin differ diff --git a/ico/weather-partly-cloudy.bin b/ico/weather-partly-cloudy.bin new file mode 100644 index 0000000..68f141f Binary files /dev/null and b/ico/weather-partly-cloudy.bin differ diff --git a/ico/weather-partly-lightning.bin b/ico/weather-partly-lightning.bin new file mode 100644 index 0000000..c33d78f Binary files /dev/null and b/ico/weather-partly-lightning.bin differ diff --git a/ico/weather-partly-rainy.bin b/ico/weather-partly-rainy.bin new file mode 100644 index 0000000..c25cd89 Binary files /dev/null and b/ico/weather-partly-rainy.bin differ diff --git a/ico/weather-partly-snowy-rainy.bin b/ico/weather-partly-snowy-rainy.bin new file mode 100644 index 0000000..4c04acb Binary files /dev/null and b/ico/weather-partly-snowy-rainy.bin differ diff --git a/ico/weather-partly-snowy.bin b/ico/weather-partly-snowy.bin new file mode 100644 index 0000000..fdfb7b0 Binary files /dev/null and b/ico/weather-partly-snowy.bin differ diff --git a/ico/weather-pouring.bin b/ico/weather-pouring.bin new file mode 100644 index 0000000..9d7f1f4 Binary files /dev/null and b/ico/weather-pouring.bin differ diff --git a/ico/weather-rainy.bin b/ico/weather-rainy.bin new file mode 100644 index 0000000..db7f044 Binary files /dev/null and b/ico/weather-rainy.bin differ diff --git a/ico/weather-snowy-heavy.bin b/ico/weather-snowy-heavy.bin new file mode 100644 index 0000000..c184c85 Binary files /dev/null and b/ico/weather-snowy-heavy.bin differ diff --git a/ico/weather-snowy-rainy.bin b/ico/weather-snowy-rainy.bin new file mode 100644 index 0000000..e26c055 Binary files /dev/null and b/ico/weather-snowy-rainy.bin differ diff --git a/ico/weather-snowy.bin b/ico/weather-snowy.bin new file mode 100644 index 0000000..759231a Binary files /dev/null and b/ico/weather-snowy.bin differ diff --git a/ico/weather-sunny.bin b/ico/weather-sunny.bin new file mode 100644 index 0000000..a6c5b07 Binary files /dev/null and b/ico/weather-sunny.bin differ diff --git a/ico/weather-sunset-down.bin b/ico/weather-sunset-down.bin new file mode 100644 index 0000000..fbad692 Binary files /dev/null and b/ico/weather-sunset-down.bin differ diff --git a/ico/weather-sunset-up.bin b/ico/weather-sunset-up.bin new file mode 100644 index 0000000..b60f709 Binary files /dev/null and b/ico/weather-sunset-up.bin differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..417cc3c --- /dev/null +++ b/main.py @@ -0,0 +1,131 @@ +#Copyright 2024 Frederick Boniface +# +#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +# +#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +# +#THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from Pico_ePaper_2_13_v4 import EPD_2in13_V4_Landscape +from ezFBfont import ezFBfont +import ezFBfont_spleen_16x32_ascii_30 as spleen30Font +import ezFBfont_spleen_8x16_ascii_14 as spleen14Font + +import ntptime, network, utime +import config, open_meteo + +epd = EPD_2in13_V4_Landscape() + +WHT = 0xff +BLK = 0x00 + +spleen30wht = ezFBfont(epd, spleen30Font, fg=WHT, bg=BLK) +spleen30blk = ezFBfont(epd, spleen30Font, fg=BLK, bg=WHT) +spleen14wht = ezFBfont(epd, spleen14Font, fg=WHT, bg=BLK) +spleen14blk = ezFBfont(epd, spleen14Font, fg=BLK, bg=WHT) + +epd.fill(WHT) +epd.display(epd.buffer) + +## Functions +def update_time(): + try: + ntptime.host = "time.fjla.net" + ntptime.settime() + except Exception as e: + raise e + +# Load icon into buffer +def blit_to_framebuffer(framebuffer, fb_width, x, y, image_bytes, img_width, img_height): + for row in range(img_height): + for col in range(img_width): + byte_index = (row * (img_width // 8)) + (col // 8) + bit_index = 7 - (col % 8) + bit = (image_bytes[byte_index] >> bit_index) & 1 + + framebuffer.pixel(x + col, y + row, bit) + +# Load weather data into buffer +def update_weather(): + # Get and load weather data + weather = open_meteo.get_weather() + blit_to_framebuffer(epd, epd.width, 3, 5, weather['icon'], 72, 72) + spleen30wht.write(f"{str(weather['current_temp'])} C", 3, 82) + spleen14wht.write(f"Min: {str(weather['min'])} C", 82, 10) + spleen14wht.write(f"Max: {str(weather['max'])} C", 82, 25) + #epd.image1Gray.text("Apparent min: " + str(weather['apparent_min']) + "C", 75, 25, WHITE) + #epd.image1Gray.text("Apparent max: " + str(weather['apparent_max']) + "C", 75, 35, WHITE) + return + +def get_day(day_no): + days = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday") + return days[day_no] + +## Init +spleen30blk.write("PicoWidget-v0.1", 5, 5) +epd.text("Initialising", 5, 40, BLK) +epd.display(epd.buffer) +epd.displayPartial(epd.buffer) + +wlan = network.WLAN(network.STA_IF) +wlan.active(True) +wlan.connect(config.WIFI_SSID, config.WIFI_PASS) +max_attempts = 10 +attempt = 0 +while not wlan.isconnected() and attempt < max_attempts: + utime.sleep(1) + attempt += 1 + +if wlan.isconnected(): + epd.text(f"WiFi Connection ({config.WIFI_SSID}): OK", 5, 52, BLK) +else: + epd.text(f"WiFi Connection ({config.WiFi_SSID}): FAIL", 5, 52, BLK) + +try: + update_time() + epd.text(f"NTP Update: OK", 5, 64, BLK) +except Exception as e: + epd.text(f"NTP Update: FAIL", 5, 64, BLK) + print(e) + +epd.text(f"Location: {config.LATLONG}", 5, 76, BLK) +epd.text(f"Buffering display...", 5, 88, BLK) +epd.displayPartial(epd.buffer) + +tick = 0 +displayed_time = None +displayed_msg = None + +while True: + partial_display_needed = False + full_display_needed = False + + if tick % 360 == 0: + epd.fill(BLK) + update_weather() + full_display_needed = True + update_time() + + t = utime.localtime() + t_str = "{:02}:{:02}".format(t[3], t[4]) + d_str = "{:02}/{:02}/{:4}".format(t[2], t[1], t[0]) + if t_str != displayed_time or full_display_needed: + spleen30wht.write(t_str, 160, 60) + spleen14wht.write(get_day(t[6]), 160, 90) + spleen14wht.write(d_str, 160, 110) + displayed_time = t_str + partial_display_needed = True + + if not full_display_needed: + if partial_display_needed: + epd.displayPartial(epd.buffer) + + if full_display_needed: + epd.display(epd.buffer) + epd.displayPartial(epd.buffer) + + tick += 1 + if tick >= 7200: + tick = 0 + + utime.sleep(20) \ No newline at end of file diff --git a/open_meteo.py b/open_meteo.py new file mode 100644 index 0000000..c56befb --- /dev/null +++ b/open_meteo.py @@ -0,0 +1,63 @@ +#Copyright 2024 Frederick Boniface +# +#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +# +#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +# +#THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import urequests + +import config + +def get_weather(): + URL = f"https://api.open-meteo.com/v1/forecast?latitude={config.LATLONG[0]}&longitude={config.LATLONG[1]}¤t=temperature_2m&daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunset,wind_speed_10m_max,wind_gusts_10m_max&timezone=Europe%2FLondon&forecast_days=1" + res = urequests.get(URL) + return parse_weather(res.json()) + +def parse_weather(weather): + output = { + "current_temp": weather['current']['temperature_2m'], + "max": weather['daily']['temperature_2m_max'][0], + "min": weather['daily']['temperature_2m_min'][0], + "sunrise": weather['daily']['sunrise'][0], + "sunset": weather['daily']['sunset'][0], + "icon": get_weather_icon(weather['daily']['weather_code'][0]), + } + return output + +def get_weather_icon(code): + with open (weather_icon_paths[code], "rb") as file: + raw_bytes = file.read() + return raw_bytes + +weather_icon_paths = { + 0: "/ico/weather-sunny.bin", #Clear Sky + 1: "/ico/weather-sunny.bin", #Mainly Clear + 2: "/ico/weather-partly-cloudy.bin", #Partly Cloudy + 3: "/ico/weather-cloudy.bin", #Overcast + 45: "/ico/weather-fog.bin", #Fog + 48: "/ico/weather-fog.bin", #Depositing Rime Fog + 51: "/ico/weather-partly-rainy.bin", #Light Drizzle + 53: "/ico/weather-partly-rainy.bin", #Moderate Drizzle + 55: "/ico/weather-partly-rainy.bin", #Dense Drizzle + 56: "/ico/weather-snowy-rainy.bin", #Light Freezing Drizzle + 57: "/ico/weather-snowy-rainy.bin", #Dense Freezing Drizzle + 61: "/ico/weather-rainy.bin", #Light Rain + 63: "/ico/weather-rainy.bin", #Moderate Rain + 65: "/ico/weather-pouring.bin", #Heavy Rain + 66: "/ico/weather-hail.bin", #Light Freezing Rain + 67: "/ico/weather-hail.bin", #Heavy Freezing Rain + 71: "/ico/weather-snowy.bin", #Slight Snowfall + 73: "/ico/weather-snowy.bin", #Moderate Snowfall + 75: "/ico/weather-snowy-heavy.bin", #Heavy Snowfall + 77: "/ico/weather-snowy-heavy.bin", #Snow Grains + 80: "/ico/weather-partly-rainy.bin", #Slight Rain Showers + 81: "/ico/weather-partly-rainy.bin", #Moderate Rain Showers + 82: "/ico/weather-pouring.bin", #Violent Rain Showers + 85: "/ico/weather-partly-snowy.bin", #Slight Snow Showers + 86: "/ico/weather-partly-snowy.bin", #Heavy Snow Showers + 95: "/ico/weather-partly-lightning.bin", #Slight or Moderate Thunderstorm + 96: "/ico/weather-lightning.bin", #Thunderstorm with Slight Hail + 99: "/ico/weather-lightning-rainy.bin", #Thunderstorm with Heavy Hail +} \ No newline at end of file