Skip to content

LCD 16x2 simulator #79

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ You can buy one of these great little I2C LCD on eBay or somewhere like [the Pi
- [LCD demo](#lcd-demo)
- [NetMonitor](#netmonitor)
- [Progress bar](#progress-bar)
- [Simulator](#simulator)
- [Tiny Dashboard](#tiny-dashboard)
1. [Implementation](#implementation)
- [Systemd](#systemd)
Expand Down Expand Up @@ -226,6 +227,29 @@ This is a demo of a graphical progress bar created with [custom characters](#cus
<img src="imgs/demo_progress_bar.jpg" width="50%">
</p>

### Simulator

- Author: [@Jumitti](https://github.com/Jumitti)

Just a 16x2 LCD screen simulator using Tkinter, allowing for easy testing and visualization of LCD displays without physical hardware. This simulator helps in developing and debugging LCD-based projects directly from a computer.
Especially if you don't have your Raspberry and LCD with you.

All you have to do is replace ``Lcd()`` and ``CustomCharacters()`` with ``LcdSimulator()`` and ``CustomCharactersSimulator()``.
The other functions are similar.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

must doc any additional dependencies as well

```Python
import drivers

display = drivers.LcdSimulator()
cc = drivers.CustomCharactersSimulator(display)

# Then the rest of your finest feats
```

<p align="center">
<img src="imgs/demo_simulator.gif" width="50%">
</p>

### Tiny dashboard

- Author: [@jdarias](https://github.com/jdarias)
Expand Down
57 changes: 57 additions & 0 deletions demo_simulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Just a 16x2 LCD screen simulator using Tkinter, allowing for easy testing and
# visualization of LCD displays without physical hardware. This simulator helps
# in developing and debugging LCD-based projects directly from a computer.

# Import necessary libraries for communication and display use
import drivers
from time import sleep
from datetime import datetime


# Load the driver and set it to "display"
# If you use something from the driver library use the "display." prefix first
display = drivers.LcdSimulator()

# Create object with custom characters data
cc = drivers.CustomCharactersSimulator(display)

# Redefine the default characters:
# Custom caracter #1. Code {0x00}.
cc.char_1_data = ["01010", "11111", "10001", "10101", "10001", "11111", "01010", "00000"]

# Load custom characters data to CG RAM:
cc.load_custom_characters_data()

# Main body for code
try:
i = 0
while True:
display.lcd_clear()

display.lcd_display_string(' Hello, World !', line=1)
if i < 1:
display.lcd_backlight(1)
text = "This is a simulation of a 16x2 LCD"
for j in range(len(text) - 14):
text2 = "{0x00}" + text[j:j + 15]
display.lcd_display_extended_string(text2, 2)
sleep(0.15)
i += 1

elif 1 <= i <= 10:
display.lcd_backlight(0)
display.lcd_display_string(str(datetime.now().time()), 2)
i += 1

if i > 10:
i = 0

sleep(0.5)

except KeyboardInterrupt:
# If there is a KeyboardInterrupt (when you press ctrl+c), exit the program and cleanup
print("Cleaning up!")
display.lcd_clear()



6 changes: 5 additions & 1 deletion drivers/__init__.py
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this modification creates an additional dependency (namely, tkinter) for everyone trying to use the driver interface for the physical (i2c) device. this is not okay. the simulator is a good addition but its use should not interfere with the other use cases.

please undo any modifications made to this file.

Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
from .i2c_dev import Lcd, CustomCharacters
try:
from .i2c_dev import Lcd, CustomCharacters
except ModuleNotFoundError as e:
print(f"{e}, you can use LCDSimulator and CustomCharactersSimulator instead")
from .simulator import LcdSimulator, CustomCharactersSimulator
234 changes: 234 additions & 0 deletions drivers/simulator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import tkinter as tk
from re import match
from time import sleep

custom_chars = {} # Storage CustomCharacters


class LcdSimulator:
def __init__(self): # Initialise the Tkinter window
self.LCD_BACKGROUND = "green"
self.LCD_FOREGROUND = "black"
self.SESSION_STATE_BACKLIGHT = ''
self.columns = 16
self.rows = 2
self.char_width = 75
self.char_height = 120
self.rectangles = []

self.rects = []
self.texts = []

self.root = tk.Tk()
self.root.title("LCD 16x2 Simulator")

self.canvas = tk.Canvas(self.root, width=self.columns * self.char_width, height=self.rows * self.char_height,
bg=self.LCD_BACKGROUND)
self.canvas.pack()

self.chars = []
for row in range(self.rows):
for col in range(self.columns):
rect = self.canvas.create_rectangle(
col * self.char_width,
row * self.char_height,
(col + 1) * self.char_width,
(row + 1) * self.char_height,
outline=self.LCD_FOREGROUND,
width=5
)
text = self.canvas.create_text(
col * self.char_width + self.char_width // 2,
row * self.char_height + self.char_height // 2,
text="",
font=("Courier", 115),
fill=self.LCD_FOREGROUND
)
self.rects.append(rect)
self.texts.append(text)
self.chars.append(text)

self.custom_characters = CustomCharactersSimulator(self)

# put string function
def lcd_display_string(self, text, line=0):
line = line - 1
start_index = line * self.columns
for i, char in enumerate(text):
if start_index + i < len(self.chars):
self.canvas.itemconfig(self.chars[start_index + i], text=char)

self.lcd_update()

# put extended string function. Extended string may contain placeholder like {0xFF} for
# displaying the particular symbol from the symbol table
def lcd_display_extended_string(self, text, line=0):
line = line - 1
i = 0
x_offset = 0
while i < len(text):
match_result = match(r'\{0[xX][0-9a-fA-F]{2}}', text[i:])
if match_result:
char_code = match_result.group(0)
custom_char_bitmap = self.custom_characters.get_custom_char(char_code)
self.custom_characters.draw_custom_char(custom_char_bitmap,
x_offset * self.char_width,
line * self.char_height, self.LCD_FOREGROUND)
x_offset += 1
i += 6
else:
self.canvas.itemconfig(self.chars[line * self.columns + x_offset], text=text[i])
x_offset += 1
i += 1
self.lcd_update()

# clear lcd
def lcd_clear(self):
self.root.update_idletasks()
self.root.update()
for i in range(2):
self.lcd_display_string(" ", i)
for rect_id in self.rectangles:
self.canvas.delete(rect_id)
self.rectangles.clear()

def lcd_update(self):
self.root.update_idletasks()
self.root.update()

# backlight control (on/off)
# options: lcd_backlight(1) = ON, lcd_backlight(0) = OFF
def lcd_backlight(self, state):
if state == 1:
self.LCD_BACKGROUND = "green"
self.LCD_FOREGROUND = "black"
elif state == 0:
self.LCD_BACKGROUND = "black"
self.LCD_FOREGROUND = "green"

self.canvas.configure(bg=self.LCD_BACKGROUND)

for rect in self.rects:
self.canvas.itemconfig(rect, outline=self.LCD_FOREGROUND)
for text in self.texts:
self.canvas.itemconfig(text, fill=self.LCD_FOREGROUND)

self.SESSION_STATE_BACKLIGHT = state


class CustomCharactersSimulator:
def __init__(self, lcd):
self.lcd = lcd
# Data for custom character #1. Code {0x00}
self.char_1_data = ["11111",
"10001",
"10001",
"10001",
"10001",
"10001",
"10001",
"11111"]
# Data for custom character #2. Code {0x01}
self.char_2_data = ["11111",
"10001",
"10001",
"10001",
"10001",
"10001",
"10001",
"11111"]
# Data for custom character #3. Code {0x02}
self.char_3_data = ["11111",
"10001",
"10001",
"10001",
"10001",
"10001",
"10001",
"11111"]
# Data for custom character #4. Code {0x03}
self.char_4_data = ["11111",
"10001",
"10001",
"10001",
"10001",
"10001",
"10001",
"11111"]
# Data for custom character #5. Code {0x04}
self.char_5_data = ["11111",
"10001",
"10001",
"10001",
"10001",
"10001",
"10001",
"11111"]
# Data for custom character #6. Code {0x05}
self.char_6_data = ["11111",
"10001",
"10001",
"10001",
"10001",
"10001",
"10001",
"11111"]
# Data for custom character #7. Code {0x06}
self.char_7_data = ["11111",
"10001",
"10001",
"10001",
"10001",
"10001",
"10001",
"11111"]
# Data for custom character #8. Code {0x07}
self.char_8_data = ["11111",
"10001",
"10001",
"10001",
"10001",
"10001",
"10001",
"11111"]

# load custom character data to CG RAM for later use in extended string. Data for
# characters is hold in file custom_characters.txt in the same folder as i2c_dev.py
# file. These custom characters can be used in printing of extended string with a
# placeholder with desired character codes: 1st - {0x00}, 2nd - {0x01}, 3rd - {0x02},
# 4th - {0x03}, 5th - {0x04}, 6th - {0x05}, 7th - {0x06} and 8th - {0x07}.
def load_custom_characters_data(self):
char_data_list = [
(f"{{0x00}}", self.char_1_data),
(f"{{0x01}}", self.char_2_data),
(f"{{0x02}}", self.char_3_data),
(f"{{0x03}}", self.char_4_data),
(f"{{0x04}}", self.char_5_data),
(f"{{0x05}}", self.char_6_data),
(f"{{0x06}}", self.char_7_data),
(f"{{0x07}}", self.char_8_data)
]

for char_name, bitmap in char_data_list:
if len(bitmap) != 8 or any(len(row) != 5 for row in bitmap):
continue
custom_chars[char_name] = bitmap

def get_custom_char(self, char_name):
return custom_chars.get(char_name, ["00000"] * 8)

# Draw CustomCharacters
def draw_custom_char(self, bitmap, x, y, color):
pixel_size = 15
for row, line in enumerate(bitmap):
for col, bit in enumerate(line):
if bit == '1':
rect_id = self.lcd.canvas.create_rectangle(
x + (col * pixel_size),
y + (row * pixel_size),
x + ((col + 1) * pixel_size),
y + ((row + 1) * pixel_size),
fill=color,
outline=color
)
self.lcd.rectangles.append(rect_id)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i couldn't help but notice that this implementation is re-using logic from the original CustomCharacters class. can't you inherit the original and override where necessary?

Binary file added imgs/demo_simulator.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.