#include "_Plugin_Helper.h"
#ifdef USES_P063

// #######################################################################################################
// #################################### Plugin 063: TTP229 KeyPad ########################################
// #######################################################################################################

/** Changelog:
 * 2025-06-14 tonhuisman: Add support for Custom Value Type per task value
 * 2025-01-12 tonhuisman: Add support for MQTT AutoDiscovery (not supported for Keypad)
 * 2025-01-04 tonhuisman: Make plugin multi-instance by not using a static variable for lastKey, but reading back from UserVar
 *                        Minor code optimizations
 */

// ESPEasy Plugin to scan a 16 key touch pad chip TTP229
// written by Jochen Krapf (jk@nerd2nerd.org)

// Important: There are several types of TTP299 chips with different features available. They are named all TTP229 but differ in the
// letter(s) followed.
// On the china boards (found on eBay and AliExpress) the TTP229-B is used which has NO! I2C-interface. It uses a proprietary serial
// protocol with clock (SCL) and bidirectional data (SDO)

// ScanCode;
// Value 1...16 for the key number
// No key - the code 0
// If more than one key is pressed, the scan code is the code with the lowest value

// If ScanCode is unchecked the value is the KeyMap 1.Key=1, 2.Key=2, 3.Key=4, 4.Key=8 ... 16.Key=32768
// If more than one key is pressed, the value is sum of all KeyMap-values

// Electronics:
// Connect SCL to 1st GPIO and SDO to 2nd GPIO. Use 3.3 volt for VCC.
// Set the jumper for 16 key mode (TP2=jumper3). Additional set jumper for multi-key (TP3=jumper4, TP4=jumper5).
// Schematics: https://www.openimpulse.com/blog/wp-content/uploads/wpsc/downloadables/TTP229B-Schematic-Diagram.pdf
// Datasheet: http://www.datasheet4u.com/download_new.php?id=996751

# define PLUGIN_063
# define PLUGIN_ID_063         63
# define PLUGIN_NAME_063       "Keypad - TTP229 Touch"
# define PLUGIN_VALUENAME1_063 "ScanCode"


uint16_t readTTP229(int16_t pinSCL, int16_t pinSDO)
{
  uint16_t value = 0;
  uint16_t mask  = 1;

  pinMode(pinSDO, OUTPUT);
  digitalWrite(pinSDO, HIGH);
  delayMicroseconds(100);

  digitalWrite(pinSDO, LOW);
  delayMicroseconds(10);

  pinMode(pinSDO, INPUT);

  for (uint8_t i = 0; i < 16; ++i)
  {
    digitalWrite(pinSCL, HIGH);
    delayMicroseconds(1);
    digitalWrite(pinSCL, LOW);

    if (!digitalRead(pinSDO)) {
      value |= mask;
    }
    delayMicroseconds(1);
    mask <<= 1;
  }

  return value;
}

