Coding/Debugging C++ using VS Code?

I am embarking on a new adventure that, (hopefully), will benefit the GoPiGo robot and everyone who uses it.

  1. Becoming familiar with C++.

  2. Becoming familiar with coding C++ using VS Code.

  3. Being able to both code and debug C++ within VS Code.

First project:
I am currently working on a graphical “checklist” program for Windows that has a list of items that must be selected before the “continue” button becomes active and allows you to proceed.[1]

I have two versions:

  • One in Python
  • One in C++

The one in Python works as expected, however the one in C++ does nothing.

The C++ version doesn’t generate any errors, but produces no output - no window, no nothing. I suspect that whatever is supposed to create the window isn’t creating the window, perhaps because it’s not being called?

Ergo:
I need to learn how to debug C++ code in VS Code.

====================================

Second project:
(After I figure out what I am doing in VS Code.)

Continue the work @cyclicalobsessive started porting the GoPiGo3 API to C++ as a way of avoiding Python being a moving target with version “X” being obsolete fifteen minutes after release.

Two questions:

  1. @cyclicalobsessive @KeithW @cleoqc

    • What experience do you have with C++ within VS Code?
    • Do you have any experience setting up a debug environment in VS Code for C++?
  2. @cyclicalobsessive

    • What progress have you made so far with porting the GoPiGo3 APIs to C++?
    • What remains to be done?
    • What difficulties do you anticipate?
      (Aside from me having zero clue about what I’m doing. :wink:)

What say ye?

============================== Footnotes ==============================

  1. Disclosure:
    Most of the actual code, (and much of the advice I received), was generated initially as an experiment to explore the feasabity of using chatGPT to write code from a requirements description. Ultimately it transformed into the main source of the development effort.
1 Like

“the GoPiGo3 APIs” for C++ would mean the GoPiGo3 and EasyGoPiGo3 classes.

The good news - Dexter already did a complete C++ GoPiGo3 class.

The bad news: Nobody has created a C++ EasyGoPiGo3 class but you don’t need it!

There is everything needed to program the GoPiGo3 in C++ including accessing all the sensors on a Pi3 or Pi4. (It also all works on PiOS Bookworm and Pi5, but only with my GoPiGo3 Software Installer.)

#include <GoPiGo3.h>   // for GoPiGo3
#include <unistd.h>    // for usleep


GoPiGo3 GPG;

// forward
GPG.set_motor_dps(MOTOR_LEFT + MOTOR_RIGHT, NO_LIMIT_SPEED);

sleep(2); // GO GO GO FAST FOR 2 seconds

// stop
GPG.set_motor_dps(MOTOR_LEFT + MOTOR_RIGHT, 0);
sleep(1);  // make sure stopped before going on

How to setup your C++ testing and run all the examples:

What remains to be done and what difficulties lie ahead?

Having EasyGoPiGo3.drive_cm() and EasyGoPiGo3.turn_degrees() would really make life easy - so:

/* FILE: EasyGoPiGo3.h

   Instance Vars:
     speed: default 150 DPS
     left_eye_color
     right_eye_color
     left_encoder_target
     right_encoder_target

   Implemented Methods:

   TODO Methods:
     set_speed(speed_in_DPS=150)
     get_speed()
     forward(): drive forward - use set_speed() or default: 150 DPS
     backward(): drive backward
     stop(): 
     drive_cm(dist_cm, blocking=true)
     drive_inches(dist_inches, blocking=true)
     right() : pivot cw around right wheel
     left():  pivot ccw around left wheel
     spin_right(): spin in place clockwise
     spin_left(): spin in place counter-clockwise
     target_reached(left_tgt_degrees, right_tgt_degrees):  use to detect when to stop forward(), backward(), right(), left(), spin_right(), spin_left()
                                                           and for non-blocking drive_cm() or drive_inches()
     reset_encoders(): resets both encoders to 0
     read_encoders(out:left, out:right, in:units=CM/INCH/DEGREE)
     read_encoders_average(out:ave, in:units=CM/INCH/DEGREE)
     turn_degrees(in:deg, blocking=true):  left: negative degrees
     blinker_on(id:{LEFT,RIGHT}
     set_left_eye_color(R,G,B)
     set_right_eye_color(R,G,B)
     set_eye_color(R,G,B)
     open_left_eye()
     open_right_eye()
     open_eyes()
     close_left_eye()
     close_right_eye()
     close_eyes()
*/

