Long distance touch light - code help

Hi,
Before I start, please forgive my lack of knowledge - I have been trying to dig up the coding I learnt at uni many years ago but even then I found it confusing! I am trying though.

I have decided to follow this tutorial I found online to make some Global Synchronised touch lamps. Here.

I have managed to get 2 lights set up and synchronised and I ‘think’ doing what they are supposed to be doing. The problem is it’s not exactly what I was hoping for…
What I am ideally wanting is for each light to have it’s own colour. So for example, whenever I touch my light - all lights in the system turn green. Then if my mum touches her light all lights turn purple, and if I add others they will have their own colour. Once I have pressed my lamp and my colour is showing I can’t change it until another colour appears.

I also don’t really want there to be a different result depending on how long the lamp is touched for (which I ‘think’ is part of the code).

Is anyone able to offer advice on what changes I can make to the code to enable this to happen - or indeed point me in the direction of which bits of code I should be looking at!

The full code is shown on this thread here incase my attempts to paste it below fail miserably.

Thank you

/*
 * Project: touch_light
 * Description: A touch light that syncs with other touch lights. Adapted from
 *              http://www.instructables.com/id/Networked-RGB-Wi-Fi-Decorative-Touch-Lights/
 * Author: Patrick Blesi
 * Date: 2017-12-09
 *
 */

#include "neopixel.h"
#include "application.h"

#include "wifi_creds.h"

// CONFIGURATION SETTINGS START
// DEBUG SETTINGS:
#define D_SERIAL false
#define D_WIFI false

String touchEventName = "touch_event";

#define NUM_PARTICLES 2 // number of touch lights in your group
// Number each Filimin starting at 1.
String particleId[] = {
  "",                         // 0
  "44002a000947373334323234", // Mum
  "2d0047001247353236343034", // Me

};

int particleColors[] = {
  0,   // Green
  90,  // Magenta
  170, // Blue
  79,  // Orange
  131  // Purple
};

// TWEAKABLE VALUES FOR CAP SENSING. THE BELOW VALUES WORK WELL AS A STARTING PLACE:
// BASELINE_VARIANCE: The higher the number the less the baseline is affected by
// current readings. (was 4)
#define BASELINE_VARIANCE 512.0
// SENSITIVITY: Integer. Higher is more sensitive (was 8)
#define SENSITIVITY 8
// BASELINE_SENSITIVITY: Integer. A trigger point such that values exceeding this point
// will not affect the baseline. Higher values make the trigger point sooner. (was 16)
#define BASELINE_SENSITIVITY 16
// SAMPLE_SIZE: Number of samples to take for one reading. Higher is more accurate
// but large values cause some latency.(was 32)
#define SAMPLE_SIZE 512
#define SAMPLES_BETWEEN_PIXEL_UPDATES 32
#define LOOPS_TO_FINAL_COLOR 150

const int minMaxColorDiffs[2][2] = {
  {5,20},   // min/Max if color change last color change from same touch light
  {50,128}  // min/Max if color change last color change from different touch light
};

// END VALUE, TIME
// 160 is approximately 1 second
const long envelopes[6][2] = {
  {0, 0},      // NOT USED
  {255, 30},   // ATTACK
  {200, 240},  // DECAY
  {200, 1000}, // SUSTAIN
  {150, 60},   // RELEASE1
  {0, 1000000} // RELEASE2 (65535 is about 6'45")
};

#define PERIODIC_UPDATE_TIME 5 // seconds
#define COLOR_CHANGE_WINDOW 10 // seconds

// CONFIGURATION SETTINGS END

// STATES:
#define ATTACK 1
#define DECAY 2
#define SUSTAIN 3
#define RELEASE1 4
#define RELEASE2 5
#define OFF 6

#define LOCAL_CHANGE 0
#define REMOTE_CHANGE 1

#define END_VALUE 0
#define TIME 1

#define tEVENT_NONE 0
#define tEVENT_TOUCH 1
#define tEVENT_RELEASE 2

String eventTypes[] = {
  "None",
  "Touch",
  "Release"
};

int sPin = D4;
int rPin = D3;

// NEOPIXEL
#define PIXEL_PIN D2
#define PIXEL_COUNT 24
#define PIXEL_TYPE WS2812B

