Categories
control dev Locomotion robots

Basic Planning

We need the robot to be somewhat intelligent, and that means some rules, or processes based on sensor input. Originally I was doing everything in PyBullet, and that leads to techniques like PyBullet-Planning. But as we’re getting closer to a deadline, simpler ideas are winning out.

I came across this paper, and figures 1 and 2 give an idea.

Figure 1 “Rule-Based Approach for Constrained Motion Control of a Teleoperated Robot Arm in a Dynamic Environment”

WordPress apparently won’t show this image in original resolution, but you can look up close in the PDF.

Here’s a fuzzy controller: FUZZY LOGIC CONTROL FOR ROBOT MAZE TRAVERSAL: AN UNDERGRADUATE CASE STUDY

I like that idea too. Fuzzifying concepts, defuzzifying into adjusting towards descriptions from rules

This is probably starting to get too many acronyms for me, but Goal Reasoning is a pretty fascinating topic. Now that I see it, of course NASA has been working on this stuff for ages.

Excerpted below (Doychev, 2021):

The ā€Cā€ Language Production System (CLIPS) is a portable, rule-based production system [Wyg89]. It was first developed for NASA as an expert system and uses forward chaining inference based on the Rete algorithm consisting of three building blocks[JCG]:
  • Fact List: The global memory of the agent. It is used as a container to
    store basic pieces of information about the world in the form of facts, which are usually of specific types. The fact list is constantly updated using the knowledge in the knowledge base.
  • Knowledge Base: It comprises heuristic knowledge in two forms:

    ā€¢ Procedural Knowledge: An operation that leads to a certain effect.
    These can, for example, modify the fact base. Functions carry procedural knowledge and can also be implemented in C++. They are
    mainly used for the utilization of the agent, such as communication to
    a behavior engine. An example for procedural knowledge would be a
    function that calls a robot-arm driver to grasp at a target location, or a
    fact base update reflecting a sensor reading.

    ā€¢ Rules: Rules play an important role in CLIPS. They can be compared
    to IF-THEN statements in procedural languages. They consist of several preconditions, that need to be satisfied by the current fact list for the rule to be activated, and effects in the form of procedural knowledge. When all its preconditions are satisfied, a rule is added to the agenda, which executes all the activated rules subsequently by firing
    the corresponding procedural knowledge.
  • Inference Engine: The main controlling block. It decides which rules
    should be executed and when. Based on the knowledge base and the fact base, it guides the execution of agenda and rules, and updates the fact base, if needed. This is performed until a stable state is reached, meaning, there are no more activated rules. The inference engine supports different conflict resolution strategies, as multiple rules can be active at a time. For example, rules are ordered by their salience, a numeric value where a higher value means higher priority. If rules with the same salience are active at a time, they are executed in the order of their activation.


  • CLIPS Executive
    The CLIPS Executive (CX) is a CLIPS-based production system which serves as a high-level controller, managing all the high-level decision making. Its main tasks involve goal formation, goal reasoning, on-demand planner invocation, goal execution and monitoring, world and agent memory (a shared database for multiple agents) information synchronization. In general, this is achieved by individual CLIPS structures (predefined facts, rules, etc.), that get added to the CLIPS environment.

Above is excerpted from paper.

So, basically it’s a rule engine.

It’s the Rete algorithm, so it’s a rule engine. It’s a cool algorithm. If you don’t know about rule engines, they are what you use when you start to have more than 50 ‘if’ statements.

Ok, that’s all I need to know. I’ve used KIE professionally. I don’t want to use Java in this project. There appear to be some simplified Python Rule Engines, and so I’ll check them out, when I have some sensor input.

I think I’m going to try this one. They snagged ‘rule-engine’ at PyPi, so they must be good.

But I’ve also looked at this python rule engine comparison, and might consider ‘Durable Rules’.

https://zerosteiner.github.io/rule-engine/getting_started.html

pip3 install rule-engine

Ok, I’ve set up an Arduino with three ultrasonic distance sensors, and it’s connected to the Raspberry Pi. I should do a write-up on that. So I can poll the Arduino and get ‘forward left’, ‘forward’ and ‘forward right’ ultrasonic sensor distance back, in JSON.

