from machine import Pin, I2C
from time import sleep

class PCF8574P(object):
    _MAX_KEYS_  = 4
    _OUT_PUT_   = 3
    _IN_PUT_    = 2
    _HIGH_      = True
    _LOW_       = False

    class MODE(object):
        def __init__(self, pcf8574p: "PCF8574P", pin: int, mode: int):
            self.pcf8574p = pcf8574p
            self.pin = pin
            self.mode = mode
            if self.mode not in (PCF8574P._IN_PUT_, PCF8574P._OUT_PUT_):
                raise ValueError(f"[ERROR]  Mode must be 2 [INPUT] or 3 [OUTPUT]")
            self.pcf8574p.setting(self.pin, self.mode)
        
        @property
        def values(self):
            self.pcf8574p.update()
            return (self.pcf8574p.memory[0] >> self.pin) & 1
        
        @values.setter
        def values(self, value: bool):
            if self.mode == PCF8574P._OUT_PUT_:
                self.pcf8574p.output(self.pin, value)
            else:
                raise ValueError(f"[ERROR] Pin {self.pin} is in INPUT mode. Use .values to get input.") 

    def __init__(self, i2c: I2C, address: hex = 0x20):
        self.i2c = i2c
        self.address = address
        self.memory = bytearray(1)
        self.memory[0] = 0xFF
        self.i2c.writeto(self.address, self.memory)
    
    def searchAddress(self):
        _address = self.i2c.scan()
        if not _address:
            return []
        return [hex(_temp) for _temp in _address]
    
    def update(self):
        try:
            self.i2c.readfrom_into(self.address, self.memory)
        except OSError as e:
            print(f"[ERROR] {e}")
    
    def setting(self, pin: int, mode: int):
        if not 0 <= pin <= 7:
            raise ValueError(f"[ERROR]  Pin {pin} not in range 0-7")
        elif mode not in (self._OUT_PUT_, self._IN_PUT_):
            raise ValueError(f"[ERROR]  Mode {mode} must be 2 [INPUT] or 3 [OUTPUT]")
        if (self.memory[0] & (1 << pin)) == (mode << pin):
            return
        if mode == self._OUT_PUT_:
            self.memory[0] &= ~(1 << pin)
        elif mode == self._IN_PUT_:
            self.memory[0] |= (1 << pin)
        self.i2c.writeto(self.address, self.memory)
    
    def output(self, pin: int, value: bool):
        if not 0 <= pin <= 7:
            raise ValueError(f"[ERROR]  Pin {pin} not in range 0-7")
        elif not isinstance(value, bool):
            raise ValueError(f"[ERROR]  Value {value} must be 0 [LOW][False] and 1 [HIGH][True]")
        if value:
            self.memory[0] |= (1 << pin)
        else:
            self.memory[0] &= ~(1 << pin)
        self.i2c.writeto(self.address, self.memory)
    
    def PIN(self, pin: int, mode: int):
        return self.MODE(self, pin, mode)  # Correctly call the MODE constructor
    
    @property
    def P_8BIT(self):
        try:
            self.i2c.readfrom_into(self.address, self.memory)
            return self.memory[0]
        except OSError as e:
            print(f"[ERROR] {e}")
    
    @P_8BIT.setter
    def P_8BIT(self, value):
        try:
            if not 0 <= value <= 0xFF:
                raise ValueError(f"[ERROR] Value must be in range 0-255")
            self.memory[0] = value
            self.i2c.writeto(self.address, self.memory)
        except OSError as e:
            print(f"[ERROR] {e}")

def main():
    chip1 = PCF8574P(I2C(0, scl=Pin(22), sda=Pin(21)))
    chip1.P_8BIT = 0x00
    chip2 = PCF8574P(I2C(0, scl=Pin(22), sda=Pin(21)), 0x21)
    chip2.P_8BIT = 0x00
    print(chip1.searchAddress())
    led1 = [
        chip1.PIN(0, chip1._OUT_PUT_),
        chip1.PIN(1, chip1._OUT_PUT_),
        chip1.PIN(2, chip1._OUT_PUT_),
        chip1.PIN(3, chip1._OUT_PUT_),
        chip1.PIN(4, chip1._OUT_PUT_),
        chip1.PIN(5, chip1._OUT_PUT_),
        chip1.PIN(6, chip1._OUT_PUT_),
        chip1.PIN(7, chip1._OUT_PUT_)
    ]
    led2 = [
        chip2.PIN(0, chip2._OUT_PUT_),
        chip2.PIN(1, chip2._OUT_PUT_),
        chip2.PIN(2, chip2._OUT_PUT_),
        chip2.PIN(3, chip2._OUT_PUT_),
        chip2.PIN(4, chip2._OUT_PUT_),
        chip2.PIN(5, chip2._OUT_PUT_),
        chip2.PIN(6, chip2._OUT_PUT_),
        chip2.PIN(7, chip2._OUT_PUT_)
    ]
    while True:
        for i in range(16):
            if 0 <= i <= 7:
                led1[i].values = PCF8574P._HIGH_
            else:
                led2[i - 8].values = PCF8574P._HIGH_
            sleep(2)
        for i in range(16):
            if 0 <= i <= 7:
                led1[i].values = PCF8574P._LOW_
            else:
                led2[i - 8].values = PCF8574P._LOW_
            sleep(2)

if __name__ == "__main__":
    main()
pcf8574pBreakout
pcf8575p1Breakout