and

/* FILE: EasyGoPiGo3.cpp
 *
 * Simplified C++ API for GoPiGo3
 *
 * Patterned after the Python EasyGoPiGo3.py
 */

#include <EasyGoPiGo3.h>

EasyGoPiGo3::EasyGoPiGo3(){
    set_speed(DEFAULT_SPEED);
}

void EasyGoPiGo3::set_speed(int speed_in){
    speed = speed_in;
};

Should have moved set_speed() to “Done”…

Difficulties? Handling the unsigned math in C++ for the encoders.

2 Likes

Does it have to be unsigned? Can it be a larger precision signed number?

2 Likes

I honestly don’t know. I attempted to understand doing the math around zero and ignore the condition where travel would take the unsigned encoder value over the max signed value. The difficulty regardless of signed or unsigned is the “is value in a range of values” test when the range crosses zero. There are conditions that must be dealt with:

  • encoder positive, both range values positive
  • encoder positive, range values positive and negative
  • encoder positive, both range values negative
  • encoder negative, both range values positive
  • encoder negative, range values positive and negative
  • encoder negative, both range values negative

Perhaps judicious use of absolute value can simplify the test; I was not able to wrap my head around it.

What makes it even more complicated is:

  • the two ranges (left range, right range) come from the target encoder values (left tgt, right tgt) plus/minus the end-point tolerance,
  • and should the stop condition be a “first encoder in range” or “both encoders in range”
    if both - then what if first encoder continues out of range before the second encoder achieves the target range.

I tried to convert the target_reached() method of EasyGoPiGo3.py for EasyGoPiGo3.cpp - my head exploded trying to debug my C++ code without a debugger:

def target_reached(self, left_target_degrees, right_target_degrees):
        """
        Checks if (*wheels have rotated for a given number of degrees*):

             * The left *wheel* has rotated for ``left_target_degrees`` degrees.
             * The right *wheel* has rotated for ``right_target_degrees`` degrees.

        If both conditions are met, it returns ``True``, otherwise it's ``False``.

        :param int left_target_degrees: Target degrees for the *left* wheel.
        :param int right_target_degrees: Target degrees for the *right* wheel.

        :return: Whether both wheels have reached their target.
        :rtype: boolean.

        For checking if the `GoPiGo3`_ robot has moved **forward** for ``360 / 360`` wheel rotations, we'd use the following code snippet.

        .. code-block:: python

            # both variables are measured in degrees
            left_motor_target = 360
            right_motor_target = 360

            # reset the encoders
            gpg3_obj.reset_encoders()
            # and make the robot move forward
            gpg3_obj.forward()

            while gpg3_obj.target_reached(left_motor_target, right_motor_target):
                # give the robot some time to move
                sleep(0.05)

            # now lets stop the robot
            # otherwise it would keep on going
            gpg3_obj.stop()

        On the other hand, for moving the `GoPiGo3`_ robot to the **right** for ``187 / 360`` wheel rotations of the left wheel, we'd use the following code snippet.

        .. code-block:: python

            # both variables are measured in degrees
            left_motor_target = 187
            right_motor_target = 0

            # reset the encoders
            gpg3_obj.reset_encoders()
            # and make the robot move to the right
            gpg3_obj.right()

            while gpg3_obj.target_reached(left_motor_target, right_motor_target):
                # give the robot some time to move
                sleep(0.05)

            # now lets stop the robot
            # otherwise it would keep on going
            gpg3_obj.stop()

        .. note::

            You *can* use this method in conjunction with the following methods:

                 * :py:meth:`~easygopigo3.EasyGoPiGo3.drive_cm`
                 * :py:meth:`~easygopigo3.EasyGoPiGo3.drive_inches`
                 * :py:meth:`~easygopigo3.EasyGoPiGo3.drive_degrees`

            when they are used in *non-blocking* mode.

            And almost *everytime* with the following ones:

                 * :py:meth:`~easygopigo3.EasyGoPiGo3.backward`
                 * :py:meth:`~easygopigo3.EasyGoPiGo3.right`
                 * :py:meth:`~easygopigo3.EasyGoPiGo3.left`
                 * :py:meth:`~easygopigo3.EasyGoPiGo3.forward`

        """
        tolerance = 5
        min_left_target = left_target_degrees - tolerance
        max_left_target = left_target_degrees + tolerance
        min_right_target = right_target_degrees - tolerance
        max_right_target = right_target_degrees + tolerance

        current_left_position = self.get_motor_encoder(self.MOTOR_LEFT)
        current_right_position = self.get_motor_encoder(self.MOTOR_RIGHT)

        if current_left_position > min_left_target and \
           current_left_position < max_left_target and \
           current_right_position > min_right_target and \
           current_right_position < max_right_target:
            return True
        else:
            return False

