New Remote Camera Robot - Code Refactoring Project

I am creating a new “thread”, (pun intended), to capture the work put into refactoring the New Remote Camera Robot project’s code.

I already know that this will be a Massive Foray into Uncharted Territory for me, working with classes, methods, threads, exception handling, and a couple of dozen other things.

This will allow me to post results, ask questions, get good advice, and avoid polluting the other New Remote Camera Robot threads.

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

First comment:

GitHub and VS Code are definitely “Hot Smokin’ Weapons”!

GitHub allows me to “keep a journal”, (so to speak), of what I’m doing with my code,

VS Code allows me to manage things - especially creating and merging branches - to keep various aspects of my experiments separate so they don’t pollute each other.

With VS Code I can decide, (like I have now), that I need to do something different - like a major code re-factoring - and create a branch that encapsulates that effort.  The nice thing about that is I can start a branch, (say like this one), and proceed merrily down it.  If I discover I made a wrong turn, I can back-track.  If I decide the whole thing has degenerated into a massive pile of GAGH!, I can trash the entire branch and start fresh.  Meanwhile, my main branch and any other working branches remain pristine.

First:  Create the new branch and publish it.
Second:  Go read all the stuff @cyclicalobsessive recommended me to read!

2 Likes

Third: Draw pretty pictures

1 Like

I have a .md file editor extension installed that allows me to do that, sort-of.

My drawing skills were never so good, even using a K&E drafting table with all the fancy gadgets.  Post Covid, they’re abysmal.

1 Like

And this is a perfect example of why this is important:

  1. Create a new “project” to refactor the project.

  2. Decide that this is a Golden Opportunity to kill off some “stale” branches and I kill off the Server_Sent_Events branch.

  3. Notice, (later), that there are important things there that have NOT been merged into anything else.  (Oops!)

  4. Discover that both VS Code and GitHub have my back:
    https://stackoverflow.com/questions/19710304/restore-branch-deleted-from-github

    • See my comment posted after the accepted answer.
       
  5. Take a deep breath and try to stop shaking after making such an abysmal mistake.

Theoretically, the best option is to never delete anything, but that rapidly becomes a total mess.

2 Likes
  1. Remove 1 sheet paper from printer
  2. Find pencil with a functioning eraser
  3. Draw Ugly Pictures
  4. Erase till the paper tears
  5. Remove 1 sheet paper from printer
  6. Redraw as pretty picture
  7. Photograph with phone
2 Likes

I’m golden until step 6.

2 Likes

Update:

I am addressing one of @KeithW’s major comments with the code - everything was named the same and following the variables through the code was like listening to Abbot and Costello’s Who’s On First?

What I did:

  • Created a global  “dictionary” structure that has a “slot” for all the robot’s functional attributes, (variables), and puts them all in one place where they can be referenced.  Note that this is not just the data returned from the browser, but also includes other essential data, (such as calculated speeds), that are used virtually everywhere.

Viz.:

robot = {
    "controller_status": 'Disconnected',
    "motion_state": 'Waiting for Joystick',
    "direction": 'None',
    "time_stamp": 0,  # a large integer that, (sometimes), becomes a float (shrug shoulders)
    "x_axis": 0.00,  #  x-axis < 0, joystick pushed left - x-axis > 0, joystick pushed right
    "y_axis": 0.00,  # y-axis < 0, joystick pushed forward - y-axis > 0 , joystick pullled back
    "head_x_axis": 0.00,  #  head x and y axes mirror the joystick x and y axes
    "head_y_axis": 0.00,  #  if the pinky-switch, (head motion enable), is pressed
    "force": 0.00,  #  force is the absolute value of the y-axis deflection
    "trigger_1": 0,   # Partial primary trigger press (motion enabled)
    "trigger_2": 0,   # Full primary trigger press  (faster speed - not yet implemented)
    "head_enable": 0,
    "normal_speed": 150,  #  Max speed if the trigger is pressed half-way
    "turbo_speed": 300,  #  Max speed if the trigger is fully pressed
    "speed": 0,  # this represents the currently selected maximum, either normal or turbo speed
    "desired_speed": 0,  #  This is the adjusted speed based on joystick force.
    "vcenter": 92,
    "hcenter": 97,
    "vposition": 92,  #  The "calibrated" positions for Charlie's head 
    "hposition": 97  #  to be centered in both axes.
    }