// STATE
unsigned char myId = 0;

int currentEvent = tEVENT_NONE;
int eventTime = Time.now();
int eventTimePrecision = random(INT_MAX);

int initColor = 0;
int currentColor = 0; // 0 to 255
int finalColor = 0;   // 0 to 255
int lastLocalColorChangeTime = Time.now();

int initBrightness = 0;    // 0 to 255
int currentBrightness = 0; // 0 to 255

unsigned char prevState = OFF;
unsigned char state = OFF;

unsigned char lastColorChangeDeviceId = 1;

long loopCount = 0;
long colorLoopCount = 0;
int lastPeriodicUpdate = Time.now();

// timestamps
unsigned long tS;
volatile unsigned long tR;

// reading and baseline
float tBaseline;

double tDelayExternal = 0;
double tBaselineExternal = 0;

Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

#ifdef WIFI_CREDENTIALS_SPECIFIED
SYSTEM_MODE(SEMI_AUTOMATIC);
#endif

void setup()
{
#ifdef WIFI_CREDENTIALS_SPECIFIED
  setupWifi();
#endif

  Particle.subscribe(touchEventName, handleTouchEvent, MY_DEVICES);

  if (D_SERIAL) Serial.begin(9600);
  if (D_WIFI) {
    Particle.variable("tDelay", &tDelayExternal, DOUBLE);
    Particle.variable("tBaseline", &tBaselineExternal, DOUBLE);
  }

  strip.begin();
  strip.show(); // Initialize all pixels to 'off'

  pinMode(sPin, OUTPUT);
  attachInterrupt(rPin, touchSense, RISING);

  myId = getMyId(particleId, NUM_PARTICLES);

  flashWhite(&strip);

  // Calibrate touch sensor- Keep hands off!!!
  tBaseline = touchSampling(); // initialize to first reading
  if (D_WIFI) tBaselineExternal = tBaseline;

  traverseColorWheel(&strip);
  fade(&strip);
}

void loop() {
  int touchEvent = touchEventCheck();

  if (touchEvent == tEVENT_NONE) {
    // Publish periodic updates to synchronize state
    bool touchedBefore = currentEvent != tEVENT_NONE;
    if (lastPeriodicUpdate < Time.now() - PERIODIC_UPDATE_TIME && touchedBefore) {
      publishTouchEvent(currentEvent, finalColor, eventTime, eventTimePrecision);
      lastPeriodicUpdate = Time.now();
    }
    return;
  }

  // Random eventTimePrecision prevents ties with other
  // server events. This allows us to determine dominant
  // color in the event of ties.
  setEvent(touchEvent, Time.now(), random(INT_MAX));

  if (D_SERIAL) Serial.println(eventTypes[touchEvent]);
  if (touchEvent == tEVENT_TOUCH) {
    int newColor = generateColor(finalColor, prevState, lastColorChangeDeviceId);
    setColor(newColor, prevState, myId);
    changeState(ATTACK, LOCAL_CHANGE);
  }
}

//============================================================
//	Setup functions
//============================================================
//------------------------------------------------------------
// Functions used during setup
//------------------------------------------------------------
void setupWifi() {
  WiFi.on();
  WiFi.disconnect();
  WiFi.clearCredentials();
  int numWifiCreds = sizeof(wifiCreds) / sizeof(*wifiCreds);
  for (int i = 0; i < numWifiCreds; i++) {
    credentials creds = wifiCreds[i];
    WiFi.setCredentials(creds.ssid, creds.password, creds.authType, creds.cipher);
  }
  WiFi.connect();
  waitUntil(WiFi.ready);
  Particle.connect();
}

int getMyId(String particleId[], int numParticles) {
  int id = 0;
  for (int i = 1; i <= numParticles; i++) {
    if (!particleId[i].compareTo(Particle.deviceID())) {
      id = i;
      break;
    }
  }
  return id;
}

void flashWhite(Adafruit_NeoPixel* strip) {
  int numPixels = strip->numPixels();
  for (byte j = 0; j < numPixels; j++) {
    strip->setPixelColor(j, 255, 255, 255);
  }
  strip->show();
  delay(250);
  for (byte j = 0; j < numPixels; j++) {
    strip->setPixelColor(j, 0, 0, 0);
  }
  strip->show();
  delay(250);
}

