|
| 1 | +""" |
| 2 | +XL9535/PCA9535/TCA9535 IO expander |
| 3 | +""" |
| 4 | + |
| 5 | +from typing import Optional, cast |
| 6 | + |
| 7 | +from ...exceptions import RuntimeConfigError |
| 8 | +from ...types import ConfigType, PinType |
| 9 | +from . import GenericGPIO, PinDirection, PinPUD |
| 10 | + |
| 11 | +REQUIREMENTS = ("smbus2",) |
| 12 | +CONFIG_SCHEMA = { |
| 13 | + "i2c_bus_num": {"type": "integer", "required": True, "empty": False}, |
| 14 | + "chip_addr": {"type": "integer", "required": True, "empty": False}, |
| 15 | +} |
| 16 | + |
| 17 | +#XL9535_INPUT_PORT_0 = 0x00, |
| 18 | +#XL9535_INPUT_PORT_1 = 0x01 |
| 19 | +XL9535_OUTPUT_PORT_0 = 0x02 |
| 20 | +#XL9535_OUTPUT_PORT_1 = 0x03 |
| 21 | +#XL9535_INVERSION_PORT_0 = 0x04 |
| 22 | +#XL9535_INVERSION_PORT_1 = 0x05 |
| 23 | +XL9535_CONFIG_PORT_0 = 0x06 |
| 24 | +#XL9535_CONFIG_PORT_1 = 0x07 |
| 25 | + |
| 26 | +class GPIO(GenericGPIO): |
| 27 | + """ |
| 28 | + Implementation of GPIO class for the XL9535/PCA9535/TCA9535 IO expander chip. |
| 29 | +
|
| 30 | + Pin numbers 0 - 15. |
| 31 | + """ |
| 32 | + |
| 33 | + PIN_SCHEMA = { |
| 34 | + "pin": {"type": 'integer', "required": True, "min": 0, "max": 15}, |
| 35 | + } |
| 36 | + |
| 37 | + def setup_module(self) -> None: |
| 38 | + # pylint: disable=import-outside-toplevel,import-error |
| 39 | + # pylint: disable=no-name-in-module |
| 40 | + from smbus2 import SMBus # type: ignore |
| 41 | + |
| 42 | + self.bus = SMBus(self.config["i2c_bus_num"]) |
| 43 | + self.address = self.config["chip_addr"] |
| 44 | + |
| 45 | + # configure all pins as outputs |
| 46 | + self.bus.write_word_data(self.address, XL9535_CONFIG_PORT_0, 0x0000) |
| 47 | + # off all outputs be default |
| 48 | + self.bus.write_word_data(self.address, XL9535_OUTPUT_PORT_0, 0x0000) |
| 49 | + |
| 50 | + def setup_pin( |
| 51 | + self, |
| 52 | + pin: PinType, |
| 53 | + direction: PinDirection, |
| 54 | + pullup: PinPUD, |
| 55 | + pin_config: ConfigType, |
| 56 | + initial: Optional[str] = None, |
| 57 | + ) -> None: |
| 58 | + # I han't found any ready-made expansion boards on sale where the chip works as an input. |
| 59 | + # For simplicity, the implementation only supports the chip working in output mode. |
| 60 | + if direction == PinDirection.INPUT: |
| 61 | + raise RuntimeConfigError("Unsupported PinDirection: INPUT") |
| 62 | + initial = pin_config.get("initial") |
| 63 | + if initial is not None: |
| 64 | + if initial == "high": |
| 65 | + self.set_pin(pin, True) |
| 66 | + elif initial == "low": |
| 67 | + self.set_pin(pin, False) |
| 68 | + |
| 69 | + def set_pin(self, pin: PinType, value: bool) -> None: |
| 70 | + assert pin in range(16), "Pin number must be an integer between 0 and 15" |
| 71 | + current_state = self.bus.read_word_data(self.address, XL9535_OUTPUT_PORT_0) |
| 72 | + bit = 1 << cast(int, pin) |
| 73 | + new_state = current_state | bit if value else current_state & (~bit & 0xffff) |
| 74 | + self.bus.write_word_data(self.address, XL9535_OUTPUT_PORT_0, new_state) |
| 75 | + |
| 76 | + def get_pin(self, pin: PinType) -> bool: |
| 77 | + assert pin in range(16), "Pin number must be an integer between 0 and 15" |
| 78 | + state = self.bus.read_word_data(self.address, XL9535_OUTPUT_PORT_0) |
| 79 | + return bool(state & 1 << cast(int, pin)) |
0 commit comments