pH controlApplication Note

Real-time pH control with Daisy: hold a target with small, steady doses

Read a Hamilton ARC pH probe with a Daisy Process, dose acid and base with two Peri P pumps, and watch the live chart as the controller holds your target.

Read time ~11 minModules Process, Peri P

Summary

Holding a solution at a target pH takes frequent, small corrections. This note shows how to use a Daisy Process with a pH probe and two Peri P pumps to dose acid and base automatically, with a live chart so you can watch the controller work.

GoalHold a solution at a target pH, here 7.0, by dosing small amounts of acid and base automatically.
ApproachRead pH about every 8 s, apply a deadband and PID rule, then dose a small, bounded volume from the acid or base Peri P.
ModulesDaisy Process with a Hamilton ARC pH probe, and two Daisy Peri P pumps, one for acid and one for base.

Why pH is hard to hold steady

pH is a logarithmic scale, so the same drop of acid or base can change very little at one pH and a great deal at another.

Many workflows depend on a steady pH, including cell culture and bioreactors, buffer preparation, enzyme reactions and titrations. In bioreactors, pH is commonly held by adding base or acid, or by sparging gases. The difficulty is that a solution does not respond evenly to each addition. For strong acids and bases, the titration curve is very steep near neutrality, and pH is far more sensitive to a change in composition around neutrality than elsewhere.

02468101214 00.51.01.52.0 Base added (1.0 = amount needed to neutralize the acid) pH Target, pH 7 Steepest point A very small addition changes the pH by several units
Figure 1. Illustrative titration curve for a strong acid neutralized by a strong base, with no buffer. The pH values come from the standard acid-base equilibrium calculation. The curve is steepest near pH 7, so a very small addition of base there changes the pH by several units.

Real samples often contain buffers, which make the pH change more gradually than in Figure 1. The same approach still applies: dose a small amount, wait, then measure. Two delays make this harder. Reagent must mix and react before the probe can report the true pH, and the probe and its electronics respond with some lag. A controller that doses faster than the solution responds will overshoot, then correct in the other direction, which shows up as oscillation. Mixing the solution helps, because adding base without thorough mixing can create local regions of high pH.

Scope of this note

This note covers the control loop and how it is tuned for a small, stirred volume. Reagent concentration, beaker volume and buffering all change how far each dose moves pH, so expect to retune the parameters for your own solution.

How the controller works

Each cycle the script reads pH, decides whether dosing is allowed, sizes a small dose, doses, and then waits.

1Read pHabout every 8 sDaisy Process2Compute errore = target − pH3Deadband checkdose only ifdosing is allowed4Size the doseu = Kp·e + Ki·I + Kd·Dlimited to 1 to 10 drops5Dosebase if u > 0acid if u < 0Peri P × 26Wait8 s to mix,then repeatRepeat for the set number of readings
Figure 2. The control loop. The Daisy Process reads pH and the two Peri P pumps dose.

A deadband that rests the pumps

Once pH is close enough to the target, there is no benefit in chasing sensor noise. The script stops dosing when the error is within ±0.1 pH units of the target. It does not start dosing again until pH has drifted beyond ±0.5. Between the two limits it keeps whatever state it was in, so a controller that is still correcting keeps going, and one that has settled stays quiet. Figure 3 shows how these zones map onto the live chart.

7.57.17.06.96.5pHBeyond ±0.5: dosing onpH above target: acid doseBetween: keeps its stateDosing continues if already onInside ±0.1: dosing offClose enough, pumps restBetween: keeps its stateStays off until beyond ±0.5Beyond ±0.5: dosing onpH below target: base dose
Figure 3. Control zones around the 7.0 target, using the same colors as the live chart: target in yellow, the ±0.1 deadband as gray dashes and the ±0.5 re-arm band in cyan.

PID output with bounded doses

The controller output is u = Kp·e + Ki·I + Kd·D, where e is the target minus the measured pH, I is the integral of recent error and D is its rate of change [1]. With kp = 0.8, ki = 0.01 and kd = 0, the script is mainly a proportional controller with a small integral term that corrects a persistent offset. Set kd above zero to add a derivative term that damps the response when pH is already moving toward the target.