void traverseColorWheel(Adafruit_NeoPixel* strip) {
  int numPixels = strip->numPixels();
  for (int i = 0; i < 256; i++) {
    uint32_t color = wheelColor(i, 255);
    for (byte j = 0; j < numPixels; j++) {
      strip->setPixelColor(j, color);
      strip->show();
    }
    delay(1);
  }
}

void fade(Adafruit_NeoPixel* strip) {
  int numPixels = strip->numPixels();
  for (int j = 255; j >= 0; j--) {
    uint32_t color = wheelColor(255, j);
    for (byte k = 0; k < numPixels; k++) {
      strip->setPixelColor(k, color);
      strip->show();
    }
    delay(1);
  }
}

//------------------------------------------------------------
// touch sampling
//
// sample touch sensor SAMPLE_SIZE times and get average RC delay [usec]
//------------------------------------------------------------
long touchSampling() {
  long tDelay = 0;
  int mSample = 0;

  for (int i = 0; i < SAMPLE_SIZE; i++) {
    if (!(i % SAMPLES_BETWEEN_PIXEL_UPDATES)) {
      updateState();
    }
    pinMode(rPin, OUTPUT); // discharge capacitance at rPin
    digitalWrite(sPin,LOW);
    digitalWrite(rPin,LOW);
    pinMode(rPin,INPUT); // revert to high impedance input
    // timestamp & transition sPin to HIGH and wait for interrupt in a read loop
    tS = micros();
    tR = tS;
    digitalWrite(sPin,HIGH);
    do {
      // wait for transition
    } while (digitalRead(rPin)==LOW);

    // accumulate the RC delay samples
    // ignore readings when micros() overflows
    if (tR > tS) {
      tDelay = tDelay + (tR - tS);
      mSample++;
    }
  }

  // calculate average RC delay [usec]
  if ((tDelay > 0) && (mSample > 0)) {
    tDelay = tDelay/mSample;
  } else {
    tDelay = 0;     // this is an error condition!
  }
  if (D_SERIAL) Serial.println(tDelay);
  if (D_WIFI) tDelayExternal = tDelay;
  // autocalibration using exponential moving average on data below specified point
  if (tDelay < (tBaseline + tBaseline/BASELINE_SENSITIVITY)) {
    tBaseline = tBaseline + (tDelay - tBaseline)/BASELINE_VARIANCE;
    if (D_WIFI) tBaselineExternal = tBaseline;
  }
  return tDelay;
}

//============================================================
//	Touch UI
//============================================================
//------------------------------------------------------------
// ISR for touch sensing
//------------------------------------------------------------
void touchSense() {
  tR = micros();
}

//------------------------------------------------------------
// touch event check
//
// check touch sensor for events:
//      tEVENT_NONE     no change
//      tEVENT_TOUCH    sensor is touched (Low to High)
//      tEVENT_RELEASE  sensor is released (High to Low)
//
//------------------------------------------------------------
int touchEventCheck() {
  int touchSense;                     // current reading
  static int touchSenseLast = LOW;    // last reading

  static unsigned long touchDebounceTimeLast = 0; // debounce timer
  int touchDebounceTime = 50;                     // debounce time

  static int touchNow = LOW;  // current debounced state
  static int touchLast = LOW; // last debounced state

  int tEvent = tEVENT_NONE;   // default event

  // read touch sensor
  long tReading = touchSampling();

  // touch sensor is HIGH if trigger point some threshold above Baseline
  if (tReading > (tBaseline + tBaseline / SENSITIVITY)) {
    touchSense = HIGH;
  } else {
    touchSense = LOW;
  }

  // debounce touch sensor
  // if state changed then reset debounce timer
  if (touchSense != touchSenseLast) {
    touchDebounceTimeLast = millis();
  }
  touchSenseLast = touchSense;

  // accept as a stable sensor reading if the debounce time is exceeded without reset
  if (millis() > touchDebounceTimeLast + touchDebounceTime) {
    touchNow = touchSense;
  }

  // set events based on transitions between readings
  if (!touchLast && touchNow) {
    tEvent = tEVENT_TOUCH;
  }

  if (touchLast && !touchNow) {
    tEvent = tEVENT_RELEASE;
  }

  // update last reading
  touchLast = touchNow;
  return tEvent;
}

void setEvent(int event, int timeOfEvent, int timePrecision) {
  currentEvent = event;
  eventTime = timeOfEvent;
  eventTimePrecision = timePrecision;
}

void handleTouchEvent(const char *event, const char *data) {
  String eventData = String(data);
  int deviceIdEnd = eventData.indexOf(',');
  int deviceId = eventData.substring(0, deviceIdEnd).toInt();
  int eventEnd = eventData.indexOf(',', deviceIdEnd + 1);
  int serverEvent = eventData.substring(deviceIdEnd + 1, eventEnd).toInt();
  int colorEnd = eventData.indexOf(',', eventEnd + 1);
  int serverColor = eventData.substring(eventEnd + 1, colorEnd).toInt();
  int eventTimeEnd = eventData.indexOf(',', colorEnd + 1);
  int serverEventTime = eventData.substring(colorEnd + 1, eventTimeEnd).toInt();
  int serverEventTimePrecision = eventData.substring(eventTimeEnd + 1).toInt();

  if (false) {
    String response = "deviceId: " + String(deviceId) + " " +
                      "serverEvent: " + String(serverEvent) + " " +
                      "serverColor: " + String(serverColor) + " " +
                      "localEventTime: " + String(eventTime) + " " +
                      "serverEventTime: " + String(serverEventTime) + " " +
                      "serverEventTimePrecision: " + String(serverEventTimePrecision);
    Particle.publish("touch_response", response, 61, PRIVATE);
  }

  if (deviceId == myId) return;
  if (serverEventTime < eventTime) return;
  // Race condition brought colors out of sync
  if (
    serverEventTime == eventTime &&
    serverEventTimePrecision == eventTimePrecision &&
    serverColor != finalColor &&
    myId < deviceId
  ) {
    setColor(serverColor, prevState, deviceId);
    changeState(ATTACK, REMOTE_CHANGE);
    return;
  }
  if (serverEventTime == eventTime && serverEventTimePrecision <= eventTimePrecision) return;

  // Valid remote update
  setEvent(serverEvent, serverEventTime, serverEventTimePrecision);

  if (serverEvent == tEVENT_TOUCH) {
    setColor(serverColor, prevState, deviceId);
    changeState(ATTACK, REMOTE_CHANGE);
  } else {
    changeState(RELEASE1, REMOTE_CHANGE);
  }
}

void setColor(int color, unsigned char prevState, unsigned char deviceId) {
  lastColorChangeDeviceId = deviceId;
  if (prevState == OFF) currentColor = color;
  initColor = currentColor;
  finalColor = color;
  colorLoopCount = 0;
  if (D_SERIAL) {
    Serial.print("get Color From Server Final color: ");
    Serial.print(initColor);
    Serial.print(", ");
    Serial.print(finalColor);
    Serial.print(", ");
  }
}

int generateColor(int currentFinalColor, unsigned char prevState, int lastColorChangeDeviceId) {
  int color = 0;
  int now = Time.now();
  Serial.println("generating color...");
  if (prevState == OFF || lastLocalColorChangeTime < now - COLOR_CHANGE_WINDOW) {
    color = particleColors[myId];
  } else {
    bool foreignId = (lastColorChangeDeviceId != myId);
    int minChange = minMaxColorDiffs[foreignId][0];
    int maxChange = minMaxColorDiffs[foreignId][1];
    int direction = random(2) * 2 - 1;
    int magnitude = random(minChange, maxChange + 1);
    color = currentFinalColor + direction * magnitude;
    color = (color + 256) % 256;
    // color = 119; // FORCE A COLOR
  }
  lastLocalColorChangeTime = now;
  if (D_SERIAL) { Serial.print("final color: "); Serial.println(finalColor); }
  return color;
}

