Pololu Robotics & Electronics
Menu
My account Comments or questions? About Pololu Contact Ordering information Distributors

Pololu Forum

Balboa & Raspberry Pi


#1

I’ve built the Balboa. Easy build with the great instructions and it works perfectly. I fitted a RPi Zero-W and that works great. Wireless is working. Now to get the two to talk.

I looked over the RPiSlave library mentioned in the Balboa manual and of course it doesn’t work. Won’t even compile with the Balance example. But I didn’t expect it to work. It’s written for a different platform.

In order to balance the Balboa needs to use the IMU, obviously, so it’s programmed as a TWI master. But the RPiSlave library is expecting the Balboa to act as a slave. Balboa can’t be both. I’m thinking about it but not sure where to go next. I can reverse the roles, make the RPi the slave and keep the Balboa as master. But then the RPi isn’t the higher authority, it’s a peripheral. Or make the Balboa a slave and have the RPi interface with the IMU and send readings down to the Balboa.

The latter seems to be the better approach. But what about latency, with the Pi being a general purpose computer? I would appreciate anyone’s thoughts on the issue.

Thanks!!

Brand new to Pololu by the way!

-D


#2

Hello.

We’ve been trying to take the second approach you mentioned (“make the Balboa a slave and have the RPi interface with the IMU and send readings down to the Balboa”).

I have an early implementation of some software that does this, but I never got it tuned to actually balance well. In any case, the tuning is going to depend on your choice of Raspberry Pi (with the Zero being a lot lighter than a full-size 3B, for example) and what kind of arms or bumpers you attach to the Balboa (which are especially recommended to protect the Raspberry Pi).

Although it’s not ready for an official release, I just made this version of the software available on GitHub in the “balboa” branch of the pololu-rpi-slave-arduino-library repository, so you could give it a try.

It works similarly to the existing demos for the A-Star and Romi, except you need to run server_balboa.py instead of server.py, and the web interface presents you with a “Calibrate” button that you need to click to calibrate the IMU before the driving controls appear for you to use (including a “Stand up” button). As I mentioned before, you’ll definitely have to adjust the balancing constants; you can do that by editing balance.py.

Please let us know if you have any trouble with the software. If you are successful getting it to work, we’d be interested to see your results!

Kevin


#3

Thank you! I’ll certainly try it out and let you know.


#4

Hi,

I’m also new to the Balboa. I’m resurrecting this thread because my question follows on and seems relevant. I’m glad to report that the server_balboa.py flask app works fine with my RPi3 Model B.

When I call the calibrate() function and then stand_up() in a python script, outside the flask web app, the Balboa flips over and won’t balance! But it works fine in the web app! I’m about to walk the code to see what I missed but thought it might be quicker to ask here.

How can I use the balance.py functions in a standalone python script to make it stand up?