boolean Plugin_063(uint8_t function, struct EventStruct *event, String& string)
{
  boolean success = false;

  switch (function)
  {
    case PLUGIN_DEVICE_ADD:
    {
      auto& dev = Device[++deviceCount];
      dev.Number         = PLUGIN_ID_063;
      dev.Type           = DEVICE_TYPE_DUAL;
      dev.VType          = Sensor_VType::SENSOR_TYPE_SWITCH;
      dev.ValueCount     = 1;
      dev.SendDataOption = true;
      dev.TimerOption    = true;
      dev.TimerOptional  = true;
      dev.setPin1Direction(gpio_direction::gpio_output);
      dev.CustomVTypeVar = true;
      break;
    }

    case PLUGIN_GET_DEVICENAME:
    {
      string = F(PLUGIN_NAME_063);
      break;
    }

    case PLUGIN_GET_DEVICEVALUENAMES:
    {
      strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_063));
      break;
    }

    case PLUGIN_GET_DEVICEGPIONAMES:
    {
      event->String1 = formatGpioName_output(F("SCL"));
      event->String2 = formatGpioName_bidirectional(F("SDO"));
      break;
    }

    # if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE
    case PLUGIN_GET_DISCOVERY_VTYPES:
    {
      #  if FEATURE_CUSTOM_TASKVAR_VTYPE

      for (uint8_t i = 0; i < event->Par5; ++i) {
        event->ParN[i] = ExtraTaskSettings.getTaskVarCustomVType(i);  // Custom/User selection
      }
      #  else // if FEATURE_CUSTOM_TASKVAR_VTYPE
      event->Par1 = static_cast<int>(Sensor_VType::SENSOR_TYPE_NONE); // Not yet supported
      #  endif // if FEATURE_CUSTOM_TASKVAR_VTYPE
      success = true;
      break;
    }
    # endif // if FEATURE_MQTT_DISCOVER || FEATURE_CUSTOM_TASKVAR_VTYPE

    case PLUGIN_WEBFORM_LOAD:
    {
      addFormCheckBox(F("ScanCode"), F("scancode"), PCONFIG(1));

      success = true;
      break;
    }

    case PLUGIN_WEBFORM_SAVE:
    {
      PCONFIG(1) = isFormItemChecked(F("scancode"));

      success = true;
      break;
    }

    case PLUGIN_INIT:
    {
      portStatusStruct newStatus;

      int16_t pinSCL = CONFIG_PIN1;
      int16_t pinSDO = CONFIG_PIN2;

      if (loglevelActiveFor(LOG_LEVEL_INFO)) {
        addLog(LOG_LEVEL_INFO, strformat(F("Tkey : GPIO: %d %d"), pinSCL, pinSDO));
      }

      if (validGpio(pinSCL) && validGpio(pinSDO))
      {
        pinMode(pinSCL, OUTPUT);
        digitalWrite(pinSCL, LOW);

        constexpr pluginID_t P063_PLUGIN_ID{ PLUGIN_ID_063 };

        uint32_t key = createKey(P063_PLUGIN_ID, pinSCL);

        // WARNING: operator [] creates an entry in the map if key does not exist
        newStatus = globalMapPortStatus[key];
        newStatus.task++; // add this GPIO/port as a task
        newStatus.mode  = PIN_MODE_OUTPUT;
        newStatus.state = 0;
        savePortStatus(key, newStatus);

        // setPinState(PLUGIN_ID_063, pinSCL, PIN_MODE_OUTPUT, 0);

        pinMode(pinSDO, OUTPUT);
        digitalWrite(pinSDO, LOW);
        key = createKey(P063_PLUGIN_ID, pinSDO);

        // WARNING: operator [] creates an entry in the map if key does not exist
        newStatus = globalMapPortStatus[key];
        newStatus.task++; // add this GPIO/port as a task
        newStatus.mode  = PIN_MODE_INPUT;
        newStatus.state = 0;
        savePortStatus(key, newStatus);

        // setPinState(PLUGIN_ID_063, pinSDO, PIN_MODE_INPUT, 0);
        success = true;
      }

      break;
    }

    case PLUGIN_TEN_PER_SECOND:
    {
      const uint16_t keyLast = UserVar.getFloat(event->TaskIndex, 0); // Last set value, makes plugin multi-instance
      const int16_t  pinSCL  = CONFIG_PIN1;
      const int16_t  pinSDO  = CONFIG_PIN2;

      uint16_t key = readTTP229(pinSCL, pinSDO);

      if (key && PCONFIG(1))
      {
        uint16_t colMask = 0x01;

        for (uint8_t col = 1; col <= 16; ++col)
        {
          if (key & colMask) // this key pressed?
          {
            key = col;
            break;
          }
          colMask <<= 1;
        }
      }

      if (keyLast != key)
      {
        UserVar.setFloat(event->TaskIndex, 0, key);

        if (loglevelActiveFor(LOG_LEVEL_INFO)) {
          String log = F("Tkey : ");

          if (PCONFIG(1)) {
            log = F("ScanCode=");
          }
          else {
            log = F("KeyMap=");
          }
          log += formatToHex(key);
          addLogMove(LOG_LEVEL_INFO, log);
        }

        sendData(event);
      }

      success = true;
      break;
    }

    case PLUGIN_READ:
    {
      // work is done in PLUGIN_TEN_PER_SECOND
      success = true;
      break;
    }
  }
  return success;
}

#endif // USES_P063
