Skip to content
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

70 add 2x16 lcd display #103

Merged
merged 4 commits into from
May 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
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