diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index db94e45..10b414e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ name: CI jobs: test: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: deps: @@ -32,3 +32,17 @@ jobs: run: pip install -r requirements-dev.txt - name: Run tests run: pytest + + format: + runs-on: ubuntu-24.04 + name: Check code formatting with black + steps: + - uses: actions/checkout@v3 + - name: Setup python + uses: actions/setup-python@v4 + with: + python-version: 3.12 + - name: Install black + run: pip install black~=25.1 + - name: Check code formatting + run: black --check . diff --git a/README.rst b/README.rst index 13d7e1a..af9b92e 100644 --- a/README.rst +++ b/README.rst @@ -130,6 +130,10 @@ Coding Guidelines flake8 --max-line-length=99 --ignore=E126,E127,E128,C901 RPLCD/lcd.py +Additionally, please reformat your code using `black `_:: + + black . + About HD44780 ============= diff --git a/RPLCD/__init__.py b/RPLCD/__init__.py index c79337b..cd57f62 100644 --- a/RPLCD/__init__.py +++ b/RPLCD/__init__.py @@ -9,6 +9,9 @@ class CharLCD: def __new__(cls, *args, **kwargs): from .gpio import CharLCD as GpioCharLCD - warnings.warn("Using RPLCD.CharLCD directly is deprecated. " + - "Use RPLCD.gpio.CharLCD instead!", DeprecationWarning) + + warnings.warn( + "Using RPLCD.CharLCD directly is deprecated. Use RPLCD.gpio.CharLCD instead!", + DeprecationWarning, + ) return GpioCharLCD(*args, **kwargs) diff --git a/RPLCD/codecs/__init__.py b/RPLCD/codecs/__init__.py index aa71143..d328e70 100644 --- a/RPLCD/codecs/__init__.py +++ b/RPLCD/codecs/__init__.py @@ -12,6 +12,7 @@ class FoundMultiCharMapping(Exception): """ Exception to escape nested loops. """ + pass @@ -25,8 +26,7 @@ def __init__(self, codec): def encode(self, input_): # type: (str) -> List[int] result = [] - window_iter = sliding_window( - input_, self.codec.combined_chars_lookahead) + window_iter = sliding_window(input_, self.codec.combined_chars_lookahead) while True: try: window = next(window_iter) @@ -60,8 +60,7 @@ def encode(self, input_): # type: (str) -> List[int] continue # Otherwise, do a regular lookup in the encoding table - result.append(self.codec.encoding_table.get( - char, self.codec.replacement_char)) + result.append(self.codec.encoding_table.get(char, self.codec.replacement_char)) return result diff --git a/RPLCD/common.py b/RPLCD/common.py index f11d002..cfb24f8 100644 --- a/RPLCD/common.py +++ b/RPLCD/common.py @@ -19,6 +19,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + import itertools import time @@ -72,7 +73,8 @@ RS_DATA = 0x01 -# # # Helper classes # # # +# # # HELPER CLASSES # # # + class Alignment(object): left = LCD_ENTRYLEFT @@ -92,6 +94,7 @@ class CursorMode(object): # # # HELPER FUNCTIONS # # # + def msleep(milliseconds): """Sleep the specified amount of milliseconds.""" time.sleep(milliseconds / 1000.0) diff --git a/RPLCD/gpio.py b/RPLCD/gpio.py index fba9c3b..4e12a19 100644 --- a/RPLCD/gpio.py +++ b/RPLCD/gpio.py @@ -19,6 +19,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + from collections import namedtuple import RPi.GPIO as GPIO @@ -35,13 +36,23 @@ class CharLCD(BaseCharLCD): - def __init__(self, numbering_mode=None, pin_rs=None, pin_rw=None, pin_e=None, pins_data=None, - pin_backlight=None, backlight_mode='active_low', - backlight_enabled=True, - cols=20, rows=4, dotsize=8, - charmap='A02', - auto_linebreaks=True, - compat_mode=False): + def __init__( + self, + numbering_mode=None, + pin_rs=None, + pin_rw=None, + pin_e=None, + pins_data=None, + pin_backlight=None, + backlight_mode='active_low', + backlight_enabled=True, + cols=20, + rows=4, + dotsize=8, + charmap='A02', + auto_linebreaks=True, + compat_mode=False, + ): """ Character LCD controller. @@ -100,10 +111,12 @@ def __init__(self, numbering_mode=None, pin_rs=None, pin_rw=None, pin_e=None, pi if numbering_mode == GPIO.BCM or numbering_mode == GPIO.BOARD: self.numbering_mode = numbering_mode else: - raise ValueError('Invalid GPIO numbering mode: numbering_mode=%s, ' - 'must be either GPIO.BOARD or GPIO.BCM.\n' - 'See https://gist.github.com/dbrgn/77d984a822bfc9fddc844f67016d0f7e ' - 'for more details.' % numbering_mode) + raise ValueError( + 'Invalid GPIO numbering mode: numbering_mode=%s, ' + 'must be either GPIO.BOARD or GPIO.BCM.\n' + 'See https://gist.github.com/dbrgn/77d984a822bfc9fddc844f67016d0f7e ' + 'for more details.' % numbering_mode + ) if pin_rs is None: raise ValueError('pin_rs is not defined.') if pin_e is None: @@ -118,17 +131,27 @@ def __init__(self, numbering_mode=None, pin_rs=None, pin_rw=None, pin_e=None, pi else: raise ValueError('There should be exactly 4 or 8 data pins.') block2 = pins_data[-4:] - self.pins = PinConfig(rs=pin_rs, rw=pin_rw, e=pin_e, - d0=block1[0], d1=block1[1], d2=block1[2], d3=block1[3], - d4=block2[0], d5=block2[1], d6=block2[2], d7=block2[3], - backlight=pin_backlight, - mode=numbering_mode) + self.pins = PinConfig( + rs=pin_rs, + rw=pin_rw, + e=pin_e, + d0=block1[0], + d1=block1[1], + d2=block1[2], + d3=block1[3], + d4=block2[0], + d5=block2[1], + d6=block2[2], + d7=block2[3], + backlight=pin_backlight, + mode=numbering_mode, + ) self.backlight_mode = backlight_mode # Call superclass - super(CharLCD, self).__init__(cols, rows, dotsize, - charmap=charmap, - auto_linebreaks=auto_linebreaks) + super(CharLCD, self).__init__( + cols, rows, dotsize, charmap=charmap, auto_linebreaks=auto_linebreaks + ) # Set backlight status if pin_backlight is not None: @@ -150,9 +173,19 @@ def _init_connection(self): GPIO.output(self.pins.rw, 0) def _close_connection(self): - pins = (self.pins.rs, self.pins.rw, self.pins.e, self.pins.d0, self.pins.d1, - self.pins.d2, self.pins.d3, self.pins.d4, self.pins.d5, self.pins.d6, - self.pins.d7) + pins = ( + self.pins.rs, + self.pins.rw, + self.pins.e, + self.pins.d0, + self.pins.d1, + self.pins.d2, + self.pins.d3, + self.pins.d4, + self.pins.d5, + self.pins.d6, + self.pins.d7, + ) active_pins = [pin for pin in pins if pin is not None] GPIO.cleanup(active_pins) @@ -171,11 +204,13 @@ def _set_backlight_enabled(self, value): if not isinstance(value, bool): raise ValueError('backlight_enabled must be set to ``True`` or ``False``.') self._backlight_enabled = value - GPIO.output(self.pins.backlight, - value ^ (self.backlight_mode == 'active_low')) + GPIO.output(self.pins.backlight, value ^ (self.backlight_mode == 'active_low')) - backlight_enabled = property(_get_backlight_enabled, _set_backlight_enabled, - doc='Whether or not to turn on the backlight.') + backlight_enabled = property( + _get_backlight_enabled, + _set_backlight_enabled, + doc='Whether or not to turn on the backlight.', + ) # Low level commands @@ -205,11 +240,11 @@ def _send(self, value, mode): self.last_send_event = now() def _send_data(self, value): - """Send data to the display. """ + """Send data to the display.""" self._send(value, c.RS_DATA) def _send_instruction(self, value): - """Send instruction to the display. """ + """Send instruction to the display.""" self._send(value, c.RS_INSTRUCTION) def _write4bits(self, value): diff --git a/RPLCD/i2c.py b/RPLCD/i2c.py index 0c4a5bf..3f369ef 100644 --- a/RPLCD/i2c.py +++ b/RPLCD/i2c.py @@ -19,6 +19,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + try: from smbus import SMBus except ImportError: @@ -38,7 +39,7 @@ # MCP230XX backlight control MCP230XX_BACKLIGHT = 0x80 -MCP230XX_NOBACKLIGHT = 0x7f +MCP230XX_NOBACKLIGHT = 0x7F # MCP230XX pin bitmasks and datamask MCP230XX_RS = 0x02 @@ -58,11 +59,19 @@ class CharLCD(BaseCharLCD): - def __init__(self, i2c_expander, address, expander_params=None, port=1, - cols=20, rows=4, dotsize=8, - charmap='A02', - auto_linebreaks=True, - backlight_enabled=True): + def __init__( + self, + i2c_expander, + address, + expander_params=None, + port=1, + cols=20, + rows=4, + dotsize=8, + charmap='A02', + auto_linebreaks=True, + backlight_enabled=True, + ): """ CharLCD via PCF8574 I2C port expander: @@ -93,7 +102,7 @@ def __init__(self, i2c_expander, address, expander_params=None, port=1, Pin mapping:: 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 - -- | -- | -- | -- | -- | - | -- | - + -- | -- | -- | -- | -- | - | -- | - BL | D7 | D6 | D5 | D4 | E | RS | - @@ -138,8 +147,10 @@ def __init__(self, i2c_expander, address, expander_params=None, port=1, # Errorchecking for expander parameters if expander_params is None: if self._i2c_expander == 'MCP23017': - raise ValueError('MCP23017: expander_params[\'gpio_bank\'] is not defined, ' - 'must be either \'A\' or \'B\'') + raise ValueError( + 'MCP23017: expander_params[\'gpio_bank\'] is not defined, ' + 'must be either \'A\' or \'B\'' + ) else: self._expander_params = {} else: @@ -148,8 +159,10 @@ def __init__(self, i2c_expander, address, expander_params=None, port=1, self._expander_params = {} self._expander_params['gpio_bank'] = expander_params['gpio_bank'] else: - raise ValueError('MCP23017: expander_params[\'gpio_bank\'] is \'%s\', ' - 'must be either \'A\' or \'B\'' % expander_params['gpio_bank']) + raise ValueError( + 'MCP23017: expander_params[\'gpio_bank\'] is \'%s\', ' + 'must be either \'A\' or \'B\'' % expander_params['gpio_bank'] + ) # Currently the I2C mode only supports 4 bit communication self.data_bus_mode = c.LCD_4BITMODE @@ -161,9 +174,9 @@ def __init__(self, i2c_expander, address, expander_params=None, port=1, self._backlight = MCP230XX_BACKLIGHT if backlight_enabled else MCP230XX_NOBACKLIGHT # Call superclass - super(CharLCD, self).__init__(cols, rows, dotsize, - charmap=charmap, - auto_linebreaks=auto_linebreaks) + super(CharLCD, self).__init__( + cols, rows, dotsize, charmap=charmap, auto_linebreaks=auto_linebreaks + ) # Refresh backlight status self.backlight_enabled = backlight_enabled @@ -216,8 +229,11 @@ def _set_backlight_enabled(self, value): self._mcp_data &= MCP230XX_NOBACKLIGHT self.bus.write_byte_data(self._address, self._mcp_gpio, self._mcp_data) - backlight_enabled = property(_get_backlight_enabled, _set_backlight_enabled, - doc='Whether or not to enable the backlight. Either ``True`` or ``False``.') + backlight_enabled = property( + _get_backlight_enabled, + _set_backlight_enabled, + doc='Whether or not to enable the backlight. Either ``True`` or ``False``.', + ) # Low level commands @@ -225,8 +241,9 @@ def _send_data(self, value): if self._i2c_expander == 'PCF8574': self.bus.write_byte(self._address, (c.RS_DATA | (value & 0xF0)) | self._backlight) self._pulse_data(c.RS_DATA | (value & 0xF0)) - self.bus.write_byte(self._address, (c.RS_DATA | - ((value << 4) & 0xF0)) | self._backlight) + self.bus.write_byte( + self._address, (c.RS_DATA | ((value << 4) & 0xF0)) | self._backlight + ) self._pulse_data(c.RS_DATA | ((value << 4) & 0xF0)) elif self._i2c_expander in ['MCP23008', 'MCP23017']: self._mcp_data |= MCP230XX_RS @@ -235,11 +252,13 @@ def _send_data(self, value): def _send_instruction(self, value): if self._i2c_expander == 'PCF8574': - self.bus.write_byte(self._address, (c.RS_INSTRUCTION | - (value & 0xF0)) | self._backlight) + self.bus.write_byte( + self._address, (c.RS_INSTRUCTION | (value & 0xF0)) | self._backlight + ) self._pulse_data(c.RS_INSTRUCTION | (value & 0xF0)) - self.bus.write_byte(self._address, (c.RS_INSTRUCTION | - ((value << 4) & 0xF0)) | self._backlight) + self.bus.write_byte( + self._address, (c.RS_INSTRUCTION | ((value << 4) & 0xF0)) | self._backlight + ) self._pulse_data(c.RS_INSTRUCTION | ((value << 4) & 0xF0)) elif self._i2c_expander in ['MCP23008', 'MCP23017']: self._mcp_data &= ~MCP230XX_RS diff --git a/RPLCD/lcd.py b/RPLCD/lcd.py index 83f8b95..a58ed2d 100644 --- a/RPLCD/lcd.py +++ b/RPLCD/lcd.py @@ -19,6 +19,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + from collections import namedtuple from . import codecs @@ -30,6 +31,7 @@ # # # MAIN # # # + class BaseCharLCD(object): # Init, setup, teardown @@ -67,7 +69,8 @@ def __init__(self, cols=20, rows=4, dotsize=8, charmap='A02', auto_linebreaks=Tr pass else: raise ValueError( - 'The ``charmap`` argument must be either ``A00`` or ``A02`` or ``ST0B``') + 'The ``charmap`` argument must be either ``A00`` or ``A02`` or ``ST0B``' + ) # LCD configuration self.lcd = LCDConfig(rows=rows, cols=cols, dotsize=dotsize) @@ -146,8 +149,9 @@ def _get_cursor_pos(self): def _set_cursor_pos(self, value): if not hasattr(value, '__getitem__') or len(value) != 2: raise ValueError('Cursor position should be determined by a 2-tuple.') - if self.auto_linebreaks and \ - (value[0] not in range(self.lcd.rows) or value[1] not in range(self.lcd.cols)): + if self.auto_linebreaks and ( + value[0] not in range(self.lcd.rows) or value[1] not in range(self.lcd.cols) + ): msg = 'Cursor position {pos!r} invalid on a {lcd.rows}x{lcd.cols} LCD.' raise ValueError(msg.format(pos=value, lcd=self.lcd)) row_offsets = [0x00, 0x40, self.lcd.cols, 0x40 + self.lcd.cols] @@ -155,8 +159,9 @@ def _set_cursor_pos(self, value): self.command(c.LCD_SETDDRAMADDR | row_offsets[value[0]] + value[1]) c.usleep(50) - cursor_pos = property(_get_cursor_pos, _set_cursor_pos, - doc='The cursor position as a 2-tuple (row, col).') + cursor_pos = property( + _get_cursor_pos, _set_cursor_pos, doc='The cursor position as a 2-tuple (row, col).' + ) def _get_text_align_mode(self): if self._text_align_mode == c.Alignment.left: @@ -176,8 +181,11 @@ def _set_text_align_mode(self, value): self.command(c.LCD_ENTRYMODESET | self._text_align_mode | self._display_shift_mode) c.usleep(50) - text_align_mode = property(_get_text_align_mode, _set_text_align_mode, - doc='The text alignment (``left`` or ``right``).') + text_align_mode = property( + _get_text_align_mode, + _set_text_align_mode, + doc='The text alignment (``left`` or ``right``).', + ) def _get_write_shift_mode(self): if self._display_shift_mode == c.ShiftMode.cursor: @@ -197,8 +205,11 @@ def _set_write_shift_mode(self, value): self.command(c.LCD_ENTRYMODESET | self._text_align_mode | self._display_shift_mode) c.usleep(50) - write_shift_mode = property(_get_write_shift_mode, _set_write_shift_mode, - doc='The shift mode when writing (``cursor`` or ``display``).') + write_shift_mode = property( + _get_write_shift_mode, + _set_write_shift_mode, + doc='The shift mode when writing (``cursor`` or ``display``).', + ) def _get_display_enabled(self): return self._display_mode == c.LCD_DISPLAYON @@ -208,8 +219,9 @@ def _set_display_enabled(self, value): self.command(c.LCD_DISPLAYCONTROL | self._display_mode | self._cursor_mode) c.usleep(50) - display_enabled = property(_get_display_enabled, _set_display_enabled, - doc='Whether or not to display any characters.') + display_enabled = property( + _get_display_enabled, _set_display_enabled, doc='Whether or not to display any characters.' + ) def _get_cursor_mode(self): if self._cursor_mode == c.CursorMode.hide: @@ -233,8 +245,11 @@ def _set_cursor_mode(self, value): self.command(c.LCD_DISPLAYCONTROL | self._display_mode | self._cursor_mode) c.usleep(50) - cursor_mode = property(_get_cursor_mode, _set_cursor_mode, - doc='How the cursor should behave (``hide``, ``line`` or ``blink``).') + cursor_mode = property( + _get_cursor_mode, + _set_cursor_mode, + doc='How the cursor should behave (``hide``, ``line`` or ``blink``).', + ) # High level commands @@ -284,8 +299,8 @@ def write_string(self, value): # linebreak happened recently, and the lookahead matches too, # ignore this write. if self.recent_auto_linebreak is True: - crlf = (char == codecs.CR and lookahead == codecs.LF) - lfcr = (char == codecs.LF and lookahead == codecs.CR) + crlf = char == codecs.CR and lookahead == codecs.LF + lfcr = char == codecs.LF and lookahead == codecs.CR if crlf or lfcr: ignored = True continue diff --git a/RPLCD/pigpio.py b/RPLCD/pigpio.py index 0127ace..0815c7a 100644 --- a/RPLCD/pigpio.py +++ b/RPLCD/pigpio.py @@ -20,6 +20,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + from collections import namedtuple import pigpio @@ -39,16 +40,28 @@ class CharLCD(BaseCharLCD): - def __init__(self, pi, - pin_rs=None, pin_rw=None, pin_e=None, pin_e2=None, - pins_data=None, - pin_backlight=None, backlight_mode='active_low', - backlight_pwm=False, backlight_enabled=True, - pin_contrast=None, contrast_mode='active_high', - contrast_pwm=None, contrast=0.5, - cols=20, rows=4, dotsize=8, - charmap='A02', - auto_linebreaks=True): + def __init__( + self, + pi, + pin_rs=None, + pin_rw=None, + pin_e=None, + pin_e2=None, + pins_data=None, + pin_backlight=None, + backlight_mode='active_low', + backlight_pwm=False, + backlight_enabled=True, + pin_contrast=None, + contrast_mode='active_high', + contrast_pwm=None, + contrast=0.5, + cols=20, + rows=4, + dotsize=8, + charmap='A02', + auto_linebreaks=True, + ): """ Character LCD controller. @@ -135,19 +148,31 @@ def __init__(self, pi, else: raise ValueError('There should be exactly 4 or 8 data pins.') block2 = pins_data[-4:] - self.pins = PinConfig(rs=pin_rs, rw=pin_rw, e=pin_e, e2=pin_e2, - d0=block1[0], d1=block1[1], d2=block1[2], d3=block1[3], - d4=block2[0], d5=block2[1], d6=block2[2], d7=block2[3], - backlight=pin_backlight, contrast=pin_contrast) + self.pins = PinConfig( + rs=pin_rs, + rw=pin_rw, + e=pin_e, + e2=pin_e2, + d0=block1[0], + d1=block1[1], + d2=block1[2], + d3=block1[3], + d4=block2[0], + d5=block2[1], + d6=block2[2], + d7=block2[3], + backlight=pin_backlight, + contrast=pin_contrast, + ) self.backlight_mode = backlight_mode self.backlight_pwm = backlight_pwm self.contrast_mode = contrast_mode self.contrast_pwm = contrast_pwm # Call superclass - super(CharLCD, self).__init__(cols, rows, dotsize, - charmap=charmap, - auto_linebreaks=auto_linebreaks) + super(CharLCD, self).__init__( + cols, rows, dotsize, charmap=charmap, auto_linebreaks=auto_linebreaks + ) # Set backlight status if pin_backlight is not None: @@ -181,41 +206,51 @@ def _init_connection(self): # pigpio script to pulse the enable flag to process data enablepulse = [ - 'write {pin.e} 0', - 'mics 1', - 'trig {pin.e} 1 1', - 'mics 100'] # Commands need > 37us to settle + 'write {pin.e} 0', + 'mics 1', + 'trig {pin.e} 1 1', + 'mics 100', # Commands need > 37us to settle + ] # pigpio script to write data to the LCD - piscript = ['write {pin.rs} p0'] # Choose instruction or data mode + piscript = ['write {pin.rs} p0'] # Choose instruction or data mode if self.pins.rw is not None: # If the RW pin is used, set it to low piscript.append('write {pin.rw} 0') if self.data_bus_mode == c.LCD_8BITMODE: # Script to write 8 bits of data into the data bus. - piscript.extend( # Write data in 1 chunk of 8 bits - ['write {pin.d0} p1', # Write 8 bits of data into the data bus - 'write {pin.d1} p2', - 'write {pin.d2} p3', - 'write {pin.d3} p4', - 'write {pin.d4} p5', - 'write {pin.d5} p6', - 'write {pin.d6} p7', - 'write {pin.d7} p8']) - piscript.extend(enablepulse) # Process data + piscript.extend( # Write data in 1 chunk of 8 bits + [ + 'write {pin.d0} p1', # Write 8 bits of data into the data bus + 'write {pin.d1} p2', + 'write {pin.d2} p3', + 'write {pin.d3} p4', + 'write {pin.d4} p5', + 'write {pin.d5} p6', + 'write {pin.d6} p7', + 'write {pin.d7} p8', + ] + ) + piscript.extend(enablepulse) # Process data else: - piscript.extend( # Write data in 2 chunks of 4 bits - ['write {pin.d4} p5', # Write 4 bits of data into the data bus - 'write {pin.d5} p6', - 'write {pin.d6} p7', - 'write {pin.d7} p8']) - piscript.extend(enablepulse) # Process data + piscript.extend( # Write data in 2 chunks of 4 bits + [ + 'write {pin.d4} p5', # Write 4 bits of data into the data bus + 'write {pin.d5} p6', + 'write {pin.d6} p7', + 'write {pin.d7} p8', + ] + ) + piscript.extend(enablepulse) # Process data piscript.extend( - ['write {pin.d4} p1', # Write 4 bits of data into the data bus - 'write {pin.d5} p2', - 'write {pin.d6} p3', - 'write {pin.d7} p4']) - piscript.extend(enablepulse) # Process data + [ + 'write {pin.d4} p1', # Write 4 bits of data into the data bus + 'write {pin.d5} p2', + 'write {pin.d6} p3', + 'write {pin.d7} p4', + ] + ) + piscript.extend(enablepulse) # Process data # Make one string and insert the pin values piscript = ' '.join(piscript).format(pin=self.pins) @@ -246,28 +281,32 @@ def _set_backlight_enabled(self, value): if self.backlight_pwm: if not ((0 <= value <= 1) or isinstance(value, bool)): raise ValueError( - 'backlight_enabled must be set to a value ' - 'between 0 and 1 or to ``True`` or ``False``, ' - 'if PWM is enabled; got {}'.format(value)) + 'backlight_enabled must be set to a value ' + 'between 0 and 1 or to ``True`` or ``False``, ' + 'if PWM is enabled; got {}'.format(value) + ) else: if not isinstance(value, bool): raise ValueError( - 'backlight_enabled must be set to ``True`` or ``False``, ' - 'if PWM is not enabled; got: {}'.format(value)) + 'backlight_enabled must be set to ``True`` or ``False``, ' + 'if PWM is not enabled; got: {}'.format(value) + ) self._backlight_enabled = value if self.backlight_pwm: # Convert perceived brightness (as requested by `value`) to duty # cycle (see comment above definition of PWM): - dc = 2**(value / PWM) - 1 + dc = 2 ** (value / PWM) - 1 if self.backlight_mode == 'active_low': dc = 255 - dc self.pi.set_PWM_dutycycle(self.pins.backlight, round(dc)) else: - self.pi.write(self.pins.backlight, - value ^ (self.backlight_mode == 'active_low')) + self.pi.write(self.pins.backlight, value ^ (self.backlight_mode == 'active_low')) - backlight_enabled = property(_get_backlight_enabled, _set_backlight_enabled, - doc='Turn on/off or set the brightness of the backlight.') + backlight_enabled = property( + _get_backlight_enabled, + _set_backlight_enabled, + doc='Turn on/off or set the brightness of the backlight.', + ) def _get_contrast(self): # We could probably read the current GPIO output state via sysfs, but @@ -287,8 +326,7 @@ def _set_contrast(self, value): dc = 255 - dc self.pi.set_PWM_dutycycle(self.pins.contrast, round(dc)) - contrast = property(_get_contrast, _set_contrast, - doc='Set the LCD contrast.') + contrast = property(_get_contrast, _set_contrast, doc='Set the LCD contrast.') # Low level commands @@ -313,9 +351,9 @@ def _send(self, value, mode): pigpio.exceptions = True def _send_data(self, value): - """Send data to the display. """ + """Send data to the display.""" self._send(value, c.RS_DATA) def _send_instruction(self, value): - """Send instruction to the display. """ + """Send instruction to the display.""" self._send(value, c.RS_INSTRUCTION) diff --git a/RPLCD_Tests/entrypoint.py b/RPLCD_Tests/entrypoint.py index de0095b..52ee1e3 100755 --- a/RPLCD_Tests/entrypoint.py +++ b/RPLCD_Tests/entrypoint.py @@ -19,6 +19,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ + import sys # Import supported tests @@ -40,7 +41,7 @@ def print_usage(error=None): print(' testsuite - Tests display formatting, 20x4 and 16x2 displays supported.') print('') # Options for i2c mode - if ((len(sys.argv) > 1) and (sys.argv[1] == 'i2c')): + if (len(sys.argv) > 1) and (sys.argv[1] == 'i2c'): print(' i2c options:') print('') print(' expander - Supported I²C port expanders are PCF8574, MCP23008 and MCP23017') @@ -60,13 +61,17 @@ def print_usage(error=None): print('') print('Examples:') print('') - print(sys.argv[0] + ' i2c testsuite expander=PCF8574 addr=0x27 port=1 ' - 'cols=20 rows=4 charmap=A00') - print(sys.argv[0] + ' i2c testsuite expander=MCP23017 addr=0x20 port=1 ' - 'kols=20 rows=4 charmap=A00 gpio_bank=A') + print( + sys.argv[0] + ' i2c testsuite expander=PCF8574 addr=0x27 port=1 ' + 'cols=20 rows=4 charmap=A00' + ) + print( + sys.argv[0] + ' i2c testsuite expander=MCP23017 addr=0x20 port=1 ' + 'kols=20 rows=4 charmap=A00 gpio_bank=A' + ) # Options for GPIO mode - elif ((len(sys.argv) > 1) and (sys.argv[1] == 'gpio')): + elif (len(sys.argv) > 1) and (sys.argv[1] == 'gpio'): print(' gpio options:') print('') @@ -87,10 +92,12 @@ def print_usage(error=None): print('') print('Example:') print('') - print(sys.argv[0] + ' gpio testsuite cols=20 rows=4 mode=BCM rs=15 rw=None e=16 ' - 'bl=None data=21,22,23,24 charmap=A00') + print( + sys.argv[0] + ' gpio testsuite cols=20 rows=4 mode=BCM rs=15 rw=None e=16 ' + 'bl=None data=21,22,23,24 charmap=A00' + ) # Options for PIGPIO mode - elif ((len(sys.argv) > 1) and (sys.argv[1] == 'pigpio')): + elif (len(sys.argv) > 1) and (sys.argv[1] == 'pigpio'): print(' pigpio options:') print('') @@ -118,8 +125,10 @@ def print_usage(error=None): print('') print('Example:') print('') - print(sys.argv[0] + ' pigpio testsuite cols=20 rows=4 rs=15 rw=None e=16 ' - 'bl=None data=21,22,23,24 charmap=A00') + print( + sys.argv[0] + ' pigpio testsuite cols=20 rows=4 rs=15 rw=None e=16 ' + 'bl=None data=21,22,23,24 charmap=A00' + ) else: print(' For info about options run:') print('') @@ -133,10 +142,10 @@ def print_usage(error=None): def options_pop(value, default=no_default): - ''' Pops value from options with error checking - value: which option to pop and check. - default: optional, sets a default if not defined. - returns: a string corresponding to the option on the command line + '''Pops value from options with error checking + value: which option to pop and check. + default: optional, sets a default if not defined. + returns: a string corresponding to the option on the command line ''' global options try: @@ -186,11 +195,20 @@ def run(): address = int(options_pop('addr'), 16) port = int(options_pop('port', '1')) try: - lcd = i2c.CharLCD(i2c_expander, address, port=port, charmap=charmap, cols=cols, - rows=rows, expander_params=options) + lcd = i2c.CharLCD( + i2c_expander, + address, + port=port, + charmap=charmap, + cols=cols, + rows=rows, + expander_params=options, + ) except IOError: - print_usage('IOError: Usually caused by the wrong i2c address/port ' - 'or device not connected properly') + print_usage( + 'IOError: Usually caused by the wrong i2c address/port ' + 'or device not connected properly' + ) elif lcdmode == 'gpio': import RPi.GPIO as GPIO @@ -221,8 +239,17 @@ def run(): pins_data = data.split(',') # Convert data pins to int pins_data = [int(pin) for pin in pins_data] - lcd = gpio.CharLCD(pin_rs=rs, pin_rw=rw, pin_e=e, pins_data=pins_data, pin_backlight=bl, - numbering_mode=numbering_mode, cols=cols, rows=rows, charmap=charmap) + lcd = gpio.CharLCD( + pin_rs=rs, + pin_rw=rw, + pin_e=e, + pins_data=pins_data, + pin_backlight=bl, + numbering_mode=numbering_mode, + cols=cols, + rows=rows, + charmap=charmap, + ) elif lcdmode == 'pigpio': from pigpio import pi @@ -254,12 +281,21 @@ def run(): pins_data = data.split(',') # Convert data pins to int pins_data = [int(pin) for pin in pins_data] - lcd = pigpio.CharLCD(pi, - pin_rs=rs, pin_rw=rw, pin_e=e, pins_data=pins_data, pin_backlight=bl, - cols=cols, rows=rows, charmap=charmap) + lcd = pigpio.CharLCD( + pi, + pin_rs=rs, + pin_rw=rw, + pin_e=e, + pins_data=pins_data, + pin_backlight=bl, + cols=cols, + rows=rows, + charmap=charmap, + ) else: - print_usage('Connection type %s is not supported. Must be either i2c, gpio or pigpio' % - lcdmode) + print_usage( + 'Connection type %s is not supported. Must be either i2c, gpio or pigpio' % lcdmode + ) # Run selected test if test == 'show_charmap': diff --git a/RPLCD_Tests/testsuite_16x2.py b/RPLCD_Tests/testsuite_16x2.py index 052ce7b..5bc3a74 100644 --- a/RPLCD_Tests/testsuite_16x2.py +++ b/RPLCD_Tests/testsuite_16x2.py @@ -63,8 +63,10 @@ def run(lcd): input('The string "cursor" should now be on the second row, column 0. ') lcd.home() - input('Cursor should now be at initial position. Everything should be shifted ' - 'to the right by 5 characters. ') + input( + 'Cursor should now be at initial position. Everything should be shifted ' + 'to the right by 5 characters. ' + ) lcd.cursor_pos = (1, 15) lcd.write_string('X') @@ -103,8 +105,10 @@ def run(lcd): lcd.cursor_pos = (0, 4) lcd.write_string('5\n') lcd.write_string('6') - input('The numbers 1-6 should now be displayed in a zig zag line starting ' - 'in the top left corner. ') + input( + 'The numbers 1-6 should now be displayed in a zig zag line starting ' + 'in the top left corner. ' + ) lcd.clear() lcd.write_string('This will wrap around both lines') diff --git a/RPLCD_Tests/testsuite_20x4.py b/RPLCD_Tests/testsuite_20x4.py index 5defa28..b67333b 100644 --- a/RPLCD_Tests/testsuite_20x4.py +++ b/RPLCD_Tests/testsuite_20x4.py @@ -65,8 +65,10 @@ def run(lcd): input('The string "cursor" should now be on the third row, column 0. ') lcd.home() - input('Cursor should now be at initial position. Everything should be shifted ' - 'to the right by 5 characters. ') + input( + 'Cursor should now be at initial position. Everything should be shifted ' + 'to the right by 5 characters. ' + ) lcd.cursor_pos = (3, 19) lcd.write_string('X') @@ -98,8 +100,10 @@ def run(lcd): lcd.write_string('2\n') lcd.write_string('3\n') lcd.write_string('4') - input('The numbers 1-4 should now be displayed, each line shifted to the right ' - 'by 1 char more than the previous. ') + input( + 'The numbers 1-4 should now be displayed, each line shifted to the right ' + 'by 1 char more than the previous. ' + ) lcd.clear() lcd.write_string('This is a long string that will wrap across multiple lines!') diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2a28038 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,9 @@ +[tool.black] +line-length = 99 +target-version = ["py38", "py39", "py310", "py311", "py312"] +extend-exclude = ''' +(^/RPLCD/codecs/.*\.py$ +|^/docs/conf.py$ +) +''' +skip-string-normalization = true diff --git a/setup.py b/setup.py index 6fdacf6..a4c535a 100644 --- a/setup.py +++ b/setup.py @@ -2,36 +2,37 @@ from setuptools import setup -readme = open('README.rst').read() +readme = open("README.rst").read() -setup(name='RPLCD', - version='1.3.1', - description='A Raspberry Pi LCD library for the widely used Hitachi HD44780 controller.', - long_description=readme, - author='Danilo Bargen', - author_email='mail@dbrgn.ch', - url='https://github.com/dbrgn/RPLCD', - license='MIT', - keywords='raspberry, raspberry pi, lcd, liquid crystal, hitachi, hd44780', - packages=['RPLCD', 'RPLCD.codecs', 'RPLCD_Tests'], - entry_points={ - 'console_scripts': ['rplcd-tests=RPLCD_Tests.entrypoint:run'], - }, - platforms=['any'], - python_requires='>=3.8', - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Other Environment', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: MIT License', - 'Operating System :: POSIX', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Topic :: System :: Hardware :: Hardware Drivers', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], - ) +setup( + name="RPLCD", + version="1.3.1", + description="A Raspberry Pi LCD library for the widely used Hitachi HD44780 controller.", + long_description=readme, + author="Danilo Bargen", + author_email="mail@dbrgn.ch", + url="https://github.com/dbrgn/RPLCD", + license="MIT", + keywords="raspberry, raspberry pi, lcd, liquid crystal, hitachi, hd44780", + packages=["RPLCD", "RPLCD.codecs", "RPLCD_Tests"], + entry_points={ + "console_scripts": ["rplcd-tests=RPLCD_Tests.entrypoint:run"], + }, + platforms=["any"], + python_requires=">=3.8", + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Environment :: Other Environment", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: POSIX", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: System :: Hardware :: Hardware Drivers", + "Topic :: Software Development :: Libraries :: Python Modules", + ], +) diff --git a/tests/conftest.py b/tests/conftest.py index a6eb12e..21d214d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,6 +16,7 @@ @pytest.fixture def charlcd_kwargs(): import RPi.GPIO as GPIO + return { 'numbering_mode': GPIO.BOARD, 'pin_rs': 15, diff --git a/tests/test_auto_linebreaks.py b/tests/test_auto_linebreaks.py index cfbf0b0..a4220df 100644 --- a/tests/test_auto_linebreaks.py +++ b/tests/test_auto_linebreaks.py @@ -13,6 +13,7 @@ def _func(cols, rows, auto_linebreaks): mocker.patch.object(lcd, '_send_data') mocker.patch.object(lcd, '_send_instruction') return lcd + return _func diff --git a/tests/test_codecs.py b/tests/test_codecs.py index 758bd5b..7c9840c 100644 --- a/tests/test_codecs.py +++ b/tests/test_codecs.py @@ -3,38 +3,57 @@ from RPLCD import codecs -@pytest.mark.parametrize(['input_', 'result_a00', 'result_a02', 'result_st0b'], [ - # Empty - ('', [], [], []), - # Single char, obvious mapping - (' ', [32], [32], [32]), - ('a', [97], [97], [97]), - # Single char, different mapping depending on charmap - ('α', [224], [144], [223]), - # Single char, only available on some charmaps - ('♡', [32], [157], [32]), - ('❤', [32], [157], [32]), - ('°', [223], [32], [178]), - # Multiple 1:1 mapped chars - ('asdf', [97, 115, 100, 102], [97, 115, 100, 102], [97, 115, 100, 102]), - # Combined mapping - ('\u207B\u00B9', [233], [32, 185], [191, 32]), - ('as\u207B\u00B9df', [97, 115, 233, 100, 102], [97, 115, 32, 185, 100, 102], [97, 115, 191, 32, 100, 102]), - ('\u207B', [32], [32], [191]), - ('\u207Ba', [32, 97], [32, 97], [191, 97]), - # Containing newlines and carriage returns - ('a\r\nb', [97, codecs.CR, codecs.LF, 98], [97, codecs.CR, codecs.LF, 98], [97, codecs.CR, codecs.LF, 98]), -]) +@pytest.mark.parametrize( + ['input_', 'result_a00', 'result_a02', 'result_st0b'], + [ + # Empty + ('', [], [], []), + # Single char, obvious mapping + (' ', [32], [32], [32]), + ('a', [97], [97], [97]), + # Single char, different mapping depending on charmap + ('α', [224], [144], [223]), + # Single char, only available on some charmaps + ('♡', [32], [157], [32]), + ('❤', [32], [157], [32]), + ('°', [223], [32], [178]), + # Multiple 1:1 mapped chars + ('asdf', [97, 115, 100, 102], [97, 115, 100, 102], [97, 115, 100, 102]), + # Combined mapping + ('\u207b\u00b9', [233], [32, 185], [191, 32]), + ( + 'as\u207b\u00b9df', + [97, 115, 233, 100, 102], + [97, 115, 32, 185, 100, 102], + [97, 115, 191, 32, 100, 102], + ), + ('\u207b', [32], [32], [191]), + ('\u207ba', [32, 97], [32, 97], [191, 97]), + # Containing newlines and carriage returns + ( + 'a\r\nb', + [97, codecs.CR, codecs.LF, 98], + [97, codecs.CR, codecs.LF, 98], + [97, codecs.CR, codecs.LF, 98], + ), + ], +) def test_encode(input_, result_a00, result_a02, result_st0b): a00 = codecs.A00Codec() a02 = codecs.A02Codec() st0b = codecs.ST0BCodec() - assert a00.encode(input_) == result_a00, \ - 'A00: Input %r encoded to %s' % (input_, a00.encode(input_)) + assert a00.encode(input_) == result_a00, 'A00: Input %r encoded to %s' % ( + input_, + a00.encode(input_), + ) - assert a02.encode(input_) == result_a02, \ - 'A02: Input %r encoded to %s' % (input_, a02.encode(input_)) - - assert st0b.encode(input_) == result_st0b, \ - 'ST0B: Input %r encoded to %s' % (input_, st0b.encode(input_)) + assert a02.encode(input_) == result_a02, 'A02: Input %r encoded to %s' % ( + input_, + a02.encode(input_), + ) + + assert st0b.encode(input_) == result_st0b, 'ST0B: Input %r encoded to %s' % ( + input_, + st0b.encode(input_), + ) diff --git a/tests/test_common.py b/tests/test_common.py index 8971d5c..148ada9 100644 --- a/tests/test_common.py +++ b/tests/test_common.py @@ -3,13 +3,16 @@ from RPLCD import common -@pytest.mark.parametrize(['input_', 'lookahead', 'result'], [ - ('hi', 0, [('h',), ('i',)]), - ('hi', 1, [('h', 'i'), ('i', ' ')]), - ('hi', 2, [('h', 'i', ' '), ('i', ' ', ' ')]), - ('', 0, []), - ('', 1, []), - ('', 7, []), -]) +@pytest.mark.parametrize( + ['input_', 'lookahead', 'result'], + [ + ('hi', 0, [('h',), ('i',)]), + ('hi', 1, [('h', 'i'), ('i', ' ')]), + ('hi', 2, [('h', 'i', ' '), ('i', ' ', ' ')]), + ('', 0, []), + ('', 1, []), + ('', 7, []), + ], +) def test_window_function(input_, lookahead, result): assert list(common.sliding_window(input_, lookahead)) == result diff --git a/tests/test_write.py b/tests/test_write.py index 695b107..c337f03 100644 --- a/tests/test_write.py +++ b/tests/test_write.py @@ -59,10 +59,13 @@ def test_caching(mocker, charlcd_kwargs): assert instruction_calls[2] == (LCD_SETDDRAMADDR | 5,) -@pytest.mark.parametrize(['charmap', 'ue'], [ - ('A00', 0b11110101), - ('A02', 0b11111100), -]) +@pytest.mark.parametrize( + ['charmap', 'ue'], + [ + ('A00', 0b11110101), + ('A02', 0b11111100), + ], +) def test_charmap(mocker, charmap, ue, charlcd_kwargs): """ The charmap should be used. The "ü" Umlaut should be encoded correctly. @@ -81,10 +84,13 @@ def test_charmap(mocker, charmap, ue, charlcd_kwargs): assert calls[3] == (105,) -@pytest.mark.parametrize(['rows', 'cols'], [ - (2, 16), - (4, 20), -]) +@pytest.mark.parametrize( + ['rows', 'cols'], + [ + (2, 16), + (4, 20), + ], +) def test_write_newline(mocker, rows, cols, charlcd_kwargs): """ Write text containing CR/LF chars to the display.