'Peek-a-boo' Behaviour
Threshold-Triggered Action
One of the ways you can play 'peek-a-boo' with RoboBrrd, is by covering up the light sensors, and then uncovering them. What happens here is a change from light to dark, then dark to light. The change from dark to light in both sensors is what we are looking for here.
In order to do this, we will need to create a threshold for when we can say that RoboBrrd senses darkness. The current LDR values get compared to this threshold and a state is set. By comparing the current state with the state of the previous iteration, we can detect the change from dark to light.
Variables
uint8_t ldr_add = 20;
uint16_t ldrL_thresh = ldrL_low+ldr_add;
uint16_t ldrR_thresh = ldrR_low+ldr_add;
Here we are setting the threshold values. The thresholds are set to be a small amount above the lowest LDR reading. This small amount is controlled by the
ldr_add variable.
When you set
ldr_add close to 0, then it will become trickier for it to be triggered. Increasing it by a large amount will make it easier to trigger- so much easier that it might be triggered all the time. Sometimes this variable does require tinkering to get just right.
Of course, it all depends on your sensors. If you want, you can also set the thresholds to some arbitrary value.
These variable declarations go at the top of your sketch, along with the other variable declarations.
Loop
ldrL_val = map(getLDR_L(), ldrL_low, ldrL_high, 0, 1023);
ldrR_val = map(getLDR_R(), ldrR_low, ldrR_high, 0, 1023);
if(ldrL_val <= ldrL_thresh && ldrR_val <= ldrR_thresh) {
Serial << "dark" << endl;
} else if(ldrL_val <= ldrL_thresh && ldrR_val > ldrR_thresh) {
Serial << "left" << endl;
} else if(ldrL_val > ldrL_thresh && ldrR_val <= ldrR_thresh) {
Serial << "right" << endl;
} else {
Serial << "bright!" << endl;
}
Now it is time to get the LDR values. As we are getting the value, we are also mapping it from the calibrated low and high values to 0-1023. This will make it easier in the next lines of code to compare the value to the threshold.
For comparing the value to the threshold, we need to do it for both sides. This means there are four cases to look at, two where both the left and right are either less/greater than the threshold, and two where the left and right differ from being less/greater than the threshold.
As you can see, we are printing out the phrase to the Serial Monitor. Now would be a good time to upload this code and test it out with your RoboBrrd. This way, you'll be able to see if the threshold is set properly, and make any adjustments. For example, if it is always sending 'bright', then your threshold is too low, and you need to add more to it. If it is always sending 'dark/left/right', then your threshold is too high, and you need to subtract some from it. The thresholds may differ from each side.
Variables
uint8_t peekState = 3;
boolean covered = false;
Time to add some more variables. These two are for the peek state, one is the current state and the other is the previous state.
Loop
if(ldrL_val <= ldrL_thresh && ldrR_val <= ldrR_thresh) {
peekState = 0;
} else if(ldrL_val <= ldrL_thresh && ldrR_val > ldrR_thresh) {
peekState = 1;
} else if(ldrL_val > ldrL_thresh && ldrR_val <= ldrR_thresh) {
peekState = 2;
} else {
peekState = 3;
}
if(peekState == 3 && covered == true) {
Serial << "PEEK A BOO!" << endl;
covered = false;
} else if(peekState == 0) {
Serial << "BOO!" << endl;
covered = true;
}
The first few lines of code are the same as the ones we had previously, but we removed the Serial prints and added setting the peekState variable.
Whenever we see the
peekState as being 0, then we set
covered to be true. Then, once the LDRs are uncovered,
peekState is 3 AND
covered is true, then it is a legitimate 'peek a boo', and
covered is set back to false.
One of the flaws of this approach is that if one of the LDRs is uncovered, followed by the other being uncovered, it would still be classified as a 'peek-a-boo'. This flaw can be fixed by checking if
peekState is 1 or 2, and setting
covered to false.
This is pretty boring just printing out text to the screen. Let's add some animation!
Animation
if(peekState == 3 && covered == true) {
Serial << "PEEK A BOO!" << endl;
beak.attach(bpin);
lwing.write(l_upper);
rwing.write(r_upper);
beakOpen(250);
eyes(100,255,0);
delay(100);
lwing.write(l_middle);
rwing.write(r_middle);
beakClose(250);
eyes(ledVals[0], ledVals[1], ledVals[2]);
beak.detach();
covered = false;
} else if(peekState == 0) {
Serial << "BOO!" << endl;
//eyes(50,50,50);
beak.attach(bpin);
lwing.write(l_lower);
rwing.write(r_lower);
beakClose(250);
beak.detach();
covered = true;
}
Here is the same if statement as above, but now with all the calls to animate RoboBrrd's movements. We tell RoboBrrd to basically look excited when
peekState is 3, as a nice treat to the human who has been playing along. Since RoboBrrd doesn't enjoy its eyes being covered, it acts 'scared' whenever
peekState is 0.
You might notice when
peekState is 0, that we commented out an eye update. This was because it accidentally triggered
peekState to be 3. This is most likely caused by the LDRs being in close proximity of the LEDs. So, we just leave RoboBrrd's eyes as the same colour for now.
You can customize the animations as much as you want. Just remember that the more times you command a servo to move, you should delay for the servo to move to its end position. With too many delays, a lag time will become evident when playing with RoboBrrd. It's all a balance between the animation and lag time. ;]
Testing it out
When you are done modifying the code, upload it to your RoboBrrd, open the Serial Monitor, and let's try it out! With your hands not covering RoboBrrd, it should not be printing anything out by default. If you cover one of the LDRs, it should print 'BOO!'. When you uncover the LDR, then cover both of them up at the same time, then it should print out the phrase, 'PEEK A BOO!'.
By changing the threshold value, there will be slight differences in the way the behaviour works. Another thing that you can add is to compare the current peekState to a previous one. An example of using this in action could be with a 'pattern game', where you would have to cover/uncover the LDRs in a specific pattern. Almost like a secret door-knock, but with LDRs.
Function
Similar to what we did with the calibrate code, copy all of this code from loop(). Put it into a function named peekabooBehaviour(). Be sure to save your sketch, and delete the code from loop now.
---
Ready for the next behaviour? Let's go!
'Puppeteer' Behaviour
Direct Control Action
You can actually puppeteer RoboBrrd's wings using the light sensors. What is happening is that as we change the light level, then it will map the reading to the wing's upper and lower values. We can just write the reading to the servo, and it will move. It's quite straightforward, but makes for a nice trick.
One of the extra things that we will do, so that we can also control the beak, is by checking to see if the current value will be within a threshold range. This threshold range is defined by the lower servo values, plus or minus some constant number. The constant number serves as a way to increase the triggering range. It's similar to what we did in the previous peek a boo behaviour.
Variables
boolean beakState = false;
Only one variable this time- a boolean for the beak state. It will only ever be set to open or closed in this example.
Setup
lwing.attach(lpin);
rwing.attach(rpin);
We need to make sure that we attach the wing servos, so add this to the end of your setup code.
Loop
ldrL_val = map(getLDR_L(), ldrL_low, ldrL_high, l_lower, l_upper);
ldrR_val = map(getLDR_R(), ldrR_low, ldrR_high, r_lower, r_upper);
lwing.write(ldrL_val);
rwing.write(ldrR_val);
if(ldrL_val <= ldrL_thresh && ldrR_val <= ldrR_thresh) {
beak.attach(bpin);
if(beakState) {
beakOpen(250);
} else {
beakClose(250);
}
beak.detach();
beakState = !beakState;
}
As always, we start out by retrieving the current value of the LDRs. You might notice something different this time, that the value is being mapped to the lower and upper servo values. This way we can write it directly to the servo.
The next block of code is where we are checking to see if both wings are in some area of being down (or lowered). If they are, then we attach the beak servo, open/close it, detach the servo, and change the beak state. The next iteration of the loop, if the beak was open- then now it will be closed (or vice versa).
Testing it out
Upload the code to your RoboBrrd, and try it out. It should be a pretty fun effect, where by using your hands to bring shade on the light sensors, you get to control the wing servos.
If the movement seems jittery to you, one of the modifications you can make is by adding a delay(100); to the end of the loop section. It will slow down the delay time a little, but also not make the servos as crazy.
There are some better ways other than just adding a delay that we can use to smooth the light readings. Can you think of some? We will be diving into this in part 2 of the tutorial.
Function
Take the code from loop, and add it to a function named puppeteerBehaviour(). Be sure to save your sketch!
Summary
Now you should have two working behaviours for RoboBrrd using its light dependent resistors. Plus, a routine to follow to calibrate the light sensor values in case you are using RoboBrrd in a different light scene. From here, you can go on to make more interesting behaviours with the photocells! Possibly combining the two in an interesting way... or something!
The lighting in the room can have a substantial effect on whether or not the behaviours work. It is obviously hard to make it work in a completely pitch dark room, but even when testing with a dimly-lit room, if we moved a few cm, then we would have to re-calibrate the LDRs. It works well when there is a decent amount of ambient light in the room. If your lower/upper values do change, it will effect the mapping of the ldrValue. If you ever see a larger number than 1023, that is probably what happened.
So, what did we learn here?
- How the light sensors behave in light and darkness
- Making RoboBrrd do different actions based off of a threshold
- Mapping the light values to move RoboBrrd's wing
- What else did you learn?
What remaining questions do we have?
- How to make the movement less jittery?
- Can we combine the two behaviours in some way?
- How do we control sounds from the light sensors?
- What other questions do you have?
If you are stuck on this tutorial, feel free to ask any questions on the forum.
Next: Photocell Behaviours Part 2
Now that we have the basics of photocell behaviours under our belt, let's try out some more for different RoboBrrd interactivity!
PART 2 COMING SOON!