Hi fellow Bittle and Nybble owners !
This is a my first analysis of the main code of opencat, it will surely miss a lot of details as I haven't done a lot of testing yet but it contains early explanations of how the code is structured and can serve as a starting point for the community to discuss and better understand the code together.
All the code I'm going to talk about is located in the file OpenCat.ino at the root (the main folder).
The robot start by running once the setup( ) function located at the line 283.
void setup() {
// hardware setup
}
That function configure and initialize all the hardware: the i2c serial bus, MPU gyro sensor, the servos controller, the IR sensor and many others, if you add an addon (like the mu vision sensor) it will be in this function that you will add the configuration and initialization code for it. To know what code to add for a sensor refer to the sensor documentation.
After that, the robot will run the loop( ) function located at the line 407.
It will loop (hence the name) indefinitely on that function, so is located here all the code that brings your robot to life.
void loop() {
// code executed indefinitely
}
The first interesting section of that function is the part were the robot read his sensors, it start at line 430 with the IR sensor, the serial port then the gyro sensor.
if (irrecv.decode(&results)) {
// get the pushed remote key
}
if ( Serial.available() > 0) {
// read data receive via serial port
}
// you can another sensors
If you want to remove a sensor functionnality to reclaim memory (the board have a very limited amount of memory space, you can see how much memory does your program take in the arduino IDE console at program compilation) you can remove or comment the code dedicated to a sensor here (or use ifdef if you know how to use them). If you want to add a sensor it will also be the right place to put the sensor reading code.
The next part is where the robot chooses the action to do according to some variables that you will have to set in the previous sensor reading section.
This section start at line 472 with a conditional block on the variable newCmdIdx, this variable is set to zero at the start of the loop so if you want to tell your robot it have something new to do, set it to more than 0 in the sensor reading section.
if (newCmdIdx) {
// robot take new action
}
If the robot know it have something new to do, it will go to a switch on the token variable at line 476. A switch is a like a signpost to different parts of the code, according to the value of the token variable (a single character) the robot will run different code located after the ‘cases’ on which you can see the related token value.
switch (token) {
case 'd': {
strcpy(lastCmd, "rest");
skillByName(lastCmd);
break;
}
case 'g': {
if (!checkGyro)
checkBodyMotion();
// countDown = COUNT_DOWN;
checkGyro = !checkGyro;
token = 'k';
break;
}
// others cases
// default -> if none of the declared cases
default: if (Serial.available() > 0) {
String inBuffer = Serial.readStringUntil('\n');
strcpy(newCmd, inBuffer.c_str());
}
}
From there, everything I'm going to say is very speculative (lack of tests) but as I understand:
Token value 'k' is the "validating" token, it mean that the robot will call the motion part of the code only if the token is at value 'k' when he reaches it (at the end of the loop). You can see that the value 'k' have no case in the switch. The others tokens are therefore there to execute a specific code (to set newCmd string value about which I speak below for example). These codes will run once as newCmdIdx is reset to 0 at the beginning of each loop. However the token is not reset between each loop so if the token is set to value 'k' the robot will run the motion part of the code at each loop (it's how the robot walk), if not at 'k' the robot will not move (like in the rest position). A lot of interesting things can be done here, but details are for another day. 😉 You can see in the snippet above, that the token ‘g’ set the token to ‘k’ at the end of his case block so the robot will execute this code once and run the motion code after it (among other things...). You can see that the token ‘d’ him does not set the token value to 'k' and call the skill directly so the robot set to the "rest" posture and will not move while a new action is not called in the sensor reading part.
After this switch the robot will check the newCmd string at line 649:
if (strcmp(newCmd, "") && strcmp(newCmd, lastCmd) ) {
// ... some other code
if (token == 'k') { //validating key
motion.loadBySkillName(newCmd);
// ... some other code
}
// ... some other code
}
The newCmd string is set to a void string at the beginning of each loop, as you can guess, it is used to load a new skill. As you can see in the code above, if newCmd is not void (strcmp(newCmd, "") mean different of "") the block will be executed and then if the token is at value ‘k’ the skill named by the string in newCmd will be loaded into the motion object. The names are those set in the array «skillNameWithType[] » of the file OpenCat.h.
Near the end of the loop (line 739) the skill loaded into the motion object will be run if the token is at value ‘k’ as mentioned above:
//motion block
{
if (token == 'k') {
//motion code...
}
else
timer = 0;
}
That’s all for the moment ! I hope you will find these informations useful, I will continue to add details on future posts (I didn't talk about the lastCmd string for exemple...) as with tinkering, some parts of the code will become clearer. Have fun ! 😸
Your explanations are really insightfull thank you so much for taking the time to document this for those who want to learn! :) While I was able to follow your Explanation I cant find such File in the Root. I do have an OpenCat.ino file, however its only 151 lines long. Any Idea what I could be missing? Thanks again for your hard work!