PID Control for Peristaltic Pump and Balance

  1. Introduction
  2. Imports
  3. Device Configuration
  4. PID Controller
  5. PID Control Loop
  6. Modification of PID Parameters

Introduction

This guide walks you through a sample code that demonstrates how to implement PID control to control the rate of a peristaltic pump based on the weight reading from a balance. The code performs tasks like setting up the devices, configuring PID parameters, and running control loops.

pid_controller_filling

Imports

The first step is to import all the necessary modules from the Aqueduct library.

# Import necessary modules
import time

from aqueduct.core.aq import Aqueduct
from aqueduct.core.aq import InitParams
from aqueduct.core.pid import Pid
from aqueduct.core.pid import PidController
from aqueduct.core.pid import Schedule
from aqueduct.core.pid import ScheduleConstraints
from aqueduct.core.pid import ScheduleParameters
from aqueduct.devices.balance import Balance
from aqueduct.devices.base.utils import DeviceTypes
from aqueduct.devices.pump.peristaltic import PeristalticPump

Initialization

Here, initialization parameters are parsed from the command line and used to initialize the Aqueduct instance.

# Parse the initialization parameters from the command line
params = InitParams.parse()

# Initialize the Aqueduct instance with the provided parameters
aq = Aqueduct(params.user_id, params.ip_address, params.port)

Device Configuration

Clear Setup

Any existing setup should be cleared before adding new devices.

# Clear the existing setup
aq.clear_setup()

Add Devices

This section adds a peristaltic pump and a balance to the setup and then retrieves the newly added devices.

# Add Peristaltic Pump and Balance devices
aq.add_device(DeviceTypes.PERISTALTIC_PUMP, PUMP_NAME, 1)
aq.add_device(DeviceTypes.BALANCE, BAL_NAME, 1)

# Retrieve the setup to confirm the added devices
aq.get_setup()

# Retrieve PeristalticPump and Balance instances
pp: PeristalticPump = aq.devices.get(PUMP_NAME)
bal: Balance = aq.devices.get(BAL_NAME)

PID Controller

PID Parameters and Schedule

Define the PID schedule parameters and constraints. Each PID controller can have multiple schedules. Constraints help to determine which schedule is used at each time step.

# Create a PID controller
params = ScheduleParameters()
params.kp = 10.0
params.kd = 5.0
constraints = ScheduleConstraints()
sched = Schedule(params, constraints)

Creating the PID Controller

The PID controller is then created with the defined schedules.

# Get process and control values for PID
process = bal.to_pid_process_value(index=0)
control = pp.to_pid_control_output(index=0)

pid = PidController("fill_controller", process, control, pid)

PID Control Loop

The code then runs a PID control loop to control the device for a duration.

# Perform PID control loop for a duration
start = time.monotonic_ns()
while time.monotonic_ns() < start + 30 * 1e9:
   # set the sim rate of change of the balance based on the pump rate
   bal.set_sim_rates_of_change(
      [
            pp.get_ml_min()[0] / 60,
      ]
   )
   time.sleep(0.01)

Modification of PID Parameters

Finally, the code shows how to modify PID parameters and rerun the control loop.

# Change PID parameters and setpoint
pid.pid.schedule[0].change_parameters(kp=30, kd=10)
pid.change_setpoint(40)

Here's the full code:

"""
Demo code showcasing the usage of Aqueduct for PID control of a Peristaltic Pump and Balance.
"""
# Import necessary modules
import time

from aqueduct.core.aq import Aqueduct
from aqueduct.core.aq import InitParams
from aqueduct.core.pid import Pid
from aqueduct.core.pid import PidController
from aqueduct.core.pid import Schedule
from aqueduct.core.pid import ScheduleConstraints
from aqueduct.core.pid import ScheduleParameters
from aqueduct.devices.balance import Balance
from aqueduct.devices.base.utils import DeviceTypes
from aqueduct.devices.pump.peristaltic import PeristalticPump

# Parse the initialization parameters from the command line
params = InitParams.parse()

# Initialize the Aqueduct instance with the provided parameters
aq = Aqueduct(params.user_id, params.ip_address, params.port)

# Perform system initialization if specified
aq.initialize(params.init)

# Set a delay between sending commands to the pump
aq.set_command_delay(0.05)

# Define names for devices
PUMP_NAME = "PUMP"
BAL_NAME = "BALANCE"

# Clear the existing setup and add Peristaltic Pump and Balance devices
aq.clear_setup()
aq.add_device(DeviceTypes.PERISTALTIC_PUMP, PUMP_NAME, 1)
aq.add_device(DeviceTypes.BALANCE, BAL_NAME, 1)

# Retrieve the setup to confirm the added devices
aq.get_setup()

# Retrieve PeristalticPump and Balance instances
pp: PeristalticPump = aq.devices.get(PUMP_NAME)
bal: Balance = aq.devices.get(BAL_NAME)

# Create a start command for the Peristaltic Pump
c = pp.make_start_command(
    mode=pp.MODE.Continuous,
    direction=pp.STATUS.Clockwise,
    rate_value=1,
    rate_units=pp.RATE_UNITS.MlMin,
)
commands = pp.make_commands()
pp.set_command(commands, 0, c)
pp.start(commands=commands)

# Get process and control values for PID
process = bal.to_pid_process_value(index=0)
control = pp.to_pid_control_output(index=0)

# Create a PID controller
params = ScheduleParameters()
params.kp = 10.0
params.kd = 5.0
constraints = ScheduleConstraints()
sched = Schedule(params, constraints)
pid = Pid(20)
pid.output_limits = (0, 100)
pid.add_schedule(sched)
pid.enabled = True
pid = PidController("fill_controller", process, control, pid)

# Set noise level for simulation
bal.set_sim_noise(
    [
        0,
    ]
)

# Wait for some time
time.sleep(1)

# Create PID controller on Aqueduct
aq.create_pid_controller(pid)

# Perform PID control loop for a duration
start = time.monotonic_ns()
while time.monotonic_ns() < start + 30 * 1e9:
    bal.set_sim_rates_of_change(
        [
            pp.get_ml_min()[0] / 60,
        ]
    )
    time.sleep(0.01)

# Change PID parameters and setpoint
pid.pid.schedule[0].change_parameters(kp=30, kd=10)
pid.change_setpoint(40)

# Perform PID control loop again for a duration
start = time.monotonic_ns()
while time.monotonic_ns() < start + 30 * 1e9:
    bal.set_sim_rates_of_change(
        [
            pp.get_ml_min()[0] / 60,
        ]
    )
    time.sleep(0.01)

# Delete the created PID controller
pid.delete()