P

python-pneumaticbox

Python library for implementing Pneumaticbox clients

The packages provide a convenient interface to the airserver process on the RBO Lab's Pneumaticbox system

Name Last Update
airmassdata Loading commit data...
config Loading commit data...
lib/pneumaticbox Loading commit data...
scripts Loading commit data...
test Loading commit data...
.gitignore Loading commit data...
CMakeLists.txt Loading commit data...
CONTRIBUTORS.txt Loading commit data...
Changes.txt Loading commit data...
LICENCE.txt Loading commit data...
README.md Loading commit data...
drawing.svg Loading commit data...
package.xml Loading commit data...
setup.py Loading commit data...

A Python package for controlling RBO Lab's Pneumaticbox

This Python package provides two modules to communicate with and configure RBO Lab's Pneumaticbox

Installation

To use the package, simply use the standard package installation procedure:

sudo python setup.py install

Test the connection

To test the connection, disconnect the air supply of the pneumaticbox and call the following script from the command line:

export PNEUMATICBOX_IP=pneumaticboxname
python test/servertest_1.py

The script will pulse all 16 valves in sequences twice, then switch on all deflation valves. The LEDs on the valve array should light up in sequence too.

If nothing happens, check whether server and client use the same API version. To find version on the pneumaticbox, look at the log file:

user@client:~# ssh ubuntu@penumaticboxname
ubuntu@pneumaticboxname:~# sudo grep "API" /var/log/upstart/airserver.log

The Pneumaticbox

The Pneumaticbox consists of a valve array, an interface board, and an embedded computer (Beaglebone Black) running Ubuntu Linux. You can either run clients on the embedded computer, or you can run clients remotely using a TCP connection.

The airserver process

The airserver is a process on the embedded computer that manages I/O and configures and runs controllers for you. It is modeled on a circuit breadboard metaphor: There are named (uniquely numbered) signals, and you can tell the airserver which signals to connect the input and output of a specific controller instantiation. Every controller has a unique numbers too, by which they are referred to for configuring, activating, deactivating and deleting them.

The airserver usually gets started automatically on startup as an upstart service, so you only need to power up the pneumaticbox to be ready to use it.

The airserver listens on port 7650 and requires a DHCP server for bringing up the ethernet port. Alternatively, you can connect to the box using the usb port. The Pneumaticbox is configured to use 192.168.7.2 as its IP address on the USB network interface. Configure your computer to the static IP address 192.168.7.1.

Safety

The airserver contains as a basic safety feature a special state called "EMERGENCY SOFT STOP" When this state is triggered by the controller software, the client has to reset the server using MsgReset(). This also resets any configuration, so the client has to reconfigure the server. The state is triggered upon unrecoverable errors, such when a client advertises an incompatible API version to the server, or when a watchdog controller triggers.

Currently, the only way to determine if an EMERGENCY SOFT STOP was triggered, is to check the systems logs on the pneumaticbox (/var/logs/syslog or with 'journalctl -f').

Signals provided in the Server

Signals within the airserver are uniquely identified, time dependent float values and are internally referred to by a unique integer number. The pneumaticbox.api module provides human readable signal names the client should use. Some signals provide access to pressure sensor values (SIGNAL_PRESSURE_x), some signals control the state of the pneumatic valves (SIGNAL_VALVE_x). The client only is allowed to directly set signals named SIGNAL_CLIENT_x, by sending MsgSignalEvent messages.

Live display of signals

All Signals can be displayed remotely, have a look at scripts/monitor_pressures.py for how it is done.

Signals can be monitored up to the control loop frequency. Keep in mind that signals are transmitted across the network. While every signal value is timestamped, the message delivery may be delayed arbitrarily long.

Connecting to the airserver using the python package

To connect to the airserver, simply instantiate the pneumaticbox.io.Airserver object. By default, it will reset the airserver and announce the client's API version to the server upon connection.

Example Code:

>>> from pneumaticbox import utils, io, api
>>> airserver = utils.connectToDefaultPneumaticbox()
Connecting to pneumaticboxname:7650
>>> airserver.submit(api.MsgReset())  #example of sending a message
>>> airserver.submit([api.MsgReset(),api.MsgReset(),api.MsgReset()])  #example of sending a list of messages (or any other iterable object)

The airserver is able to receive messages on several connections simultaneously, but it does not isolate clients from each other. It up to the you to ensure that this use case is sensible.

Message semantics

Every message that is sent from client to server has a timestamp. This timestamp determines at which point in time the message takes effect. You can therefore instantly dispatch signal changes and rely on the airserver for timely execution.

