/*  BrailleGlove – multi-dot, Arduino Micro – “strong click” version
    ----------------------------------------------------------------
    • 6 × DRV2605L, all on address 0x5A behind a TCA9548A
    • INT pads of all six boards tied together → Arduino pin D10
      (this pin is not used for PWM any more but must still be an output)
    • One I²C “GO” command starts the same haptic effect on every open bus
    • Serial protocol:
          - one line “0-63\n”   : Braille cell (bits 0-5 = dots 1-6)
          - line “S\n”          : word separator
*/

#include <Wire.h>

/* ---------- I²C addresses ---------- */
constexpr uint8_t TCA_ADDR = 0x70;   // TCA9548A multiplexer
constexpr uint8_t DRV_ADDR = 0x5A;   // DRV2605L
constexpr uint8_t MODE_REG = 0x01;   // DRV2605L mode register

/* ---------- timing ---------- */
constexpr uint16_t DOT_HIT_MS      =  50;   // effect length (≈ built-in click)
constexpr uint16_t INTER_LETTER_MS = 150;   // pause after each cell
constexpr uint16_t WORD_PAUSE_MS   = 400;   // pause after “S”

/* ---------- forward declarations ---------- */
void selectBuses(uint8_t mask);
void deselectBuses();
void playStrongClick();
void vibratePattern(uint8_t pattern);
void initialiseDRV2605();

/* ---------------- SETUP ------------------ */
void setup()
{
  Wire.begin();
  Serial.begin(9600);

  /* configure every DRV2605L once */
  for (uint8_t ch = 0; ch < 6; ++ch) {
    selectBuses(1 << ch);            // open *only* this channel
    initialiseDRV2605();
  }
  deselectBuses();

  pinMode(10, OUTPUT);               // keep INT line at a defined level
  digitalWrite(10, LOW);             // (pull-down resistor is even safer)

  Serial.println(F("Braille glove ready 👍"));
}

/* ---------------- LOOP ------------------- */
void loop()
{
  if (!Serial.available()) return;

  String msg = Serial.readStringUntil('\n');
  msg.trim();

  if (msg == F("S")) {               // word separator
    delay(WORD_PAUSE_MS);
    return;
  }

  int value = msg.toInt();           // expect 0-63
  if (value < 0 || value > 63) return;

  vibratePattern(static_cast<uint8_t>(value));
}

/* ---------- Braille → haptics ------------ */
void vibratePattern(uint8_t pattern)
{
  uint8_t mask = pattern & 0x3F;     // only six dots exist
  if (mask) {
    selectBuses(mask);               // open all required channels
    playStrongClick();               // start effect on every open DRV
    delay(DOT_HIT_MS);               // let the click finish
    deselectBuses();                 // optional: close channels
  }
  delay(INTER_LETTER_MS);
}

/* ---------- fire one “strong click” ------ */
void playStrongClick()
{
  /* select effect 1 (strong click) */
  Wire.beginTransmission(DRV_ADDR);
  Wire.write(0x04);       // register: waveform sequence slot 0
  Wire.write(1);          // effect 1
  Wire.endTransmission();

  /* GO = 1 → start playback */
  Wire.beginTransmission(DRV_ADDR);
  Wire.write(0x0C);       // GO register
  Wire.write(1);
  Wire.endTransmission();
}

/* ---------- one-time DRV setup ----------- */
void initialiseDRV2605()
{
  /* standby off, internal trigger mode */
  Wire.beginTransmission(DRV_ADDR);
  Wire.write(MODE_REG);
  Wire.write(0x00);       // Internal trigger (not PWM)
  Wire.endTransmission();

  /* library selection: Library B (balanced ERM set) */
  Wire.beginTransmission(DRV_ADDR);
  Wire.write(0x03);       // library selection register
  Wire.write(0x02);       // library B
  Wire.endTransmission();

  /* max output level */
  Wire.beginTransmission(DRV_ADDR);
  Wire.write(0x17);       // rated voltage / overdrive clamp
  Wire.write(0xFF);
  Wire.endTransmission();

  delay(10);
}

/* ---------- TCA helper functions --------- */
inline void selectBuses(uint8_t mask)   // mask bits 0-7 select channels
{
  Wire.beginTransmission(TCA_ADDR);
  Wire.write(mask);
  Wire.endTransmission();
}

inline void deselectBuses() { selectBuses(0x00); }