Keep ki small. The script sums the error in pH·seconds over the last 10 readings, which is about 72 s. If the error stays the same for that long, the integral term adds ki × 72 times the error, which is 0.72 times the error at ki = 0.01. That is already close to the proportional term (0.8), so the integral term roughly doubles the response when pH is stuck away from the target, and it fades once the error leaves the window. A ki of 0.1 would be nine times the proportional term and likely overshoot, because a dose takes time to show up in the reading. If pH drifts and will not settle, raise ki gradually. If it oscillates around the target, lower it. Narrowing epsilon is the other way to hold pH closer to the target.

A positive u means pH is below target, so the base pump runs. A negative u runs the acid pump. The dose volume is |u| × 0.10 mL, limited to between one drop (0.033 mL) and ten drops (0.33 mL). It is delivered at 7.3 mL/min, so the longest dose blocks the loop for about 2.7 s. Table 1 works through a few readings.

The loop timing is set by the script. Each cycle waits 8 s, takes a reading and, if a dose is needed, runs the pump and waits for it to finish. A cycle therefore lasts 8 s plus the dose time, which is up to about 11 s, so the solution always has at least 8 s to mix before the next reading. The 5 s cooldown is a safeguard between doses. With the 8 s interval it never delays a dose, and it only takes effect if you shorten INTERVAL_S below 5 s.

Measured pHError eOutput uDose givenDose time
2.0+5.0+4.0Base, 0.330 mL (limited to 10 drops)2.7 s
6.5+0.5+0.4Base, 0.040 mL0.3 s
6.8+0.2+0.16Base, 0.033 mL (raised to 1 drop)0.3 s
7.05−0.05Inside ±0.1No dose–
8.0−1.0−0.8Acid, 0.080 mL0.7 s
Table 1. Doses the script calculates for several readings at the 7.0 target, using kp = 0.8 and kd = 0 on a first reading. The integral term is zero then, and with ki = 0.01 it adds to u only when the error persists over later readings.
Dose speed follows the calibration

The script delivers each dose at 50% of the pump’s maximum flow, which is 2.5 times the calibration value. With the 5.84 mL/min calibration used here, that is 7.3 mL/min. Slower delivery reduces overshoot, so increase the speed only if doses are too slow to register.

Daisy instruments for pH control

Daisy Process: the reading

Daisy Process connects to a Hamilton ARC pH probe and measures the pH of your solution. Each measurement returns the pH, the probe signal in millivolts and the temperature. The module has four probe channels, so it can also read other ARC probes such as dissolved oxygen, dissolved CO2, ORP, conductivity or cell density on its other channels. This note reads one pH probe, and the script’s pH_channel setting tells the Daisy Process which channel to read. The Hamilton Polilyte Plus Arc used in this note is specified for pH 0 to 14 with a sensitivity of about 57 to 59 mV/pH at 25 °C [2].

4
probe channels per Daisy Process
6
stainless-steel rollers in a Peri P head
2
Peri P pumps, one for acid and one for base

Daisy Peri P: the dose

Daisy Peri P is a precision peristaltic pump with six stainless-steel rollers and pre-stretched tubing for reproducible delivery. Each pump is calibrated by entering the flow you measure at 60 rpm for the installed tubing, and the script then dispenses by volume. Recalibrate whenever the tubing is changed or its cartridge pressure shifts. Use tubing that is compatible with your acid and base, which for Peri P is PharMed BPT or silicone.

Dose size matters more than speed

At 7.3 mL/min a single drop (0.033 mL) takes about a quarter of a second, so the pumps can deliver very small amounts and the loop stays responsive between doses.

Why use Daisy for pH control

A dedicated pH controller is a good choice when you need a standard loop and nothing else. Daisy is a good fit when you want equipment that can be reconfigured. The Daisy Process and two Peri P pumps in this note can run a pH protocol today, then be set up for a different protocol tomorrow. Here is how it compares with common alternatives.

ApproachWhere it falls shortWhat Daisy adds
Manual titration or a timed syringe pumpDoses are not tied to the measured pH, so overshoot is common near neutralEvery dose is sized from the latest reading, down to a single 0.033 mL drop
Dedicated pH or bioreactor controllerControl logic is usually fixed, so unusual rules are hard to addThe control law is a short Python script, so deadband, dose limits and cooldown are all editable
Custom rig (microcontroller, separate meter and pumps)Each device needs its own driver, wiring and plotting codeProbe and both pumps share one USB chain and one script, with a live chart included