Internally, the airserver always uses absolute times. If the timestamp value of a message is less than 10000 seconds, the airserver will assume it to be a time delay *relative to the time of message arrival". The latter simplifies programming and sidesteps clock synchronisation across your systems, but also adds jitter from network communication. Be careful when using relative timestamps across wireless networks!

Example Code:

>>> from pneumaticbox import io
>>> airserver = io.Airserver("pneumaticboxname") #connects to airserver, resets it, and advertises API version
Connecting to pneumaticboxname:7650
>>> airserver.submit(api.MsgReset())  #example of sending a message
>>> airserver.submit([api.MsgReset(),api.MsgReset(),api.MsgReset()])  #example of sending a list of messages (or any other iterable object)

The airserver is able to receive messages on several connections simultaneously, but the airserver itself does not isolate clients. It up to the client(s) to ensure that this use case is sensible.

Messages from Client to airserver/Pneumaticbox

A client can send various messages to the airserver. These messages are defined as objects in the pneumaticbox.api submodule, as are the signal names. The communication itself is completely asynchronous. There are 4 kinds of messages:

  • Configuration messages intended to be sent upon initial configuration:

    • MsgReset() used to completely reset the server state
    • MsgConfigurationXxx() family of messages used to configure components/controllers
    • MsgAdvertiseAPIVersionMsg() to announce the API version being used after connecting to the airserver
  • Runtime messages intended to control the runtime behavior

    • MsgSignalEvent() to change the value of client signals
    • MsgControllerActivate() to activate an already configured controller/component
    • MsgControllerDeactivate()to deactivate a controller/component (which stays configured and reactivatable)
  • Messages to handle signal monitoring

    • MsgSubscribe() to start periodic updates on a certain signal
    • MsgUnSubscribe() to stop periodic updates on a certain signal

In case a message's absolute timestamp is in the past, the airserver will execute the message immediately (best effort).

Setting Signals

The most often used message during operation is MsgSignalEvent(): it is used to tell the airserver to change the value of a certain signal at a certain time to a given value. Most times, this is used to provide client-controlled desired/target values to active controllers. The time of message dispatch is not relevant, the timestamp of the message is. MsgSignalEvent() can only be used to write to signals dedicated to the client, which are indicated by the CLIENT_SIGNAL_x name.

Timing

Internally, the airserver always uses absolute times. If the timestamp value of a message is less than 10000 seconds, the airserver will assume it to be a time delay *relative to the time of message arrival". The latter simplifies programming and sidesteps clock synchronisation across your systems, but also adds jitter from network communication. Be careful when using relative timestamps across wireless networks!

To execute a message immediately, set its timestamp to 0. Execution order of messages with the same timestamp is not guaranteed. If order is important, make sure to monotonically increase the timestamp (e.g. by using 1ns increments)

The client can subscribe to signals of the airserver, which will then send periodic updates on the chosen signal state. Any signal can be subscribed to, and any number of signals can be subscribed. This is subject to the network delay and throughput though, and no guarantee on a timely delivery is given by the server: it is not intended for implementing closed loop control. All signal states are delivered with an absolute timestamp (using the time on the pneumaticbox) to simplify synchronization with other data sources. This facility is intended for monitoring and for debugging, but not for fast, closed-loop control.

In case a message's absolute timestamp is in the past, the airserver will execute the message immediately (best effort).

Components

Components are individual functionalities, such as controllers or input providers. When activated, the components run concurrently. Components with lower ids are executed before components of higher ids. This can be used to improve delays, or give certain components a higher priority for writing signals.

Controllers

The airserver provides several types of Controllers, whose inputs and outputs can be freely connected to signals by the client. The controllers are executed in ascending order of their id. No checks are made on multiple controllers writing to the same sígnal. In this case, controllers with larger id's will override controllers with lower id's.

  • The Threshold controller reads an input signal and writes two valve signals. If the input signal value exceeds 0.5 the actuation channel is inflated, if it drops below -0.5 the actuation channel is deflated. If the value is in between, the valves stay closed and the actuation channel is isolated.
  • The PressureLimiter controller monitors a given pressure sensor and compares it to a fixed value or a the value of a reference signal. If the pressure exceeds either limit, the inflating valve is closed. Usually, this controller is run with a larger id than the controller it is intended to override.

Configuring Controllers

