CapTouch long/short touch detecting

Hi all,

This is my first post and first project with a particle device, so I’m quite new on this.

I’m trying to replicate/extend the code given in FamilyLamp project: Family Lamp - Github which I already have it working.

Now I would like to adapt it to my requirements. I would like to be able to detect long or short touch. I’m planning to use short touch to light all the led with white (to act like a normal lamp) and long touch to activate the connectivity feature.

I’m using the library CapTouch, and the function CapTouch::TouchEvent. I would have thought, that it would allow me to detect the second touch, but it always gives me NoTouch in the second attempt. I tried to activate the event state, but it seems the implementation of the event class is not completed.

So far I got this code (modifying the preamble of loop function of the original Familylamp), attached at the end of this post.

The idea of the code is to check if it’s touching, wait for one second, check again if it is touching. If it is still touching, it is a “long” touch, if not, it was a short one.

Any ideas or suggestions are very appreciated! Kind regards,

 
void loop() {
    // begin default
    //dayTracking();
    //touchEvent = Touch.getEvent();
    
    //if (touchEvent == CapTouch::TouchEvent) {
    //	whileTouching();
    //}
    //
    //if (Time.now() - lastColorUpdate > decayTime && lampOn == 1) {
    //    if (Time.now() - lastDecayDelay >= decayDelay) {
    //         extinguish();
    //         lastDecayDelay = Time.now();
    //     }
    // }
    //end defult
    
    
    
    // =========================================================================================================
    // being test
    
    dayTracking();
    touchEvent = Touch.getEvent();   
    
    // touched
    if (touchEvent == CapTouch::TouchEvent){ // if touching
        Particle.publish("Touching!");
        
        if (while_is_not_running==1){
            //Particle.publish("waiting!");
            delay(900);
        }
         if (while_is_not_running!=1){
            //Particle.publish("not waiting!");
        }
        
        //Touched
        
        touchEvent = Touch.getEvent();
        Particle.publish("Second touch", String(touchEvent));
        
        if (touchEvent == CapTouch::NoEvent){// we still touch it 
            Particle.publish("WhileTouching!");
             whileTouching();
             while_is_not_running = 0;
        }
        
        if (touchEvent == CapTouch::ReleaseEvent){ //we have released
            // It is a short touch, so we intend to light on/off the leds in white. 
            //get leds brightness
            Particle.publish("into led!");
            while_is_not_running = 1;
            //int led_is_on = strip.getBrightness();
            uint32_t led_is_on = strip.getPixelColor(1);
            
            Particle.publish("color of ",String((led_is_on)));
            
            //LEDs are on
            if (led_is_on != 0) {
                //turning off
                strip.clear();
                strip.show();
                Particle.publish("turning off!","data is off?");
            }
            
            //LEDs are off
            if (led_is_on == 0) {
                //turning on
                for(int i = 0; i <= strip.numPixels(); i++) {
                    strip.setPixelColor(strip.numPixels() - i, 255, 255, 255);
                }
                strip.show();
                Particle.publish("turning on!");
            }
        }
    }
    
    //resetting jumping delay variable
    if (touchEvent != CapTouch::TouchEvent){ 
        while_is_not_running = 1;
    }
    

    // To do the decay. 
    if (Time.now() - lastColorUpdate > decayTime && lampOn == 1) {
        if (Time.now() - lastDecayDelay >= decayDelay) {
            extinguish();
            lastDecayDelay = Time.now();
        }
    }
    
    // end test
    // =========================================================================================================
    
    // Special idle functions
    //... More things

}

For detecting touches long and short you may want to give @peekay123 's touch / button library a try.

I’m not sure if it’s compatible with the Cap touch output but it may be there is code in there you can apply to your project if not.

1 Like

I haven’t followed the logic of your snippet completely, but since you do seem to get a touch and a release event, wouldn’t it be easier to just record the time of the touch event and on release calculate the time the sensor was touched and distinguish from that whether it was a short or a long touch?

Having delays block the code flow for extended periods (aka more than a few milliseconds) isn’t the best practice.
This problem would lend itself to a simple FSM approach.

3 Likes

Perhaps, something like this (untested) snippet …

const uint16_t TOUCH_SHORT = 200; 			// minimum short touch duration (ms) 
const uint16_t TOUCH_LONG = 1000; 			// minimum long touch duration (ms)
const uint16_t TOUCH_TOO_LONG = 1800; 	    // maximum long touch duration (ms)
const uint8_t TOUCH_TYPE_TBD = 0, TOUCH_TYPE_SHORT = 1, TOUCH_TYPE_LONG = 2;

bool touchSensor = false;  		// true indicates that touch has been sensed
time_t touchMillis = 0;			// time when touch was first sensed
uint8_t touchType = 0;          // indicates type of touch that was detected (values = TOUCH_TYPE_ constants)
bool actionPending = false;		// true indicates taht an action is pending

void setup() {
}

void loop() {
	// TODO: insert logic to set touchSensor true or false 
	
	// Set touchType and set actionPending flag based on touch duration
	if (touchSensor) { // touch is detected
		if (touchMillis == 0) { 	// set touchMillis to current millis() when touch is first detected
			touchMillis = millis();
		} else if (millis() >= (touchMillis + TOUCH_TOO_LONG)) { // cancel action when touch is too long
			actionPending = false;
			touchType = TOUCH_TYPE_TBD;
		} else if (millis() >= (touchMillis + TOUCH_LONG)) { // convert short touch to long touch
			touchType = TOUCH_TYPE_LONG;
		} else if (millis() >= (touchMillis + TOUCH_SHORT)) { // register a short touch
			touchType = TOUCH_TYPE_SHORT;
			actionPending = true;
		}
	} else { // touchSensor == false
		touchMillis = 0;  // touches shorter than TOUCH_SHORT will be ignored.
	}
	
	// Perform appropriate action when touch ends
	
	if ((actionPending) && (touchMillis == 0)) {
		if (touchType == TOUCH_SHORT) {
			// TODO: add short action
			actionPending = false;
			touchType = TOUCH_TYPE_TBD;
		} else if (touchType == TOUCH_LONG) {
			// TODO: add long action
			actionPending = false;
			touchType = TOUCH_TYPE_TBD;
		}
	}
}		

@RWB

Thank very much for the suggestion, right now I think it is not very useful, but it might be for future project!

@ScruffR

You are right I might be over complicating the code. The thing is at beginning I was trying to execute the function whileTouching() at the same time that the touching. This was intended, because that function shows different colors until you stop to touch it. So the procedure “check how long is the touching > executing the functions” wasn’t exactly what I wanted. I hope I’m clear with my explanations.
At the end I gave up and I did it in another way. Long touch start to loop over the colors, until you touch it again and it stop in one color.
The code is attached later.

@Bear

Thank very much for your snippet, it was very useful. I used as base to re-write the code. I might upload the code to github.

Thank very much all. kind regards, German

// declarations...
//...
// code
    dayTracking();
    touchEvent2 = Touch.getEvent();
    
    if (touchEvent2 == CapTouch::TouchEvent){
        touchSensor = true;
        zero_time = millis();
        
        while (touchEvent2 != CapTouch::ReleaseEvent) {
            
            t_touching = millis();
            
            touchEvent2 = Touch.getEvent();
        }
    }
    
    
    t_touching = t_touching - zero_time;
    //Serial.printlnf("time touching %d",t_touching);
    
    if (t_touching == 0 ){
        // do nothing
        
    } else if (t_touching > TOUCH_LONG){
        
    	Serial.printlnf("long in");
    	
        subtimer = millis();
        dt = 0;
        
        Serial.printlnf("time touching %d",t_touching);
    	Serial.printlnf("while long in %d",dt);
    	
        while (dt < t_touching){
            touchEvent = CapTouch::TouchEvent;
            whileTouching();
            
            dt = millis() - subtimer;
    	    Serial.printlnf("while long in %d",dt);
        }
        touchEvent = CapTouch::ReleaseEvent;
        whileTouching(); 
        lampOn =1;
        
    } else if (t_touching> TOUCH_SHORT) {
        uint32_t led_is_on = strip.getPixelColor(1);
    	Serial.printlnf("short in");
        
        if ((led_is_on != 0)) {
            //turning off
            strip.clear();
            strip.show();
            Serial.printlnf("turning lamp off!");
        }
        
        //LEDs are off
        if ((led_is_on == 0) ) {
            //turning on
            for(int i = 0; i <= strip.numPixels(); i++) {
                strip.setPixelColor(strip.numPixels() - i, 255, 255, 255);
            }
            strip.show();
            Serial.printlnf("turning lamp on!");
        }
    } 
    
    t_touching = 0;
    zero_time = 0;
    subtimer = 0;
    dt = 0;
    
    // To do the decay.     
    if ((Time.now() - lastColorUpdate > decayTime) && (lampOn == 1)) {
        if (Time.now() - lastDecayDelay >= decayDelay) {
            
            Serial.printlnf("in decay %d",(Time.now() - lastDecayDelay ));
            extinguish();
            lastDecayDelay = Time.now();
            //lampOn = 0;
        }
    }
    

