text: # -*- coding: utf-8 -*-
  
  # Octowire Framework
  # Copyright (c) ImmunIT - Jordan Ovrè / Paul Duncan
  # License: Apache 2.0
  # Paul Duncan / Eresse <pduncan@immunit.ch>
  # Jordan Ovrè / Ghecko <jovre@immunit.ch>
  
  
  import struct
  from octowire.octowire import Octowire
  
  
  class GPIO(Octowire):
      """
      GPIO protocol class.
      """
      OPCODE = b"\x08"
      OPERATION_OUTPUT = b"\x01"
      OPERATION_INPUT = b"\x02"
      OPERATION_PULLUP = b"\x03"
      OPERATION_PULLDOWN = b"\x04"
      OPERATION_DEACTIVATE_PULL = b"\x05"
      OPERATION_SET_PIN_HIGH = b"\x06"
      OPERATION_SET_PIN_LOW = b"\x07"
      OPERATION_READ_PIN = b"\x08"
  
      OUTPUT = 0
      INPUT = 1
  
      PULL_UP = 0
      PULL_DOWN = 1
      PULL_DISABLE = None
  
      def __init__(self, serial_instance, gpio_pin):
          self.serial_instance = serial_instance
          self.gpio_pin = gpio_pin
          super().__init__(serial_instance=self.serial_instance)
          if not gpio_pin and gpio_pin not in [0, 15]:
              raise ValueError('gpio_pin parameter should be defined between 0 and 15.')
          if not self.ensure_binary_mode():
              raise ValueError("Unable to enter binary mode.")
          self.direction_status = None
          self.pull_status = None
          self.pin_status = 0
  
      def _send_operation_code(self, operation_code, operation_name=None, disable_timeout=False):
          """
          This function sends an operation code and handles the response code.
          :param operation_code: The operation code (byte).
          :return: Nothing
          """
          args_size = struct.pack("<H", 2)
          self.serial_instance.write(args_size + self.OPCODE + bytes([self.gpio_pin]) + operation_code)
          self._read_response_code(operation_name=operation_name, disable_timeout=disable_timeout)
  
      @property
      def direction(self):
          """
          GPIO pin direction getter.
          :return: The pin direction (0=output, 1=input).
          :rtype: int
          """
          return self.direction_status
  
      @direction.setter
      def direction(self, direction):
          """
          Configure GPIO pin direction (0 = output, 1 = input).
          """
          if direction not in [0, 1]:
              raise ValueError("Invalid pin direction, the value should be 0 for output or 1 for input.")
          # Configure GPIO pin as output
          if direction == 0:
              self._send_operation_code(operation_code=self.OPERATION_OUTPUT, operation_name="set GPIO pin as output")
              self.direction_status = 1
          # Configure pin as input
          else:
              self._send_operation_code(operation_code=self.OPERATION_INPUT, operation_name="set GPIO pin as input")
              self.direction_status = 0
  
      @property
      def pull(self):
          """
          Return the pull config (up, down or deactivated).
          :return: The current pull resistor status (0 for up, 1 for down, None for deactivated).
          """
          return self.pull_status
  
      @pull.setter
      def pull(self, value):
          """
          Configure the pull resistor (up, down or deactivated).
          """
          if value not in [self.PULL_UP, self.PULL_DOWN, self.PULL_DISABLE]:
              raise ValueError("Invalid pull value, the value should be 0 for pull-up,"
                               " 1 for pull-down or None for pull disabled.")
          # Deactivate pull resistors
          if value is None:
              self._send_operation_code(operation_code=self.OPERATION_DEACTIVATE_PULL, operation_name="GPIO pull disable")
              self.pull_status = None
          # Activate pull-up resistor
          if value == 0:
              self._send_operation_code(operation_code=self.OPERATION_PULLUP, operation_name="GPIO set pull-up")
              self.pull_status = 0
          # Activate pull-down resistor
          if value == 1:
              self._send_operation_code(operation_code=self.OPERATION_PULLDOWN, operation_name="GPIO set pull-down")
              self.pull_status = 1
  
      def _set_output_pin_high(self):
          """
          Set the GPIO output pin to high.
          """
          self._send_operation_code(operation_code=self.OPERATION_SET_PIN_HIGH, operation_name="Set GPIO pin to high",
                                    disable_timeout=True)
          self.pin_status = 1
  
      def _set_output_pin_low(self):
          """
          Set the GPIO output pin to low.
          """
          self._send_operation_code(operation_code=self.OPERATION_SET_PIN_LOW, operation_name="Set GPIO pin to low",
                                    disable_timeout=True)
          self.pin_status = 0
  
      @property
      def status(self):
          """
          Return the current output pin status.
          :return: Pin status (0 for low, 1 for high).
          :rtype: int
          """
          return self.pin_status
  
      @status.setter
      def status(self, value):
          """
          Set the GPIO output pin to low or high.
          """
          if value not in [0, 1]:
              raise ValueError("Invalid status value, the value should be 0 for low or 1 for high.")
          if value == 0:
              self._set_output_pin_low()
          elif value == 1:
              self._set_output_pin_high()
  
      def toggle(self):
          """
          Toggle the GPIO output pin (switch between low and high).
          """
          self.status = 1 if self.pin_status == 0 else 0
  
      def read(self):
          """
          Return the status of the input pin.
          :return: The status of the pin (0 for low, 1 for high).
          :rtype: int
          """
          self._send_operation_code(operation_code=self.OPERATION_READ_PIN, operation_name="GPIO read input pin")
          return self.serial_instance.read(1)
  