The airserver provides several types of Controllers, whose inputs and outputs can be freely connected to signals by the client. The controllers are executed in ascending order of their id. No checks are made on multiple controllers writing to the same sígnal. In this case, controllers with larger id's will override controllers with lower id's.

  • The Threshold controller reads an input signal and writes two valve signals. If the input signal value exceeds 0.5 the actuation channel is inflated, if it drops below -0.5 the actuation channel is deflated. If the value is in between, the valves stay closed and the actuation channel is isolated.
  • The WatchdogPressure controller monitors a given pressure sensor that is assumed to be connected to an actuation channel. If the value exceeds a certain threshold, the airserver is put into "EMERGENCY SOFT STOP" mode and the channels are deflated or disconnected (depending on the configuration)
  • The PressureLimiter controller monitors a given pressure sensor and compares it to a fixed value or a the value of a reference signal. If the pressure exceeds either limit, the inflating valve is closed. Usually, this controller is run with a larger id than the controller it is intended to override.

For each controller, the pneumaticbox.api module defines default configurations for convenience. Usually, one controller is responsible for one channel, which is connected to two valves and a pressure sensor. A typical setup is to configure a Threshold Controller for each used channel, a Pressure Limiter and a Pressure Watchdog:

from pneumaticbox import api, io
airserver = io.Airserver(pneumaticboxname)

#configure threshold controllers in default configuration, with id=0-3:
for i in range(4):
   airserver.submit(api.MsgConfigurationControllerThreshold(i))
#condigure pressure limiters with id=60-63:
for i in range(4):
   airserver.submit(api.MsgControllerConfigurationPressureLimiter(i, id_offset = 60, limit=30.0))
#configure Pressure Watchdogs with id=20-23:
for i in range(4):
   airserver.submit(api.MsgConfigurationWatchdogPressure(i, id_offset = 20, limit=80.0))