(I’ve also attached a VL6180X ToF sensor to the I2C bus on the top header row and it works fine (with some help from here)

All the best!

2019-01-21T18:58:00Z


#5

Hello.

Can you post your Python script?

- Amanda


#6

Hi Amanda,

Yes, of course. I’m sure it looks familiar! The halt call doesn’t work but I’m not actually calling it right now. The robot plays all of the notes but fails to stand up and flips over.

I’ve left the other Python scripts in the directory unchanged (Balance.py for example).

I’m assuming there’s some variables that are not set which must be set somewhere between the javascript and the python app perhaps.

#!/usr/bin/env python3

# Copyright Pololu Corporation.  For more information, see https://www.pololu.com/

import time

from a_star import AStar
a_star = AStar()

from balance import Balancer
balancer = Balancer()

import json

led0_state = False
led1_state = False
led2_state = False

def status():
    buttons = a_star.read_buttons()
    analog = a_star.read_analog()
    battery_millivolts = a_star.read_battery_millivolts()
    encoders = a_star.read_encoders()
    calibrated = balancer.calibrated

    data = {
        "buttons": buttons,
        "battery_millivolts": battery_millivolts,
        "analog": analog,
        "encoders": encoders,
        "calibrated": calibrated
    }
    return json.dumps(data)


def calibrate():
    balancer.setup()
    balancer.start()
    return ""


def stand_up():
    balancer.stand_up()
    return ""

def drive(left, right):
    balancer.drive(int(left), int(right))
    return ""

def leds(led0, led1, led2):
    a_star.leds(led0, led1, led2)
    global led0_state
    global led1_state
    global led2_state
    led0_state = led0
    led1_state = led1
    led2_state = led2
    return ""

def hearbeat(state):
    if state == 0:
      a_star.leds(led0_state, led1_state, led2_state)
    else:
        a_star.leds(not led0_state, not led1_state, not led2_state)
    return ""

def play_notes(notes):
    a_star.play_notes(notes)
    return ""

def halt():
    call(["bash", "-c", "(sleep 2; sudo halt)&"])

def shutting_down():
    return "Shutting down in 2 seconds! You can remove power when the green LED stops flashing."

calibrate()
play_notes("l16ceg>c")
time.sleep(5)
play_notes("l16deg>d")
stand_up()
play_notes("l16deeg>de")


#7

Can you add an infinite loop at the end of your script? For example,

while True:
  time.sleep(1)

I suspect that when your script terminates, the balancer stops running, causing your Balboa to stop keeping itself upright.

- Amanda


#8

Yes, that works - many thanks.

I see now there’s no inner loop in the balance.py code. It could be my imagination, but it does seem to twitch about a lot, presumably because of latency as it checks the loop in the python code but I can fix that later.

Here is the working code for the VL6180X to work in the flask web app. First up, get the ST_VL6108X python code from here. Save the ST_VL6180X file in the same directory as the server_balboa.py file. Connect the sensor to the I2C bus to access the sensor from the Raspberry Pi. The pinout diagram shows how to connect it up.

In the index.html file located in the pi/server_balboa_resources/templates folder, add the following HTML table:

<table>
<tr><th rowspan=2>VL6180X Time Of Flight</th><td>ToF Lux</td><td>ToF Distance</td></tr>
<tr><td id="tof_lux"></td><td id="tof_distance"></td></tr>
</table>

then, the pi/server_balboa.py script should be modified to add the setup for the sensor and then render the values in the HTML table:

#!/usr/bin/env python3

# Copyright Pololu Corporation.  For more information, see https://www.pololu.com/
from flask import Flask
from flask import render_template
from flask import redirect
from subprocess import call
app = Flask(__name__, static_folder='server_balboa_resources/static', template_folder='server_balboa_resources/templates')
app.debug = True

import sys

from a_star import AStar
a_star = AStar()

from ST_VL6180X import VL6180X

debug = False

tof_address = 0x29
tof_sensor = VL6180X(address=tof_address, debug=debug)
# apply pre calibrated offset
tof_sensor.set_range_offset(23)
tof_sensor.default_settings()

from balance import Balancer
balancer = Balancer()

tof_distance = 255

import json

led0_state = False
led1_state = False
led2_state = False

@app.route("/")
def hello():
    return render_template("index.html")

@app.route("/status.json")
def status():
    buttons = a_star.read_buttons()
    analog = a_star.read_analog()
    battery_millivolts = a_star.read_battery_millivolts()
    encoders = a_star.read_encoders()
    calibrated = balancer.calibrated
    tof_distance = tof_sensor.get_distance()
    tof_lux = round(tof_sensor.get_ambient_light(20), 2)
    data = {
        "buttons": buttons,
        "battery_millivolts": battery_millivolts,
        "analog": analog,
        "encoders": encoders,
        "calibrated": calibrated,
        # adding in the sensor values here
        "tof_distance": tof_distance,
        "tof_lux": tof_lux
    }
    return json.dumps(data)

@app.route("/calibrate")
def calibrate():
    balancer.setup()
    balancer.start()
    return ""

@app.route("/stand_up")
def stand_up():
    balancer.stand_up()
    return ""

@app.route("/drive/<left>,<right>")
def drive(left, right):
    balancer.drive(int(left), int(right))
    return ""

@app.route("/leds/<int:led0>,<int:led1>,<int:led2>")
def leds(led0, led1, led2):
    a_star.leds(led0, led1, led2)
    global led0_state
    global led1_state
    global led2_state
    led0_state = led0
    led1_state = led1
    led2_state = led2
    return ""

@app.route("/heartbeat/<int:state>")
def hearbeat(state):
    if state == 0:
      a_star.leds(led0_state, led1_state, led2_state)
    else:
        a_star.leds(not led0_state, not led1_state, not led2_state)
    return ""

@app.route("/play_notes/<notes>")
def play_notes(notes):
    a_star.play_notes(notes)
    return ""

@app.route("/halt")
def halt():
    call(["bash", "-c", "(sleep 2; sudo halt)&"])
    return redirect("/shutting-down")

@app.route("/shutting-down")
def shutting_down():
    return "Shutting down in 2 seconds! You can remove power when the green LED stops flashing."

if __name__ == "__main__":
    app.run(host = "0.0.0.0")

finally the Javascript in pi/server_balboa_resources/static needs to be modified to pass the collected JSON values back to the HTML.

function update_status(json) {
  s = JSON.parse(json)
  $("#button0").html(s["buttons"][0] ? '1' : '0')
  $("#button1").html(s["buttons"][1] ? '1' : '0')
  $("#button2").html(s["buttons"][2] ? '1' : '0')

  $("#battery_millivolts").html(s["battery_millivolts"])

  $("#analog0").html(s["analog"][0])
  $("#analog1").html(s["analog"][1])
  $("#analog2").html(s["analog"][2])
  $("#analog3").html(s["analog"][3])
  $("#analog4").html(s["analog"][4])
  $("#analog5").html(s["analog"][5])
    
  $("#tof_distance").html(s["tof_distance"])
  $("#tof_lux").html(s["tof_lux"])
  
  $("#encoders0").html(s["encoders"][0])
  $("#encoders1").html(s["encoders"][1])

  if(!calibrated && s["calibrated"])
  {
    calibrateDone()
    calibrated = true
  }

  setTimeout(poll, 100)
}

Should look like this: