@Override
  public void setState(Pin pin, PinState state) {
    // validate
    if (hasPin(pin) == false) {
      throw new InvalidPinException(pin);
    }
    // only permit invocation on pins set to DIGITAL_OUTPUT modes
    if (getPinCache(pin).getMode() != PinMode.DIGITAL_OUTPUT) {
      throw new InvalidPinModeException(
          pin,
          "Invalid pin mode on pin ["
              + pin.getName()
              + "]; cannot setState() when pin mode is ["
              + getPinCache(pin).getMode().getName()
              + "]");
    }
    try {
      // determine A or B port based on pin address
      if (pin.getAddress() < GPIO_B_OFFSET) {
        setStateA(pin, state);
      } else {
        setStateB(pin, state);
      }
    } catch (IOException ex) {
      throw new RuntimeException(ex);
    }

    // cache pin state
    getPinCache(pin).setState(state);
  }
    public void run() {
      while (!shuttingDown) {
        try {
          // only process for interrupts if a pin on port A is configured as an input pin
          if (currentDirectionA > 0) {
            // process interrupts for port A
            byte pinInterruptA = provider.read(REGISTER_INTF_A);

            // validate that there is at least one interrupt active on port A
            if (pinInterruptA > 0) {
              // read the current pin states on port A
              byte pinInterruptState = provider.read(REGISTER_GPIO_A);

              // loop over the available pins on port B
              for (Pin pin : MCP23S17Pin.ALL_A_PINS) {
                int pinAddressA = pin.getAddress() - GPIO_A_OFFSET;

                // is there an interrupt flag on this pin?
                if ((pinInterruptA & pinAddressA) > 0) {
                  // System.out.println("INTERRUPT ON PIN [" + pin.getName() + "]");
                  evaluatePinForChangeA(pin, pinInterruptState);
                }
              }
            }
          }

          // only process for interrupts if a pin on port B is configured as an input pin
          if (currentDirectionB > 0) {
            // process interrupts for port B
            int pinInterruptB = (int) provider.read(REGISTER_INTF_B);

            // validate that there is at least one interrupt active on port B
            if (pinInterruptB > 0) {
              // read the current pin states on port B
              int pinInterruptState = (int) provider.read(REGISTER_GPIO_B);

              // loop over the available pins on port B
              for (Pin pin : MCP23S17Pin.ALL_B_PINS) {
                int pinAddressB = pin.getAddress() - GPIO_B_OFFSET;

                // is there an interrupt flag on this pin?
                if ((pinInterruptB & pinAddressB) > 0) {
                  // System.out.println("INTERRUPT ON PIN [" + pin.getName() + "]");
                  evaluatePinForChangeB(pin, pinInterruptState);
                }
              }
            }
          }

          // ... lets take a short breather ...
          Thread.currentThread();
          Thread.sleep(50);
        } catch (Exception ex) {
          ex.printStackTrace();
        }
      }
    }
    private void dispatchPinChangeEvent(int pinAddress, PinState state) {
      // iterate over the pin listeners map
      for (Pin pin : listeners.keySet()) {
        // System.out.println("<<< DISPATCH >>> " + pin.getName() + " : " +
        // state.getName());

        // dispatch this event to the listener
        // if a matching pin address is found
        if (pin.getAddress() == pinAddress) {
          // dispatch this event to all listener handlers
          for (PinListener listener : listeners.get(pin)) {
            listener.handlePinEvent(new PinDigitalStateChangeEvent(this, pin, state));
          }
        }
      }
    }
  @Override
  public void setMode(Pin pin, PinMode mode) {
    // validate
    if (!pin.getSupportedPinModes().contains(mode)) {
      throw new InvalidPinModeException(
          pin,
          "Invalid pin mode ["
              + mode.getName()
              + "]; pin ["
              + pin.getName()
              + "] does not support this mode.");
    }
    // validate
    if (!pin.getSupportedPinModes().contains(mode)) {
      throw new UnsupportedPinModeException(pin, mode);
    }
    // determine A or B port based on pin address
    try {
      if (pin.getAddress() < GPIO_B_OFFSET) {
        setModeA(pin, mode);
      } else {
        setModeB(pin, mode);
      }
    } catch (IOException ex) {
      throw new RuntimeException(ex);
    }

    // cache mode
    getPinCache(pin).setMode(mode);

    // if any pins are configured as input pins, then we need to start the interrupt monitoring
    // thread
    if (currentDirectionA > 0 || currentDirectionB > 0) {
      // if the monitor has not been started, then start it now
      if (monitor == null) {
        // start monitoring thread
        monitor = new GpioStateMonitor(this);
        monitor.start();
      }
    } else {
      // shutdown and destroy monitoring thread since there are no input pins configured
      if (monitor != null) {
        monitor.shutdown();
        monitor = null;
      }
    }
  }
  private PinState getStateB(Pin pin) {

    // determine pin address
    int pinAddress = pin.getAddress() - GPIO_B_OFFSET;

    // determine pin state
    PinState state = (currentStatesB & pinAddress) == pinAddress ? PinState.HIGH : PinState.LOW;

    // cache state
    getPinCache(pin).setState(state);

    return state;
  }
  private void setStateB(Pin pin, PinState state) throws IOException {
    // determine pin address
    int pinAddress = pin.getAddress() - GPIO_B_OFFSET;

    // determine state value for pin bit
    if (state.isHigh()) {
      currentStatesB |= pinAddress;
    } else {
      currentStatesB &= ~pinAddress;
    }

    // update state value
    write(REGISTER_GPIO_B, (byte) currentStatesB);
  }
  private void setPullResistanceB(Pin pin, PinPullResistance resistance) throws IOException {
    // determine pin address
    int pinAddress = pin.getAddress() - GPIO_B_OFFSET;

    // determine pull up value for pin bit
    if (resistance == PinPullResistance.PULL_UP) {
      currentPullupB |= pinAddress;
    } else {
      currentPullupB &= ~pinAddress;
    }

    // next update pull up resistor value
    write(REGISTER_GPPU_B, (byte) currentPullupB);
  }
    private void evaluatePinForChangeB(Pin pin, int state) {
      if (getPinCache(pin).isExported()) {
        // determine pin address
        int pinAddress = pin.getAddress() - GPIO_B_OFFSET;

        if ((state & pinAddress) != (currentStatesB & pinAddress)) {
          PinState newState = (state & pinAddress) == pinAddress ? PinState.HIGH : PinState.LOW;

          // cache state
          getPinCache(pin).setState(newState);

          // determine and cache state value for pin bit
          if (newState.isHigh()) {
            currentStatesB |= pinAddress;
          } else {
            currentStatesB &= ~pinAddress;
          }

          // change detected for INPUT PIN
          // System.out.println("<<< CHANGE >>> " + pin.getName() + " : " + state);
          dispatchPinChangeEvent(pin.getAddress(), newState);
        }
      }
    }
  @Override
  public void setPullResistance(Pin pin, PinPullResistance resistance) {
    // validate
    if (hasPin(pin) == false) {
      throw new InvalidPinException(pin);
    }
    // validate
    if (!pin.getSupportedPinPullResistance().contains(resistance)) {
      throw new UnsupportedPinPullResistanceException(pin, resistance);
    }
    try {
      // determine A or B port based on pin address
      if (pin.getAddress() < GPIO_B_OFFSET) {
        setPullResistanceA(pin, resistance);
      } else {
        setPullResistanceB(pin, resistance);
      }
    } catch (IOException ex) {
      throw new RuntimeException(ex);
    }

    // cache resistance
    getPinCache(pin).setResistance(resistance);
  }
  @Override
  public PinState getState(Pin pin) {
    // call super method to perform validation on pin
    PinState result = super.getState(pin);

    // determine A or B port based on pin address
    if (pin.getAddress() < GPIO_B_OFFSET) {
      result = getStateA(pin); // get pin state
    } else {
      result = getStateB(pin); // get pin state
    }

    // return pin state
    return result;
  }
  private void setModeB(Pin pin, PinMode mode) throws IOException {
    // determine register and pin address
    int pinAddress = pin.getAddress() - GPIO_B_OFFSET;

    // determine update direction value based on mode
    if (mode == PinMode.DIGITAL_INPUT) {
      currentDirectionB |= pinAddress;
    } else if (mode == PinMode.DIGITAL_OUTPUT) {
      currentDirectionB &= ~pinAddress;
    }

    // next update direction (mode) value
    write(REGISTER_IODIR_B, (byte) currentDirectionB);

    // enable interrupts; interrupt on any change from previous state
    write(REGISTER_GPINTEN_B, (byte) currentDirectionB);
  }
 @Override
 public GpioPinPwmOutput provisionPwmOutputPin(GpioProvider provider, Pin pin, int defaultValue) {
   return provisionPwmOutputPin(provider, pin, pin.getName(), defaultValue);
 }
 @Override
 public GpioPinAnalogOutput provisionAnalogOutputPin(
     GpioProvider provider, Pin pin, double defaultValue) {
   return provisionAnalogOutputPin(provider, pin, pin.getName(), defaultValue);
 }
 @Override
 public GpioPinDigitalOutput provisionDigitalOutputPin(
     GpioProvider provider, Pin pin, PinState defaultState) {
   return provisionDigitalOutputPin(provider, pin, pin.getName(), defaultState);
 }
 @Override
 public GpioPinDigitalInput provisionDigitalInputPin(
     GpioProvider provider, Pin pin, PinPullResistance resistance) {
   // create new GPIO pin instance
   return provisionDigitalInputPin(provider, pin, pin.getName(), resistance);
 }
 @Override
 public GpioPinDigitalMultipurpose provisionDigitalMultipurposePin(
     GpioProvider provider, Pin pin, PinMode mode, PinPullResistance resistance) {
   // create new GPIO pin instance
   return provisionDigitalMultipurposePin(provider, pin, pin.getName(), mode, resistance);
 }
 @Override
 public GpioPin provisionPin(GpioProvider provider, Pin pin, PinMode mode) {
   return provisionPin(provider, pin, pin.getName(), mode);
 }