Because the modules are separate, nothing is locked to pH. The same Peri P pumps, for example, can be set up for droplet generation, and the Daisy Process can read other ARC probes on its other channels.

Example setup

The Daisy Process and both Peri P pumps are daisy-chained together, and the leader Daisy connects to the computer by USB (Figure 4). Each Peri P draws from its own reservoir and doses into the same stirred beaker, where the pH probe measures the result.

Daisy ProcesspH, mV, TEMPERATUREAcid reservoirDaisy Peri PACIDBase reservoirDaisy Peri PBASEStirred beakerSolution to controlDaisy chainVP8 cableHamilton ARCpH probeDosing tubes and probe sitin a well-mixed regionof the beaker
Figure 4. A Daisy Process reads the pH probe while two Peri P pumps dose acid and base.

What you need

Daisies

ItemQuantityNotes
Daisy Process (SKU DPROC-04)1Reads pH, mV and temperature from the Hamilton Polilyte Plus Arc pH probe
Daisy Peri P (SKU DPERP)2One for acid and one for base

Other items

ItemQuantityNotes
Hamilton Polilyte Plus Arc pH probe and VP8 cable1Connects to a channel of the Daisy Process
Peri P tubing and fittings2 setsPharMed BPT or silicone, compatible with your acid and base
Acid and base reservoirs2Concentrations chosen for your solution
Beaker1Stir bar or another way to mix

Connect and check

  1. Connect the probe first. Connect the Hamilton ARC pH probe to the VP8 cable, and the cable to a channel of the Daisy Process. If it is not on channel 1, update pH_channel in the script. Always connect a probe before powering the module to prevent an error, and power down before disconnecting it.
  2. Set up the beaker and tubing. Load the tubing into each Peri P first, then place the input end in its reservoir and the output end in the beaker. Place the probe in the stirred solution and aim the dosing tubes at a well-mixed region, away from the probe tip. Make all tubing connections before powering on the pumps.
  3. Chain the modules, then power on. Daisy-chain the Daisy Process and both Peri P pumps together before powering anything on, with the Daisy Process as the leader. Connect the leader Daisy to the computer with the micro USB to USB-C cable. Then power on the modules. The chain order sets each instrument’s index in the script:
    IndexDaisyScript name
    0Daisy ProcesspH_meter
    1Daisy Peri P, acidacid_pump
    2Daisy Peri P, basebase_pump
  4. Check the instrument list and probe. Open the Daisy Controller Software, then click the instrument refresh button. Confirm the pH probe reports a satisfactory quality indication and check when its last calibration occurred.

Script

The script has four parts, and each part builds on the one before it. The first sets the output file, connects to the instruments, calibrates and primes the pumps and takes a test reading. The second sets the control parameters. The third sets up the live plot, and the fourth runs the control loop. Table 2 lists the settings to check before you run it, and the control parameters are described in the sections above.

ParameterValueWhat it does
file_path, file_nameYour folder, pH_output.txtWhere the log file and the saved plot are written
pH_channel1Daisy Process channel the pH probe is connected to. Default is 1, so set it to 2, 3 or 4 if you use another channel
calibrate()5.84 mL/minFlow you measure at 60 rpm for each pump and its tubing. See below
target_pH7.0The pH to hold
Table 2. Settings to check before running the script.

1. Connect, calibrate and prime

Before you run it, change file_path from the placeholder to a real folder on your computer. The log file pH_output.txt and the final plot image pH_plot.png are saved there. Set pH_channel to the Daisy Process channel your probe is on.

The value you pass to calibrate() is the flow each pump delivers at 60 rpm with your tubing installed. To find the calibration value for your tubing and solution, use the Daisy Peristaltic Pump quick start guide. The 5.84 mL/min here is only an example. Once the pumps are calibrated, prime each line so reagent reaches the beaker before the loop starts.

"""Real-time pH control with Daisy and Peri P pumps
Reads pH from a Hamilton ARC probe on the Daisy Process, then doses acid or
base with two Peri P pumps to hold a target pH. A live matplotlib chart shows
the readings, and every reading is logged to a text file.
Runs in the Daisy Controller Software's Python notebook (uses self.my_interface).
"""

# ---- Import and set the output file ----
# Packages to install
import time
from IPython.display import display, HTML

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

from datetime import datetime
from collections import deque


# Set your output file path and output file name
file_path = "/Users/yourusername/Documents/"
file_name = "pH_output.txt"

# ---- Print instruments found ----
print("Instruments found:")
for position, (code, inst) in enumerate(zip(self.my_interface.instruments_list[0], self.my_interface.groups[0].inst)):
    print(f"  [{position}]  {code}: {inst.name}")

# ---- Assign your instruments ----
# Define your instruments
pH_meter = self.my_interface.groups[0].inst[0]     # Daisy Process
acid_pump = self.my_interface.groups[0].inst[1]    # Daisy Peri P, acid
base_pump = self.my_interface.groups[0].inst[2]    # Daisy Peri P, base

# ---- Calibrate the Peri P pumps (ml/min measured at 60 RPM) ----
# Peri P
# Set the calibration (ml/min @ 60 rpm) for each Daisy Peri P
acid_pump.calibrate(5.84)
base_pump.calibrate(5.84)

print(f"Acid pump calibration set to {acid_pump.calibration_speed} ml/min @ 60 RPM")
print(f"Base pump calibration set to {base_pump.calibration_speed} ml/min @ 60 RPM")

# ---- Prime both Peri P pumps ----
# Parameters for the Daisy Peri Ps are flow rate (ml/min), total volume (ml), and wait
# Wait allows for the pump to finish running before running the next line

acid_pump.run(6.0, 1.5, True)

base_pump.run(6.0, 1.5, True)

# ---- Take a test pH reading ----
# pH meter

# Set the channel your pH meter is connected to
# Default is channel 1, if using 2-4 specify channel
pH_channel = 1

# Take a test measurement of your pH meter
pH, mV, T = pH_meter.measure_parameters(pH_channel)

print(f"pH: {pH:.3f} mV: {mV:.3f} Temperature: {T:.2f} °C")

2. Set the control parameters

# ---- Set control parameters ----
# Set your desired pH
target_pH = 7.0

# Set your inner band, epsilon, the margin for error
epsilon = 0.1

# Set your outer band, dosing re-arms outside this band, must be ≥ epsilon
outer_band = 0.5


# Set your PID gains
kp = 0.8
ki = 0.01  # small integral term corrects steady-state offset; increase gradually if pH drifts and won't settle
kd = 0.0

# Set your dosing parameters
DROP_ML           = 0.033              # ~1 drop in ml
ACID_MAX_ML_MIN   = 2.5 * acid_pump.calibration_speed  # Peri P max: 2.5 × calibration speed
BASE_MAX_ML_MIN   = 2.5 * base_pump.calibration_speed
scale_ml_per_unit = 0.10               # dose volume = |PID output| * scale
max_ml            = 10.0 * DROP_ML     # maximum dose per event (~0.33 ml)
min_ml            =  1.0 * DROP_ML     # minimum dose per event (~0.033 ml)
req_rate_ml_min   = 0.5 * min(ACID_MAX_ML_MIN, BASE_MAX_ML_MIN)  # ~50% of max; slower delivery reduces overshoot, increase if doses are too slow to register
cooldown_s        = 5.0                # minimum seconds between doses

# Current pH control parameters configured
print("Parameters configured.")
print(f"  Target pH: {target_pH} ± {epsilon} (re-arms at ±{outer_band})")
print(f"  PID: kp={kp}, ki={ki}, kd={kd}")
print(f"  Acid pump max: {ACID_MAX_ML_MIN:.2f} ml/min | Base pump max: {BASE_MAX_ML_MIN:.2f} ml/min")
print(f"  Dose range: {min_ml:.4f} – {max_ml:.4f} ml at {req_rate_ml_min:.2f} ml/min")

3. Set up the live plot

This cell builds a matplotlib chart that updates with each reading. It plots pH as a green trace over a sliding 300 s window, with the target in yellow, the ±0.1 deadband in gray dashes and the ±0.5 re-arm band in cyan. This cell only sets up the chart.

# ---- Set up the real-time pH plot ----
# X-axis will shows 300 seconds (5 minutes) of dispensing events
WINDOW_SEC = 300.0


fig, ax = plt.subplots(figsize=(10, 8))
ax.set_facecolor('black')
fig.patch.set_facecolor('black')


