ROSbot GoPi5Go-Dave Architecture

Finally got around to diagramming my ROSbot GoPi5Go-Dave’s Architecture:

Not included but coded:

  • DI Distance Sensor node
  • DI BNO055 IMU node
  • Grove ultrasonic ranger node
  • slam_toolbox mapping and localization: (using gpg_cartographer from Turtlebot3)
  • nav2_gopigo3: (using gpg_navigation from Turtlebot3)
  • ros2_libcamera for piCam: (using depthai-ros with Oak-D-W97)
  • teleop_gopigo3_keyboard

Hardware installed but ROS 2 node not tested:

  • MPU9250 IMU node
2 Likes

I have “SLAM!!” implemented on both my robots too.

But only if I forget to implement the bumpers!

P.S.
Is there a way to make “bumper hit” a going process thing such that:

  • If the robot isn’t moving it either does nothing or makes a noise/flashes a light.
  • If the robot is moving, take avoidance action.  (Stop?  Backup and turn?)
  • Include the distance sensor?

Should it be a service?

What happens if the robot is already doing something, (moving under the control of a different program), when it happens?  How do I make them interact peacefully?

I don’t expect you to write the code, but hints and suggestions would be appreciated.

2 Likes

You need a “Robot Architecture”.

There are a few simple to describe robot architectures, but there are no trivial to implement architectures.

One that came out of MIT in 1986 was "Subsumption Architecture", which I used for my RugWarriorPro robot in 2000.

Google summarizes it:

Subsumption architecture is a control architecture 
that organizes robot behaviors into a hierarchy of layers 
to achieve rapid responses. It's a way of decomposing complex behaviors 
into simpler modules, which are then organized into layers. 
Each layer has a specific goal, and higher layers can subsume lower levels 
to create more comprehensive behaviors.

An example for an autonomous wall or line following robot with bumpers:

“Bottom Layer”/Highest Priority Layer: Bumpers trigger “Escape Behavior”
“Middle Layer”/High Priority Layer: Distance Sensor triggers “Avoid Behavior”
“Upper Layer”/Medium Priority Layer: Photocells trigger “Follow Behavior”
“Top Layer”/Low Priority Layer: Default Behavior: “Cruise Behavior”

So the robot having no sensors yelling at it will “cruise”

  • until a sensor triggers a higher priority behavior
    • until another sensor triggers an even higher priority behavior
    • or until the triggering sensor clears and the triggered behavior completes

It gets complicated quick when you try to keep it all straight in your programming because the simplest implementation uses multi-tasking and needs inter-task-communication (global variables is simplest)

  • tasks:
    • escape() finite state machine based on bumpers and a timer issues escape values for [translate,rotate]
    • avoid() finite state machine based on distance sensors issues avoid values for [translate,rotate]
    • follow() Issues follow values when wall/line are detected - [translate,rotate]
    • cruise() Issues cruise values for [translate, rotate]
    • arbitrate() Decides which behavior’s [translate,rotate] commands to pass to motors task
    • motors() combines translate and rotation commands to drive motors
    • report() Writes active behavior and resultant motor commands to display
    • a main() task that starts and can kill all the other tasks

  • inter-task communication:
    • cruise_trans, cruise_rotate, cruise_active
    • follow_trans, follow_rotate, follow_active
    • avoid_trans, avoid_rotate, avoid_active
    • escape_trans, escape_rotate, escape_active
    • motor_trans, motor_rotate

If you are serious about using the subsumption architecture for your robot, you can study my implementation in “Interactive C”. It is only four pages…

lewis.c.txt (5.2 KB)

1 Like

It sounds like a loop with a callback function would be simpler, (:man_facepalming:), like I did with the joystick controlled robot’s JavaScript to poll for joystick events.

In purely hardware terms I would program an interrupt handler that would be triggered when any sensor “fires” and would poll to discover which sensor was triggered.

In the case of your robots, Carl in particular, you seem to implement something like this, where the battery monitor can interrupt other functions.

How do you accomplish this in Python?

1 Like

Example creating an GPIO pin “interrupt handler” in Python:

1 Like

I read that before and it’s using a particular GPIO library that’s specially designed to implement real time interrupts.

Though I’m wondering if I can use native GoPiGo functions/methods in a callback signal handler?

I’m beginning to feel your pain.  (again)

I know exactly WHAT I want to do and I know how it should be implemented (in theory), but I have absolutely no idea how to do it in real life.

Everything I’ve read seems like it’s so close, yet so far away.
 


(An old robot vacuum we have kicking around that needs a battery.)

Maybe I should convert this into a robot?  It even has a dock!

Yeah, and I need a new head too. . . .

At this stage of the game, I’m not sure I know enough about the robot per se to even think about an architecture.

I’m not even sure what the present architecture is.  To me, it’s more like washing clothes:  You crumble everything up, toss it into the machine, add soap, turn the machine on, and hope things get clean without the color from your jeans getting on everything else.

[Shakes head, wondering what to do.]

Since I don’t know what the current architecture is,[1] I have no idea how to modify it to become the architecture I want.

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

  1. I suspect that the current “architecture” is “Sit here running the battery down until someone tells me to do something.”
1 Like

This assumes that lower layers can interrupt and temporarily take control away from higher layers - which brings me back to the original question:

How do I program something, presumably in Python, that implements a real time interrupt?

Can/should I create an “avoidance” service that runs at startup, that any program I run automatically inherits?

Then again, maybe I shouldn’t get my head in a spin-cycle and concentrate on my displays?
:wink:

1 Like

No it did not. It assumes the layers are doing their thing all the time, and posting what they want on the “global bulletin board”, and one layer is arbitrating which layer’s wishes to pass on to the motor layer.

The arbiter layer is a timed loop, not an interrupt handler

1 Like

If the action is very quick and does not require much processing.

Better to have a class with a timed loop main method that implements a state machine where each state performs some function and checks for event flags that should cause a transition to a new state.

Hardware interrupt callbacks set a class event flag and return immediately. The main state machine will see the hardware event flags and prioritize them based on the current state.

This way both hardware and software can set/reset event flags.

1 Like

Is it possible to have “avoid” and “escape” running as a service so that anything can use it, or is it better to make it a shared library?

I’m assuming that various things, like “escape” and “avoid” should use mutexes to prevent them from stepping on each other.  Right?

Either that or a set of shared global variables/flags that can be monitored like a programmatic “semaphore” to indicate what the current state is.

In this model, a process that wants to enter a particular state would set a state request flag and wait for a state acknowledge response which gives it permission to enter that state.

It then sets a running state flag and clears the state request flag.

While running, it monitors it’s state acknowledge flag and if reset, stops.  If the situation changes, the routine can clear its own running or request flags.

The main arbitration routine would be responsible for responding to the state request flags by granting or withholding permission.

If state “x” is active and a higher state needs to run, the arbiter can revoke permission by clearing the state’s acknowledge flag.  The state responds by stopping and clearing its running flag.  Once that flag is cleared, the arbiter can start the next needed process.

There would need to be a way to kill and restart a process that gets stuck and doesn’t clear its flags within a reasonable amount of time.

I could implement “flags” by creating variables that increment by orders of magnitude:
1 = request
10 = acknowledge
100 = running.
Adding or subtracting the appropriate number sets or clears the flag.

Or, I could create a True|False variable for each state for each process.  (Which might be easier to work with and would be easier to understand while running.)

I could also implement an “Emergency” flag which would halt everything.

If I wanted to get fancy, the arbiter could also manage a display showing the current state and the value of all the flags.

1 Like

No, each behavior layer (cruise, avoid, escape) is running without concern for the other, so one, two, or all of them will be “active” (“think” it is controlling the motors),

but the arbiter implements the layer priority and passes motor commands from the highest priority layer, that is “active”.

The arbiter could have “disable layer” flags that top level behaviors could use to temporarily remap a sensor input such as a “wait for bump” layer that disables the escape and cruise layers’ motor commands until bumped, then resets the disable flag for escape and cruise.

2 Likes

Very impressive - amazing progress getting ROS2 to work.
/K

1 Like