If I picture your intent correctly I’d still not see any reason for not going with my proposal.
If you have a touch and a release event then everything between the two can be considered holding. So a whileTouching logic would easily be implemented in a non blocking fashion just the same.

Your project gave me an idea for a project. Years ago, I could send and receive Morse code at a decent clip, and it would be fun to get that skill back. A fun Particle project would be to create firmware that could convert dots and dashes into characters, and vice versa. I could then have the program send me the contents of a Google doc and I could key it back and check accuracy both directions. Anyway, I wrote the snippet to capture the moment. I’m glad you found it useful. Perhaps … someday … I’ll find out if sending/receiving Morse code is like riding a bicycle.

Dear all,

I upload the code as a fork of the original project. Feel free to suggest improvements, use it or copy it.

Link: https://github.com/germa89/familamp

Kind regards,

Hi @ScruffR

I’m afraid I don’t understand what you mean with “a non blocking fashion”. Could you share a small snippet (don’t need to be related to this project)? for me to understand easier. Sorry, English is not my first language and i didn’t study computer science or similar, that’s maybe why I have problems to understand you .

@Bear
I’m glad it brought you memories. Hopefully you will find the time!

Kind regards all,

Just for illustrational purposes

void loop() {
  static uint32_t msLastPress     = 0;
  uint32_t        msPressDuration = 0;
  static uint8_t  touchTypePrev   = 0;
  uint8_t         touchTypeNew    = Touch.getEvent();

  if (touchTypeNew == touchTypePrev) return; // don't do anything when no change

  switch(touchTypeNew) {
    case CapTouch::TouchEvent:
      msLastPress = millis();
      break;
    case CapTouch::ReleaseEvent:
      if (msLastPress)
        msPressDuration = millis() - msLastPress;
      msLastPress = 0;
      break;
    default:
      break;
  }

  if (10 < msPressDuration && msPressDuration <= 250)
    doShortPressStuff(); // no longish delays allowed in here
  else if (250 < msPressDuration && msPressDuration <= 1000)
    doLongPressStuff(); // no longish delays allowed here either
  else
    treatTooLongOrTooShort();

  touchTypePrev = touchTypeNew;
}

The command “delay(5000)” command prevents the processor for doing ANYTHING for 5 full seconds. Since IOT devices only have a single processor, a “blocking” command like that can lead to a variety of problems.

A non-blocking alternative would be to:

  • compute “resumeTime” by adding 5000 to millis().
  • trigger action when millis() >= “resumeTime”.

A timer can also be used to trigger action in 5000ms … or to trigger action every 5000ms when the timer is running (between the timer.start() and timer.stop() commands).

Hey @grmn ,

I’m the original author of the Familamp code. You should be able to almost use the code as-is, with the addition of a counter that runs inside while (touchEvent != CapTouch::ReleaseEvent) {. The code inside that while loop continues indefinitely until the CapTouch library returns a release from the call to it at the end of the loop, touchEvent = Touch.getEvent();. The speed of the while loop varies based on the humidity and resistor used, but it loops quite a few times a second. The “sparkle” function (where other lamps begin to sparkle when you put your hand on the lamp) already does something similar: every 3 seconds, it sends a Particle.publish(). You could use that code as a template and do something similar if the loops occur for more than, say, 5 seconds.

It looks like you’re already making progress on your improvements by ripping CapTouch out, so if you can get it working that way, great. Getting a reasonable response from the panel was one of the more challenging aspects of the project and I wasn’t talented enough to write something from scratch, so more power to you. :slight_smile:

1 Like