# Graph axis labels and settings
ax.set_xlabel('Time (s)', color='white')
ax.set_ylabel('pH', color='white')
ax.set_title('Real-Time pH Control', color='white')
ax.tick_params(colors='white')
for spine in ax.spines.values():
    spine.set_edgecolor('white')
ax.set_ylim(0, 14)
ax.set_xlim(0, WINDOW_SEC)
ax.grid(True, alpha=0.3, color='white')

# Guide lines including: target, epsilons, outer bands
line_target      = ax.axhline(target_pH,              color='yellow',    linewidth=1,   linestyle='-')
line_upper_eps   = ax.axhline(target_pH + epsilon,     color='lightgray', linewidth=0.8, linestyle='--')
line_lower_eps   = ax.axhline(target_pH - epsilon,     color='lightgray', linewidth=0.8, linestyle='--')
line_upper_outer = ax.axhline(target_pH + outer_band,  color='cyan',      linewidth=0.8, linestyle='--')
line_lower_outer = ax.axhline(target_pH - outer_band,  color='cyan',      linewidth=0.8, linestyle='--')

# Plot pH data
ph_line, = ax.plot([], [], 'o-', color='lime', linewidth=1, markersize=3)

# Graph legend
legend_elements = [
    mpatches.Patch(color='yellow',    label=f'Target pH ({target_pH})'),
    mpatches.Patch(color='lightgray', label=f'Epsilon (±{epsilon})'),
    mpatches.Patch(color='cyan',      label=f'Outer band (±{outer_band})'),
]
ax.legend(handles=legend_elements, loc='upper right',
    facecolor='black', labelcolor='white', fontsize=8)

plt.tight_layout()
plt.close(fig)  # suppress auto-display; loop cell creates the display handles
print("Chart configured. Run the control loop cell to start.")

4. Run the controller

Each reading and dose is shown in a log below the chart, written to the output file and added to the plot. To stop early, click the stop button in the toolbar. The output file and a pH_plot.png image of the graph are saved whether the loop finishes or is stopped.

# ---- Run the real-time pH controller ----
# ---------- Control loop state ----------
err_hist     = deque(maxlen=16)  # stores (time, error) for PID integral/derivative
last_dose_ts = 0.0
can_dose     = True              # hysteresis flag

xs = deque()   # time values for plot
ys = deque()   # pH values for plot

INTERVAL_S   = 8      # seconds between measurements
N_ITERATIONS = 300    # total number of measurements

t0 = time.monotonic()
log_lines = []

chart_handle = display(fig, display_id=True)
log_handle   = display(HTML(""), display_id=True)