I tried using the Examples/motors.cpp (motors binary) to see encoder values (gently turn left wheel, right wheel rotates to match left encoder value).

target_reached() is the key to drive_cm() and rotate_degrees() - the most desired functions of the EasyGoPiGo3 class.

2 Likes

. . . . . plus any necessary dependencies that aren’t implicitly included in the existing C++ code or existing libraries.

Right?

2 Likes

From what I could see - DI built the entire GoPiGo3 and sensor API.

I have a feeling the I2C support might be only hardware I2C because I didn’t have to change any C++ code for Bookworm. Also I didn’t check if they implemented the I2C mutex code.

2 Likes

Which translates to “there is much research left to be done”.

You have accomplished much, but there appears to be much left to do as well.

Corollary question:

Assume the entirety of the GoPiGo API is written in C++ and (somehow) having done that we still have access to the internal classes and functions from outside the compiled code. (And I am not sure how to do that, if it’s even possible.)

Assuming it’s true, is it possible to create an environment where you can use this within Python the way we’ve been doing it - by including something, instantiating classes and using those class methods?

2 Likes

Of course it is possible. By you or me? No way!

If you want the simplicity of Python, use Python. If you want the speed of C++, or just want to learn C++ then use C++. Really - everything is there already, just a little less convenient without the EasyGoPiGo3 class.

I just read about someone has implemented this very thing for ROS 2 because they were tired of “slow Python3”. They launch the C++ and the Python in separate somethings with a common something that allows sharing something. (I didn’t really try to understand it deeper).

Only if someone really wants to “expand the Programming the GoPiGo3 in C++ frontier”. Like I mentioned everything is there to “Program the GoPiGo3 in C++” already. The first step for an “Expander” would be porting the EasyGoPiGo3.py target_reached() method and drive_cm() to C++. It might be a simple port for someone that knows C++.

For us, the GoPiGo3.h/.cpp contain everything needed to learn to program the GoPiGo3 in C++, and to learn/re-learn C++ along the way.

2 Likes

I posed this question to ChatGPT, outlining the situation and specifying that, though the underlying functionality would be in C++, the API must remain identical and work with existing Python code without changes.

Viz.:

What say ye?

1 Like

Not interested, but go for it if it interests you. Concept adds complexity on top of two simple APIs, with no advantage to me.

Now having ChatGPT have a go at transcoding EasyGoPiGo3.py into EasyGoPiGo.h/.cpp - that would be very, very interesting.

2 Likes

I want to have a look.

I might get heartily tired of it faster than you did, but if I can learn something about C++ and debugging in the process it won’t be wasted time.

2 Likes

For sure if you can use ChatGPT to write EasyGoPiGo3.cpp/.h for us, we can both learn something about C++, about the GoPiGo3, and you can learn how to set breakpoints in GoPiGo3 C++ code?

I’m all ears to hear how it goes and where is your pull request so I can try it out on Dave. Nicole will want a reviewer before she merges – I’m your guy!