#active the configured controllers!
for i in range(4):
   airserver.submit(api.MsgControllerActivate(i+20) #watchdogs
for i in range(4):
   airserver.submit(api.MsgControllerActivate(i+60) #limiter
for i in range(4):
   airserver.submit(api.MsgControllerActivate(i)    #threshold controller

Safety components

  • The WatchdogPressure component monitors a given pressure sensor that is assumed to be connected to an actuation channel. If the value exceeds a certain threshold, the airserver is put into "EMERGENCY SOFT STOP" mode and the channels are deflated or disconnected (depending on the configuration)

Input components

  • The I2CSensorAD7147 component provides a method to provide data from an AD7147 capacitive-touch sensor. The data are provided on specific, predefined signals once the component is activated.

Message semantics

Every message that is sent from client to server has a timestamp. This timestamp determines at which point in time the message takes effect. You can therefore instantly dispatch signal changes and rely on the airserver for timely execution.

Subscription to signals by the client

The client can subscribe to signals of the airserver, which will then send periodic updates on the chosen signal state. Any signal can be subscribed to, and any number of signals can be subscribed. This is subject to the network delay and throughput though, and no guarantee on a timely delivery is given by the server: it is not intended for implementing closed loop control. All signal states are delivered with an absolute timestamp (using the time on the pneumaticbox) to simplify synchronization with other data sources. This facility is intended for monitoring and for debugging, but not for fast, closed-loop control.

to subscribe to a signal for 3 seconds, wait and then gather all sent data from the tcp buffer:

>>> import time; from penumaticbox import api, io
>>> airserver = io.Airserver(pneumaticboxname)
>>> airserver.submit([api.MsgSignalSubscribe(SIGNAL_PRESSURE_0, period=0.5, when=0.0), api.MsgSignalUnSubscribe(SIGNAL_PRESSURE_0, when=3.0)])
>>> time.sleep(3.1) #wait for data to be sent
>>> airserver.service_incoming_messages() #process all pending messages sent by the server
>>> print(airserver._serversignals)

Interfacing with ROS

There are two python scripts that act as ROS nodes and can be used to control the hand via e.g. a gamepad:

rosrun joy joy_mapper
python softhand_control_ros.py
python softhand_joy_mapper.py 

RBO Hand 2 Calibration

The folder scripts/calibration_rbohand2/ contains two scripts to calibrate the the inflation flow rate of the valves and the volume of the attached hand (including pipes). Before calling any of the other scripts, call the script: python reset_and_configure.py at least once.

Both scripts read from and write to the configuration file config-beagle.yaml. In it the calibration parameters are saved.

It may look something like this:

actuators:
  actuation_ratio: [0.02, 0.02, 0.02, 0.02, 0, 0, 0, 0]
channels:
  mass_observer_deflationpath:
    coefficient_Pout: [0.15342735417510978, -0.02848665271583819, 0.022279337545903086,
      -0.03768450822645565, 0.05872063634774477, -0.1268245843040016, 0, 0]
    coefficient_Psupply: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0]
    coefficient_bias: [-12.840719563257792, 5.969289283128802, -1.1009093908886598,
      6.266238537036287, -5.473069475432425, 16.62198257195595, 0, 0]
    coefficient_friction: [-7.088850052249315, -4.612543565278114, -2.539566076398506,
      -3.261383044401601, -7.2970585493379, -5.48203612213951, 0, 0]
    coefficient_injector: [-4.647394371759714, -3.612291521255701, -3.0622816441891585,
      -2.620844942649715, -2.0599600089438628, -1.5536053734497708, 0, 0]
    minimal_mass_change: [0, 0, 0, 0, 0, 0, 0, 0]
  mass_observer_inflationpath:
    coefficient_Pout: [-0.7409282806383594, -0.8186361123606106, -0.16594936843762728,
      0.10222648045557545, 0.16441589075769852, -0.19033508056465967, 0, 0]
    coefficient_Psupply: [-275.991981648695, 39.55367741114038, 344.26448584919285,
      -10.39663542467864, 85.60699158991696, -22.181218954659556, 0, 0]
    coefficient_bias: [28171.886266771584, -3867.409429880208, -34909.81240767617,
      1080.9692662973046, -8694.911745169946, 2287.285153020004, 0, 0]
    coefficient_friction: [-0.1762658148448395, -0.7568799696232915, -0.028201800656972864,
      -0.02933995101088449, -0.14144375958213118, -0.3735997296671485, 0, 0]
    coefficient_injector: [-620.9305514389082, -20.914043423332416, -5.220795516308142,
      -69.1969132036338, 5.5865240268462095, -19.092993352308227, 0, 0]
    minimal_mass_change: [0, 0, 0, 0, 0, 0, 0, 0]
  nominal_volume: [0.15933785362031438, 0.07507975168649884, 0.16130037322951424,
    0.16882574369045092, 0, 0, 0, 0]
  pressure_sensor_attachement:
    channel: [0, 1, 2, 3, 4, 5, -1, -1]
    supply: 6
os: {hostname: beagle19.local}
valves:
  lohm_deflate: []
  lohm_inflate: [12.421165329774166, 11.536518313119837, 12.324727976081226, 12.65889172230656,
    0, 0, 0, 0]
  minimum_deflation_period: []
  minimum_inflation_period: []
  nominal_pressure_supply: 320.0
  on_off_time_offset: [0, 0, 0, 0, 0, 0, 0, 0]

"Lohm" Valve Flow Rate Calibration Procedure

For the flow rate calibration procedure you need a well defined reference volume. We use 0.5l PET bottles. To connect the bottle, you can drill a 4mm hole in the cap and insert has a 4mm PE tube. Use a hot glue gun to fixate and seal the pipe on the inside of the cap.

  • Disconnect anything from the valve array channels
  • Attach the bottle on the first channel
  • Start the script with python claibrate_lohm.py

  • The script will first deflate all channels, and measure the supply pressure.

  • It will then inflate one channel for a defined time period (duration) and measure the pressure afterwards.

  • This is repeated for every channel (numChannels). The script pauses after each inflation, so you can reattach the bottle to the next channel.

  • Finally the script calculates the LOhm values for each channel and saves them to config-beagle.yaml.

Channel Volume Calibration Procedure

Before calibrating the channel volume, you first have to calibrate the flow rate of your valve array (see previous paragraph)!

  • Check assumed_volumes and target_pressures in the script calibrate_volumes.py. The volumes should be set to an approximate value of the actual channel volume in liter (e.g. 0.1l). When in doubt, rather underestimate the channel volume, which will result in a lower attained pressure. The target pressures should be set to pressures that inflate the hand to 20-30% of the maximum pressure (in kPA, e.g. 30kPa).

  • Attach hand and pipes to the valve array

  • Make sure the supply pressure is measured by the last pressure sensor.

  • Start the script with python claibrate_lohm.py

  • Check calculated inflation times for plausibility (should usually be below 0.2 seconds), then start the measurement

  • The script will inflate all channels and measure pressures.

  • The calculated adjusted volumes are stored in config-beagle.yaml.

  • You can rerun the volume calibration with the refined volumes. The script should now report pressures closer to target_pressures.

Rename Config-File and move into config-Folder

Finally you should rename the created config-beagle.yaml to match your pneumaticbox, e.g. config-beagle19.yaml. You can then place it in the python-pneumaticbox/config folder, from where you can load in in all your scripts.

You can, for example, read the nominal volumes like this:

with open(configfile, 'r') as f:        
    configuration = yaml.load(f)
    volumes = numpy.array(configuration["channels"]["nominal_volume"])[:4]