with open(file_path + file_name, "a") as f:
    log_lines.append(f"Starting pH control loop ({N_ITERATIONS} readings, {INTERVAL_S}s interval)")
    log_lines.append(f"Target: pH {target_pH} ± {epsilon} | Output: {file_path + file_name}")
    log_lines.append("-" * 60)
    log_handle.update(HTML("<pre style='background:#111;color:#ccc;padding:10px;border-radius:4px;font-size:12px;font-family:monospace;margin-top:6px'>" + "\n".join(log_lines) + "</pre>"))

    try:
  for i in range(N_ITERATIONS):

      # ---- Wait ----
      time.sleep(INTERVAL_S)

      # ---- Measure ----
      pH, mV, T = pH_meter.measure_parameters(pH_channel)
      t = time.monotonic() - t0
      timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
      log_line = f"[{timestamp}] t={t:.1f}s  pH={pH:.3f}  mV={mV:.3f}  T={T:.2f}°C"
      log_lines.append(log_line)
      f.write(log_line + "  ")

      # ---- PID error ----
      e       = target_pH - pH       # positive = need base, negative = need acid
      abs_err = abs(e)

      # ---- Hysteresis flag update ----
      # Disarm when inside epsilon; re-arm only when beyond outer_band
      if abs_err <= epsilon:
          can_dose = False
      elif abs_err >= outer_band:
          can_dose = True
      # else: between epsilon and outer_band, keep previous state

      # ---- Error history ----
      err_hist.append((t, e))

      # ---- Integral: trapezoid rule over last 10 points ----
      I = 0.0
      pts = list(err_hist)[-10:]
      for j in range(1, len(pts)):
          (t0_p, e0_p), (t1_p, e1_p) = pts[j-1], pts[j]
          dt = max(1e-6, t1_p - t0_p)
          I += 0.5 * (e0_p + e1_p) * dt

      # ---- Derivative: average of last two finite differences ----
      D = 0.0
      if len(err_hist) >= 3:
          (t2, e2), (t1, e1), (t0_h, e0_h) = err_hist[-1], err_hist[-2], err_hist[-3]
          d1 = (e2 - e1)   / max(1e-6, t2 - t1)
          d0 = (e1 - e0_h) / max(1e-6, t1 - t0_h)
          D  = 0.5 * (d1 + d0)
      elif len(err_hist) == 2:
          (t1, e1), (t0_h, e0_h) = err_hist[-1], err_hist[-2]
          D = (e1 - e0_h) / max(1e-6, t1 - t0_h)

      # ---- PID output ----
      u = kp * e + ki * I + kd * D

      # ---- Hysteresis-gated dosing ----
      need_dose = can_dose and (abs_err > epsilon)
      now = time.monotonic()
      if need_dose and (now - last_dose_ts) >= cooldown_s:
          direction = "base" if u > 0 else "acid"
          vol = min(max_ml, max(min_ml, abs(u) * scale_ml_per_unit))
          last_dose_ts = now

          if direction == "acid":
              eff_rate = min(req_rate_ml_min, ACID_MAX_ML_MIN)
              acid_pump.run(eff_rate, vol, wait=True)
              dose_msg = f"ACID  {vol:.4f} ml @ {eff_rate:.2f} ml/min"
          else:
              eff_rate = min(req_rate_ml_min, BASE_MAX_ML_MIN)
              base_pump.run(eff_rate, vol, wait=True)
              dose_msg = f"BASE  {vol:.4f} ml @ {eff_rate:.2f} ml/min"

          log_lines.append(f"  → Dose {dose_msg}")
          f.write(f"dispensing {dose_msg}\n")
      else:
          f.write("pH in range\n")

      # ---- Update plot ----
      xs.append(t)
      ys.append(pH)

      # Trim to window
      while xs and xs[0] < t - WINDOW_SEC:
          xs.popleft()
          ys.popleft()

      ph_line.set_data(list(xs), list(ys))
      if t < WINDOW_SEC:
          ax.set_xlim(0, WINDOW_SEC)
      else:
          ax.set_xlim(t - WINDOW_SEC, t)
      chart_handle.update(fig)
      log_handle.update(HTML("<pre style='background:#111;color:#ccc;padding:10px;border-radius:4px;font-size:12px;font-family:monospace;margin-top:6px'>" + "\n".join(log_lines) + "</pre>"))

    except KeyboardInterrupt:
  print("\nLoop stopped manually.")
    finally:
  print("\nControl loop complete.")
  fig.savefig(file_path + "pH_plot.png", dpi=150, bbox_inches='tight', facecolor='black')
  print(f"Graph saved to {file_path}pH_plot.png")

This version runs in the Daisy Controller Software’s Python notebook. For standalone Python, use the daisylab library.

Clean the tubing after the run

Flush each line. First remove the input tubing from the solution and run the pump to empty the line. Then place the tubing in clean water and run the pump to rinse. Finally remove it from the water and run the pump to clear the line with air. Each step uses the same call as priming, for example acid_pump.run(6.0, 1.5, wait=True).

Key takeaways

  1. Small doses, steady pHBounded drops, a deadband and a wait between readings keep the controller from overshooting a sensitive solution.
  2. A live view built inThe script draws a live plot and saves a log file and a plot image for every run, so you can watch the controller work and review the run afterwards.
  3. Reconfigurable hardwareThe same Process and Peri P modules go on to buffer preparation, titration and other fluid-handling tasks after the experiment.

Questions or want to try this with your solution? Email [email protected] to request a demo or a quote.

References

  1. Åström K.J. and Murray R.M. (2008), Feedback Systems: An Introduction for Scientists and Engineers, Chapter 10, PID Control. Princeton University Press, ISBN 978-0-691-13576-2. Free online via the Caltech library: https://authors.library.caltech.edu/records/yzs24-xsx88 (accessed 22 October 2026).
  2. Hamilton Company. Polilyte Plus Arc pH sensor product page. hamiltoncompany.com/sensors/ph-sensors/polilyte-plus/polilyte-plus-arc (accessed 22 October 2026).

Control pH with Daisy.

Talk to our team about your solution, reagents and target range, or request a demo.

Request a demo or quote