I think for now, a good start would be having consequences of, forward, backwards, left, right, and stand still.

These are categories of motions. Motions have names, so far, so we will just categorize motions by their name, by whether they contain one of these cardinal motions (forward or walk, back, left, right, still) in their name.

To keep things interesting, the robot can pick motions from these categories at random. I was thinking of making scripts, to join together motions, but I’m going to come back to that later. Scripts would just be sequences of motions, so it’s not strictly necessary, since we’re going to use a rule engine now.

Ok… after thinking about it, screw the rule engine. We only need like 10 rules, at the moment, and I just need to prioritize the rules, and reevaluate often. I’ll just write them out, with a basic prioritisation.

I also see an interesting hybrid ML / Rule option from sci-kit learn.

Anyway, too complicated for now. So this would all be in a loop.

TOO_CLOSE=30
priority = 0

# High Priority '0'
if F < TOO_CLOSE:
    runMotionFromCategory("BACK")
    priority = 1
if L < TOO_CLOSE:
    runMotionFromCategory("BACK")
    runMotionFromCategory("RIGHT")
    priority = 1
if R < TOO_CLOSE:
    runMotionFromCategory("BACK")
    runMotionFromCategory("LEFT")
    priority = 1

# Medium Priority '1'  (priority is still 0)
if priority == 0 and L < R and L < F:
    runMotionFromCategory("RIGHT")
    priority = 2
 
if priority == 0 and R < L and R < F:
    runMotionFromCategory("LEFT")
    priority = 2

# Low Priority '2'  (priority is still 0)
if priority == 0 and L < F and R < F:
    runMotionFromCategory("WALK")
    priority = 3

Good enough. So I still want this to be part of the UI though, and so the threading, and being able to exit the loop will be important.

Basically, the problem is, how to implement a stop button, in HTTP / Flask. Need a global variable, basically, which can be changed. But a global variable in a web app? Sounds like a bad idea. We have session variables, but the thread that’s running the motion is in the past, and probably evaluating a different session map. Maybe not though. Will need to research and try a few things.

Yep… “Flask provides you with a special object that ensures it is only valid for the active request and that will return different values for each request.”

Ok, Flask has ‘g’ …?

from flask import g
user = getattr(flask.g, 'user', None)
user = flask.g.get('user', None)

Hmm ok that’s not right. It’s not sharable between requests, apparently. There’s Flask-Cache…. that might work? Ok I think this is it maybe.

def task():
    if exit_event.is_set(): 
            print(f'end {threading.current_thread().name} ')
            return

exit_event = threading.Event()

@app.route("/", methods=["GET", "POST"])
def index():
    exit_event.clear()
    t=threading.Thread(target=task, args=(thread_return,), daemon=True)
    t.start()
    ...
    exit_event.set()

Ok, except I’m getting “RuntimeError: Working outside of request context.” So there’s some official info

Ok the official info looks odd, calling it a test. But I found this decorator elsewhere…

@copy_current_request_context
def runMotionTask(data):

RuntimeError: This decorator can only be used when a request context is active, such as within a view function.

Ok, so I need to put the thread inside the view…. ok…

Ha it worked. This is ugly though. How do I reuse code with this shitty thread-inside-view syntax?

@app.route('/runadjustedmotion', methods=['POST', 'GET'])
def runadjustedmotion():
    data = request.get_json()
    return runadjustedmotiondirect(data)

def runadjustedmotiondirect(data):
    @copy_current_request_context
    def runMotionTask(data):
        ...

    (start thread)

Ok so. Kill switch is working. Very good.

Now, to run the brain. I don’t know how to reuse code with these inner functions. So it’s copy-paste time. The brain will need to be a single thread. So something like

@app.route('/runBrain', methods=['POST', 'GET']) 
def runBrain():     
   @copy_current_request_context     
   def runBrainTask(data):
      @copy_current_request_context     
      def runMotionFromCategory(category):
         ...
      ...
      if x then runMotionFromCategory("BACK")
      if y then runMotionFromCategory("LEFT")
   (start runBrainTask thread)

Ok let’s try it…

Ok first need to fix the motions.

Alright. It’s working! Basic Planning working.

It can shuffle around now, without bumping into walls. Further testing will be needed. But a good basis for a robot.