Skip to content

Commit

Permalink
Merge pull request #103 from somakeit/70-add-2x16-lcd-display
Browse files Browse the repository at this point in the history
70 add 2x16 lcd display
  • Loading branch information
sam57719 committed May 19, 2024
2 parents dc4c755 + 58c6ea4 commit 831ec4e
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 1 deletion.
7 changes: 6 additions & 1 deletion smibhid/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@

## Space state
# Set the space state poll frequency in seconds (>= 5), set to 0 to disable the state poll
space_state_poll_frequency_s = 5
space_state_poll_frequency_s = 5

## I2C
SDA_PIN = 8
SCL_PIN = 9
I2C_ID = 0
128 changes: 128 additions & 0 deletions smibhid/lib/LCD1602.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
## Originally copied from https://files.waveshare.com/upload/d/db/LCD1602_I2C_Module_code.zip

# -*- coding: utf-8 -*-
from time import sleep
from machine import I2C
from ulogging import uLogger

#Device I2C address
LCD_ADDRESS = (0x7c>>1)

LCD_CLEARDISPLAY = 0x01
LCD_RETURNHOME = 0x02
LCD_ENTRYMODESET = 0x04
LCD_DISPLAYCONTROL = 0x08
LCD_CURSORSHIFT = 0x10
LCD_FUNCTIONSET = 0x20
LCD_SETCGRAMADDR = 0x40
LCD_SETDDRAMADDR = 0x80

#flags for display entry mode
LCD_ENTRYRIGHT = 0x00
LCD_ENTRYLEFT = 0x02
LCD_ENTRYSHIFTINCREMENT = 0x01
LCD_ENTRYSHIFTDECREMENT = 0x00

#flags for display on/off control
LCD_DISPLAYON = 0x04
LCD_DISPLAYOFF = 0x00
LCD_CURSORON = 0x02
LCD_CURSOROFF = 0x00
LCD_BLINKON = 0x01
LCD_BLINKOFF = 0x00

#flags for display/cursor shift
LCD_DISPLAYMOVE = 0x08
LCD_CURSORMOVE = 0x00
LCD_MOVERIGHT = 0x04
LCD_MOVELEFT = 0x00

#flags for function set
LCD_8BITMODE = 0x10
LCD_4BITMODE = 0x00
LCD_2LINE = 0x08
LCD_1LINE = 0x00
LCD_5x8DOTS = 0x00

class LCD1602:
"""Drive for the LCD1602 16x2 character LED display"""
def __init__(self, log_level: int, i2c_id: int, i2c_sda: int, i2c_scl: int, col: int, row: int) -> None:
"""Configure and connect to display via I2C, throw error on connection issue."""
self.log = uLogger("LCD1602", log_level)
self.log.info("Init LCD1602 display driver")
self._row = row
self._col = col

try:
self.LCD1602_I2C = I2C(i2c_id, sda = i2c_sda, scl = i2c_scl ,freq = 400000)
self._showfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS
self._begin(self._row)
except BaseException:
self.log.error("Error connecting to LCD display on I2C bus. Check I2C pins and ID and that correct module (I2C address) is connected.")
raise

def _command(self, cmd: int) -> None:
"""Execute a command against the display driver. Refer to command constants."""
self.LCD1602_I2C.writeto_mem(LCD_ADDRESS, 0x80, chr(cmd))

def _write(self, data: int) -> None:
self.LCD1602_I2C.writeto_mem(LCD_ADDRESS, 0x40, chr(data))

def setCursor(self, col: int, row: int) -> None:
"""Position the cursor ahead of writing a character or string."""
if(row == 0):
col|=0x80
else:
col|=0xc0
self.LCD1602_I2C.writeto(LCD_ADDRESS, bytearray([0x80, col]))

def clear(self) -> None:
"""Clear the entire screen."""
self._command(LCD_CLEARDISPLAY)
sleep(0.002)

def printout(self, arg: str) -> None:
"""Print a string to the cursor position."""
if(isinstance(arg, int)):
arg=str(arg)

for x in bytearray(arg, 'utf-8'):
self._write(x)

def _display(self) -> None:
"""Turn on display"""
self._showcontrol |= LCD_DISPLAYON
self._command(LCD_DISPLAYCONTROL | self._showcontrol)

def _begin(self, lines: int) -> None:
"""Configure and set initial display output"""
if (lines > 1):
self._showfunction |= LCD_2LINE

self._numlines = lines
self._currline = 0

sleep(0.05)