. . . and so a robot variable used outside a method, or referencing a global attribute is now referenced as robot["variable"] and any local reference, (i.e. inside a routine that calculates something), is given the variable’s “descriptive” name.

Viz.:

In the place where I capture the data sent back from the browser, (args), I capture that data into the “global” data structure.

    robot["controller_status"] = str(args['controller_status'])
    robot["motion_state"] = str(args['motion_state'])
    robot["direction"] = str(args['angle_dir'])
    robot["time_stamp"] = int(args['time_stamp'])
    robot["x_axis"] = float(args['x_axis'])
    robot["y_axis"] = float(args['y_axis'])
    robot["head_x_axis"] = float(args['head_x_axis'])
    robot["head_y_axis"] = float(args['head_y_axis'])
    robot["force"] = float(args['force'])
    robot["trigger_1"] = int(args['trigger_1'])
    robot["trigger_2"] = int(args['trigger_2'])
    robot["head_enable"] = int(args['head_enable'])

However, where I do a calculation, I use the “descriptive” variable name within the function, which should be local in scope.

Code in the “main” process commands function, (the top-level function that actually moves the robot around), uses the robot[“variable”] construction to use the “global” values returned from the browser or calculated in vivo.

if robot["x_axis"] < 0:  #  Moving backward to the left
    # Moving to the left, the left wheel must be moving slower than
    # the right wheel by some percentage.
    robot["desired_speed"] = int(calc_desired_speed(robot["speed"], robot["force"]))
    robot["differential_speed"] = int(calculate_differential_speed(robot["desired_speed"], robot["x_axis"]))
    my_gopigo3.set_motor_dps(my_gopigo3.MOTOR_RIGHT, -robot["desired_speed"])
    my_gopigo3.set_motor_dps(my_gopigo3.MOTOR_LEFT, -robot["differential_speed"])
    print("moving backward to the left\n")

The calculations themselves are done in local scope

def calc_desired_speed(speed, force):
    desired_speed = int(round_up(speed * force))
    if desired_speed > speed:
        desired_speed = speed
    #  print\("calc_desired_speed: speed =", speed, "force =", force, "desired_speed =", desired_speed)
    return (desired_speed)

def calculate_differential_speed(desired_speed, x_axis):
    differential_speed = int(round_up(desired_speed - abs(desired_speed * x_axis)))
    if differential_speed > desired_speed:
        differential_speed = desired_speed
    #  print\("calculate_differential_speed: desired_speed =", desired_speed, "robot["x_axis"] =", robot["x_axis"], "differential_speed =", differential_speed)
    return (differential_speed)

def round_up(x):
    x = 100 * x  #  This will allow two decimal digits of precision ranging from zero to one.
    if x > 0:
        return (int(x + 0.5) / 100)
    elif x < 0:
        return (int(x - 0.5) / 100)
    else:
        return(0)

This should help solve some of the scoping and variable confusion that Keith noticed.

IMHO, cleaning up variable naming and scope is the first step toward actually “chunking” the program and turning it into scalable modules.

Another interesting thing:

Along with learning how to use Python dictionaries, I discovered an interesting thing with Chrome.

  • If you have the developer window open, (F12), and the focus is within the developer window, pressing the “up” arrow brings you a message asking you to “accept the EULA”. (Easter Egg?  Maybe not.)
2 Likes

Additional update:

You folks may have noticed two things:

  1. I mentioned that I am moving anything that is not related to my joystick project off the table.

  2. Yet, I seem to have abandoned the code refactoring project in favor of a bunch of other, smaller, projects.

There is actually a method to my madness, (or is it a madness to my method?):

  1. I have determined that the code refactoring project is, (at least!), as complex as the original New Remote Camera Robot project.

  2. I have determined that (as the folks in Hosea said), I am being “destroyed” by a lack of knowledge, and I am making things worse rather than better.

The conjunction of those two points, and the two previous points, is that I need to know more about what I am doing, so I have decided to work on some “smaller” projects that will allow me to gain the expertise in the various techniques I will need to refactor and re-organize the main project.

Therefore, I will require skill in techniques that I do not yet know, or perhaps even realize that I need to know.  Things like:

  • Reading configuration data from a file.
  • Creating, using, and maintaining classes.
  • Knowing when functions are appropriate and when should I use classes.
  • Migrating subsidiary routines to their own modules to simplify the main routine and make the modules themselves easier to maintain.

I still “have my eye on the goal” which is the Remote Camera Robot" project, but I am taking a detour to build the skills I need.

What say ye?

2 Likes

Update:

I have run into a huge wall:  The project, as written, is hard-coded to use one, and only one, brand of joystick - the Saitek X52 and/or X52 Pro.

I had originally thought I could create a configuration JSON file and simply transmit it to the browser.  The JavaScript running on the browser would reconfigure the joystick trigger and axis assignments based on the configuration received.

I am coming to the conclusion that it might not be so simple.  Transmitting a configuration file isn’t too difficult, (include “XXXX”), but re-configuring all the joystick controller logic to handle changed controller assignments won’t be easy.  As I imagine it, the result will be, at best, ugly and at worst, totally borked.

The easiest way would be to do an absolute minimum of processing at the browser and simply send the entire data-frame from the joystick to the robot.  But that’s a problem too.  To send data to the robot from the browser, it needs to be sent in “dictionary” format - key:value pairs - and with a potentially infinite number of configurations for joystick/gamepad button/axis assignments, that becomes unwieldy rather rapidly.

As I see it, I can do one of two things:

  1. Send the entire joystick/controller data-frame as a list of number:value pairs with the number as the key - and then map the numerical value to a controller function on the robot - along with doing all the data massaging that I do on the browser right now.

    • This would make feature/controller action updates relatively simple, as all that would be needed is another conditional block in the controller code on the robot.

    • However, ALL the controller/functional data massaging would have to be done on the robot.  Right now, a certain amount of data processing and status reporting is done on the browser before the data is transmitted back to the robot.

    • Additionally, with an unknown number of parameters returning to the robot, there will be a lot of data to handle that won’t be used, and the data-frame object can rapidly become huge.

      • The Saitek joystick has 9 axis pairs and 31 buttons, all of which would have to be transmitted back to the robot.

      • A “Standard Gamepad” has at least 16 buttons and a minimum of two axis pairs.

      • Other joysticks and/or gamepads will have different mappings and different amounts of data that get returned.

      • The obvious idea of only transmitting the first “X” number of axes and buttons sounds good, but may not work, as an essential controller button may be near the end of the list.  (On the Saitek joystick, the “second click” on the primary trigger - that I use for the “turbo speed” function, is button 14.)

    • I fear that this will place an inordinate processing load on the robot’s server code making things very slow and unresponsive.

  2. Keep things in the browser the way they are, but import a configuration JSON file, (or an xml file?), and re-assign things within the browser itself.

    • Re-assigning actual axis/button mappings themselves within the browser isn’t a trivial task, (AFAIK), and I am not sure how I would do that.

    • Updates to controller functionality or features would be ugly as that would require a lot of modifications to the controller script code.

    • However, that would have the advantage of keeping that load off the robot, helping to distribute the processing effort.

Then again, what happens if the file is missing?  Or if individual mappings are missing or incorrectly formed?  I am rapidly coming to the conclusion that this has more aspects than a cat has hair.

Either solution will be ugly as heck.

There is a third option:

  • Ignore extensibility, ignore other controller types, and design the project to work with my specific joystick only.

This would solve all the problems in one fell swoop.  However, one of my “implied” goals is to make a software project that can be used and enjoyed by others instead of just me.  Having put so much work into this project, it seems kind of selfish to not let others enjoy it too.

More research is necessary and suggestions are always welcome.

2 Likes

One other thing I just thought about:

Some browsers do not limit the number of objects in the data-frame to the objects actually implemented in the joystick/gamepad.  Chrome, for example, can send up to 99 buttons and 99 axes, filling in valid data for the ones actually implemented.  I have not researched Opera, Safari, or other browsers at all because of the data confusion and how well the gamepad API is implemented.

Sending the entire data-frame to the robot may not be feasible unless I limit it to some arbitrary value.

2 Likes

Update:

I modified the joystick data-structure in the browser JavaScript so I could try it with a “standard” game controller and I discovered that the control precision I enjoyed with the joystick is absent with a game controller - the joystick throw is too short - even with a “name brand” quality game controller.

Not just that, but the possible combination of controls, and the

I’m still going to work on making a gamepad configuration file of some kind, but I think I’m going to end up recommending a joystick instead of a gamepad.

2 Likes