2 Likes

I thought I’d see how ChatGPT would translate EasyGoPiGo3.py into C++ but it requires a sign-up and more time that I’m willing to invest at this point. I’ll wait till you publish it. It appears one has to learn how to “program” ChatGPT to get usable answers, and I’m fully booked learning to program GoPi5Go-Dave and TB5-WaLI.

2 Likes

Why don’t you send the question you wanted to ask to me. I have an account there - though I’ve never run into the wall even when anonymous.

P.S.
This experiment may fail miserably, falling out of the sky streaming flames and smoke in a blaze of glory!  If that’s the worst thing that happens here, we haven’t lost anything and we might even learn something.

My take on ChatGPT?
It makes a good reference assistant and can provide hints and potential solutions.  If the potential solutions work, so much the better.  If not, we’re not any worse than we were and the hints that the potential solution provides might guide us to the actual solution.

BTW, when I posed a similar problem to ChatGPT, it suggested several possible translation tools as noted above, but cautioned that porting from one language to another, especially languages as dissimilar as Python and C++, isn’t a task for the weak and poses significant challenges.

2 Likes

I’ve noted that ChatGPT responds to additional information and can adapt its behavior based on external observations.

My original question was to discover if it is possible to keep sessions independent and not combine them within one chat-stream.

It suggested that opening new tabs using the base URL for ChatGPT will open a distinct chat-stream.

I paused to test it and returned the following results to ChatGPT:

Viz.:

Interesting. . .

2 Likes

Exactly my understanding. Some folks are creating amazing programs for their robots using generative AI large language models, but nothing that could not have been created in a more maintainable, more independent manner. It may be faster to have an LLM write the code, but debugging your own code is usually easier than debugging someone (or something) else’s code.

2 Likes

Every word of that is absolutely true, with one caveat:
This assumes you know what the heck you’re doing in the first place!

In my case, (at least with ChatGPT), it provides a narrative about what it’s writing both in the comments and in a breakdown of what the various parts of the code does - more like a coding class than someone going “hocus-pocus” and pulling magic rabbits out of their hat.  For me, that’s invaluable.

Additionally, there’s none of the elitism found in Stack Overflow where I can submit a question that gets a “Famous Question” gold badge, (more than 10-zillion views), and it still gets edited beyond recognition and down-voted!

Sigh. . . .

2 Likes

Task: Have ChatGPT port the EasyGoPiGo3.py class to C++ (ignoring all easysensor.py usage)

Question:


Given the C++ equivalent for for the Python3 gopigo3 class,

port the Python3 easygopigo3 code:

to C++ as easygopigo3.h and easygopigo3.cpp files.

Do not attempt to port any method which requires the methods of the easysensors Python import - include the original Python code for the non-ported methods as comments in the generated C++ easygopigo3.cpp file.


Of most interest of course will be drive_cm() and target_reached(). Everything else will probably just be simple transliteration. It is the choice of types that I don’t have a clue to do, Python defaulting to “typeless” and C++ defaulting to strong typing.

Perhaps I should have hinted that the encoders are unsigned hardware registers, but I don’t know enough if that matters, so lets see what it generates and how it executes.

2 Likes

So, what “type” does C++ insist on?  Chocolate Chip?  Vanilla Dream?  Another type?

2 Likes

Integer Types:

  • int: Represents whole numbers (no decimal point).
  • short: Smaller range than int, potentially using less memory.
  • long: Larger range than int, potentially using more memory.
  • long long: Even larger range than long.
  • unsigned: Modifier to store only non-negative numbers, effectively doubling the positive range.

Floating-Point Types:

  • float: Single-precision floating-point numbers (decimal values).
  • double: Double-precision floating-point numbers (higher precision decimals).
  • long double: Extended-precision floating-point numbers (even higher precision).

then there are special defined length types like uint32_t which is what I tried to use to represent the hardware encoder register. Dealing with testing a uint32_t to be within a range of uint32_t around and across the 0/max uint32_t boundary melted my head and I gave up.

2 Likes