# Send function set command sequence
self._command(LCD_FUNCTIONSET | self._showfunction)
#delayMicroseconds(4500); # wait more than 4.1ms
sleep(0.005)
# second try
self._command(LCD_FUNCTIONSET | self._showfunction)
#delayMicroseconds(150);
sleep(0.005)
# third go
self._command(LCD_FUNCTIONSET | self._showfunction)
# finally, set # lines, font size, etc.
self._command(LCD_FUNCTIONSET | self._showfunction)
# turn the display on with no cursor or blinking default
self._showcontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF
self._display()
# clear it off
self.clear()
# Initialize to default text direction (for romance languages)
self._showmode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT
# set the entry mode
self._command(LCD_ENTRYMODESET | self._showmode)
# backlight init
55 changes: 55 additions & 0 deletions smibhid/lib/display.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from LCD1602 import LCD1602
from config import I2C_ID, SCL_PIN, SDA_PIN
from ulogging import uLogger

def check_enabled(method):
def wrapper(self, *args, **kwargs):
if self.enabled:
return method(self, *args, **kwargs)
return None
return wrapper

class Display:
"""
Display management for SMIBHID to drive displays via driver classes abstracting display constraints from the messaging required.
Provides functions to clear display and print top or bottom line text.
Currently supports 2x16 character LCD display.
"""
def __init__(self, log_level: int) -> None:
"""Connect to display using configu file values for I2C"""
self.log = uLogger("Display", log_level)
self.log.info("Init display")
self.enabled = True
try:
self.lcd = LCD1602(log_level, I2C_ID, SDA_PIN, SCL_PIN, 16, 2)
except Exception:
self.log.error("Error initialising display on I2C bus. Disabling display functionality.")
self.enabled = False

@check_enabled
def clear(self) -> None:
"""Clear entire screen"""
self.lcd.clear()

def _text_to_line(self, text: str) -> str:
"""Internal function to ensure line fits the screen and no previous line text is present for short strings."""
text = text[:16]
text = "{:<16}".format(text)
return text

@check_enabled
def print_top_line(self, text: str) -> None:
"""Print up to 16 characters on the top line."""
self.lcd.setCursor(0, 0)
self.lcd.printout(self._text_to_line(text))

@check_enabled
def print_bottom_line(self, text: str) -> None:
"""Print up to 16 characters on the bottom line."""
self.lcd.setCursor(0, 1)
self.lcd.printout(self._text_to_line(text))

@check_enabled
def print_space_state(self, state: str) -> None:
"""Abstraction for space state formatting and placement."""
self.print_bottom_line(f"Space: {state}")
10 changes: 10 additions & 0 deletions smibhid/lib/hid.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from slack_api import Wrapper
from lib.networking import WirelessNetwork
from constants import OPEN, CLOSED
from display import Display

class HID:

Expand All @@ -31,6 +32,7 @@ def __init__(self, loglevel: int) -> None:
self.space_state_check_in_error_state = False
self.checking_space_state = False
self.checking_space_state_timeout_s = 30
self.display = Display(loglevel)

self.space_state_poll_frequency = config.space_state_poll_frequency_s
if self.space_state_poll_frequency != 0 and self.space_state_poll_frequency < 5:
Expand All @@ -41,6 +43,9 @@ def startup(self) -> None:
Initialise all aysnc services for the HID.
"""
self.log.info("Starting HID")
self.display.clear()
self.display.print_top_line("S.M.I.B.H.I.D.")
self.display.print_bottom_line("Starting up...")
self.log.info(f"Starting {self.open_button.get_name()} button watcher")
create_task(self.open_button.wait_for_press())
self.log.info(f"Starting {self.closed_button.get_name()} button watcher")
Expand All @@ -67,20 +72,23 @@ def set_output_space_open(self) -> None:
self.space_state = True
self.space_open_led.on()
self.space_closed_led.off()
self.display.print_space_state("Open")
self.log.info("Space state is open.")

def set_output_space_closed(self) -> None:
"""Set LED's display etc to show the space as closed"""
self.space_state = False
self.space_open_led.off()
self.space_closed_led.on()
self.display.print_space_state("Closed")
self.log.info("Space state is closed.")

def set_output_space_none(self) -> None:
"""Set LED's display etc to show the space as none"""
self.space_state = None
self.space_open_led.off()
self.space_closed_led.off()
self.display.print_space_state("None")
self.log.info("Space state is none.")

def _set_space_state_check_to_error(self) -> None:
Expand All @@ -89,6 +97,7 @@ def _set_space_state_check_to_error(self) -> None:
self.space_state_check_in_error_state = True
self.state_check_error_open_led_flash_task = create_task(self.space_open_led.async_constant_flash(2))
self.state_check_error_closed_led_flash_task = create_task(self.space_closed_led.async_constant_flash(2))
self.display.print_space_state("Error")

def _set_space_state_check_to_ok(self) -> None:
"""Activities relating to space_state check moving to ok state"""
Expand All @@ -98,6 +107,7 @@ def _set_space_state_check_to_ok(self) -> None:
self.state_check_error_closed_led_flash_task.cancel()
self.space_open_led.off()
self.space_closed_led.off()
self._set_space_output(self.space_state)

def _free_to_check_space_state(self) -> bool:
"""Check that we're not already checking for space state"""
Expand Down

0 comments on commit 831ec4e

Please sign in to comment.