void changeState(unsigned char newState, int remoteChange) {
  prevState = state;
  state = newState;
  initBrightness = currentBrightness;
  loopCount = 0;
  if (D_SERIAL) { Serial.print("state: "); Serial.println(newState); }

  if (remoteChange) return;

  if (newState == ATTACK || newState == RELEASE1) {
    publishTouchEvent(currentEvent, finalColor, eventTime, eventTimePrecision);
  }
}

void publishTouchEvent(int event, int color, int time, int timePrecision) {
  String response = String(myId)  + "," +
                    String(event) + "," +
                    String(color) + "," +
                    String(time)  + "," +
                    String(timePrecision);
  Particle.publish(touchEventName, response, 60, PRIVATE);
}

void updateState() {
  switch (state) {
    case ATTACK:
      if (loopCount >= envelopes[ATTACK][TIME]) {
        changeState(DECAY, LOCAL_CHANGE);
      }
      break;
    case DECAY:
      if ((loopCount >= envelopes[DECAY][TIME]) || (currentEvent == tEVENT_RELEASE)) {
        changeState(SUSTAIN, LOCAL_CHANGE);
      }
      break;
    case SUSTAIN:
      if ((loopCount >= envelopes[SUSTAIN][TIME]) || (currentEvent == tEVENT_RELEASE)) {
        changeState(RELEASE1, LOCAL_CHANGE);
      }
      break;
    case RELEASE1:
      if (loopCount >= envelopes[RELEASE1][TIME]) {
        changeState(RELEASE2, LOCAL_CHANGE);
      }
      break;
    case RELEASE2:
      if (loopCount >= envelopes[RELEASE2][TIME]) {
        changeState(OFF, LOCAL_CHANGE);
      }
      break;
  }

  currentBrightness = getCurrentBrightness(state, initBrightness, loopCount);
  if (currentColor != finalColor) {
    currentColor = getCurrentColor(finalColor, initColor, colorLoopCount);
  }

  uint32_t colorAndBrightness = wheelColor(currentColor, currentBrightness);
  updateNeoPixels(colorAndBrightness);
  loopCount++;
  colorLoopCount++;
}

int getCurrentBrightness(unsigned char state, int initBrightness, int loopCount) {
  if (state == OFF) return 0;
  int brightnessDistance = envelopes[state][END_VALUE] - initBrightness;
  int brightnessDistanceXElapsedTime = brightnessDistance * loopCount / envelopes[state][TIME];
  return min(255, max(0, initBrightness + brightnessDistanceXElapsedTime));
}

int getCurrentColor(int finalColor, int initColor, int colorLoopCount) {
  if (colorLoopCount > LOOPS_TO_FINAL_COLOR) return finalColor;
  int colorDistance = calcColorChange(initColor, finalColor);
  int colorDistanceXElapsedTime = colorDistance * colorLoopCount / LOOPS_TO_FINAL_COLOR;
  return (256 + initColor + colorDistanceXElapsedTime) % 256;
}

int calcColorChange(int currentColor, int finalColor) {
  int colorChange = finalColor - currentColor;
  int direction = (colorChange < 0) * 2 - 1;
  colorChange += direction * (abs(colorChange) > 127) * 256;
  return colorChange;
}

void updateNeoPixels(uint32_t color) {
  for(char i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, color);
  }
  strip.show();
}

//============================================================
//	NEOPIXEL
//============================================================
//------------------------------------------------------------
// Wheel
//------------------------------------------------------------

uint32_t wheelColor(byte WheelPos, byte iBrightness) {
  float R, G, B;
  float brightness = iBrightness / 255.0;

  if (WheelPos < 85) {
    R = WheelPos * 3;
    G = 255 - WheelPos * 3;
    B = 0;
  } else if (WheelPos < 170) {
    WheelPos -= 85;
    R = 255 - WheelPos * 3;
    G = 0;
    B = WheelPos * 3;
  } else {
    WheelPos -= 170;
    R = 0;
    G = WheelPos * 3;
    B = 255 - WheelPos * 3;
  }
  R = R * brightness + .5;
  G = G * brightness + .5;
  B = B * brightness + .5;
  return strip.Color((byte) R,(byte) G,(byte) B);
}

Hi there - welcome to the community :wave:

The original project has some rather elaborate features that “merely” serve aesthetics (colour transitions, fade, …) which require a lot of code.
If I understand your request correctly, you don’t need any of the fancy stuff and hence you could strip ~80% of that code.

Consequently I’d deem it easier to start from scratch with a project that only does what you want than weeding through that code chuck out what’s not needed anymore.

This would also have the positive side effect that you actually learn as you go.
Learning from an over-refined 3rd party code may prove more confusing than helpful.

But don’t fret - this community is very willing to help on your journey.

The best starting points are probably

That’s all you should need to get you started.

This was a project I once did for another community member that also illustrates how to pub/sub between devices
https://go.particle.io/shared_apps/5a5ce5f960ddd1859f0008a6

1 Like

Thank you! (I think!!)

I’ll give it a go and you can correct it later! :joy:

Cheers

1 Like

Well I’ve remembered how much this all confuses me (18 years since uni had numbed it slightly). Relearning how to code from scratch is unfortunately not feasible right now so I’m going to have to use the code I have as much as possible and hopefully not have to write too much new stuff.
So, back to basics and thinking outloud here…

Essentially I need my code to…

  1. Connect lamps to the wifi (can take from current code)
  2. Test lights are working and settle on an initial colour (can take from current code)
  3. When lamp #1 is pressed all other lights turn green (using pub/sub)
  4. When lamp #2 is pressed all lights turn purple (using pub/sub)
  5. (more lamps to possibly be added later)

I’m sure I’ve missed out many more details I need to consider but I’ll build up to that!

Maybe this can give you a head start

#include <neopixel.h>
const int PIXEL_COUNT = 1;
const int PIXEL_PIN   = D2;
const int PIXEL_TYPE  = WS2812B;

const char eventName[] = "setLamp";

struct LampData {
  char name[16];
  char id[26];
  uint8_t R;
  uint8_t G;
  uint8_t B;
};

LampData ld[] = 
{ { "default", "000000000000000000000000", 0xC0, 0xC0, 0xC0 }  
, { "red"    , "40003d000347353138383138", 0xFF, 0x00, 0x00 }
, { "green"  , "26002e000447343232363230", 0x00, 0xFF, 0x00 }
, { "blue"   , "280033000d47343432313031", 0x00, 0x00, 0xFF }
};
const int lamps = sizeof(ld) / sizeof(ld[0]);

uint32_t msDelay = 50;

void setup() {
  Particle.subscribe(eventName, setLampHandler, MY_DEVICES);    
  strip.begin();
  for(int i=0; i < strip.numPixels(); i++)                      // set all NeoPixels to
    strip.setPixelColor(i, ld[0].R, ld[0].G, ld[0].B);          // default colour
  strip.show();                                                 
}

void loop() {
  static uint32_t ms = millis();
  if (millis() - ms < msDelay) return;                          // bail out if delay time hasn't expired yet
  ms = millis();
  
  if (!digitalRead(BTN)) {                                      // check if SETUP button was pressed
    msDelay = 50;                                               // button debounce period
    if (Particle.connected()) {                                 // if device is connected         
      Particle.publish(eventName, System.deviceID(), PRIVATE);  // send the event to all devices
      msDelay = 1000;                                           // ensure only one publish per second
    }
  }
  else
    msDelay = 0;                                                // nothing happened so no need to delay
}

void setLampHandler(const char* event, const char* payload) {
  int idx = getIndex(payload);                                  // who sent the event?
  if (idx <= 0) {
    idx = 0;                                                    // if device not listed use default colour
    RGB.control(false);
  }
  else 
    RGB.control(true);                                          // take over RGB LED 
  RGB.color(ld[idx].R, ld[idx].G, ld[idx].B);                   // set colour
  for(int i=0; i < strip.numPixels(); i++)                      // set all NeoPixels desired colour
    strip.setPixelColor(i, ld[idx].R, ld[idx].G, ld[idx].B);

  strip.show();                                                 // update strip to set colour
}

int getIndex(const char* id) {                                  // traverse the list to find the device ID
  for (int i = 0; i < lamps; i++) {
    Log.trace("%d: %s ==? %s", i, id, ld[i].id);
    if (!strcmp(id, ld[i].id)) return i;
  }
  return -1;
}
3 Likes