Dissolved Oxygen Monitoring for Shrimp Ponds: Optical vs Galvanic Sensors, Salinity Compensation, and Aerator Control That Beats the 3 AM Crash

At four in the afternoon the pond reads 7.2 mg/L of dissolved oxygen and the farm manager, who checked it by hand with a portable meter, goes home reassured. At 3:40 the next morning the same pond is at 2.1 mg/L, the shrimp are crowding the edges where the water is shallowest and best mixed, and the first dead animals are floating by the time anyone arrives. The meter was accurate. The schedule was wrong. Oxygen in a pond does not hold still between visits, and the hours that matter most are the ones nobody is awake to measure.
What follows is a story about building an IoT system for that problem, as it usually unfolds on a first pilot in a brackish-water shrimp cluster in coastal India: three wrong turns, each of which looked fine on the bench, and what fixed them. The story is a composite assembled from representative pilot experience, with no client named, and the numbers marked as illustrative are illustrations. The physics and the sensor behaviour are not illustrations. The sensor technologies, the saturation calculations, and the control logic are the parts of this story that carry to any pond.
The thread running through it is simple: the measurement, the compensation, and the control each fail in their own way, and a pond monitoring system is only as good as the weakest of the three.
A dissolved oxygen system exists to be right at 3 AM, not at 3 PM. Design every part, from the probe to the contactor, around the worst hour of the night.
Why Oxygen Falls Before Dawn
Pond oxygen follows a daily cycle driven by three processes. Photosynthesis by phytoplankton adds oxygen during daylight, often pushing the water above 100 percent saturation by late afternoon. Respiration by the shrimp, the plankton, and the bacteria in the bottom sediment consumes oxygen around the clock. Exchange with the air moves oxygen in or out depending on whether the water is under- or over-saturated, and aerators speed it up. After sunset, photosynthesis stops and respiration continues, so oxygen declines through the night and reaches its minimum shortly before sunrise. Several factors deepen that minimum:
Factor | Effect on the pre-dawn minimum |
Dense phytoplankton bloom | More oxygen produced by day, but far more consumed at night; a bloom that crashes can drain a pond quickly |
Cloudy day | Less photosynthesis, so the pond starts the night with less oxygen in reserve |
Heavy feeding | More waste, more bacterial respiration, higher oxygen demand through the night |
High water temperature | Lowers the water's capacity to hold oxygen and raises the animals' metabolic demand |
Higher salinity | Lowers the water's capacity to hold oxygen |
Large animal biomass late in the crop | Respiration scales with the biomass in the pond |
The capacity effects are worth putting numbers on, because they are computed, not guessed. The saturation concentration of oxygen in water at normal atmospheric pressure depends on temperature and salinity. At 28 degrees Celsius, fresh water holds about 7.8 mg/L at 100 percent saturation. At 30 parts per thousand salinity, typical of many brackish shrimp ponds, the same 100 percent saturation corresponds to about 6.6 mg/L. At 32 degrees and 30 parts per thousand it is about 6.2 mg/L. The pond has less oxygen to start with and less margin to fall.
Most shrimp farming guidance treats concentrations below roughly 4 to 5 mg/L as stressful for the animals, with growth and feed conversion suffering before any mortality appears, and the lower the concentration falls the faster the damage accumulates. A pond that falls from 6.6 mg/L to 4 mg/L has used about 40 percent of its available buffer, which at a respiration-driven decline can take a few hours.
A manual measurement schedule built around daytime visits samples the high point of the cycle. Many farms measure twice a day; a consultant visiting a handful of ponds may measure once. Neither sees the minimum.
A membrane probe that consumes oxygen reads low in still water, which is exactly the pond at night.
The First Probe Lied in Still Water
The pilot began with the cheapest sensor that appeared to work: an electrochemical probe of the familiar membrane-covered type. On the bench in a bucket of aerated water, it agreed with a reference meter within a few percent. It went into the first pond for four days of testing, and the pilot team compared its readings with a handheld meter once a day.
The comparison was uncomfortable. In the aerated area near an aerator, the probe tracked the reference well. At a quiet corner of the pond, where water barely moved, it read as much as 30 percent below the handheld value. Moved around the pond, the probe's readings seemed to depend more on where it was than on what the oxygen was doing.
The cause is a property of the sensing principle itself. Membrane-covered electrochemical probes (the galvanic and polarographic types) work by consuming oxygen: oxygen diffuses through a membrane and is reduced at an electrode, producing a current proportional to the oxygen's partial pressure. The sensor therefore removes oxygen from the thin layer of water next to the membrane. In water that moves across the membrane, fresh water replaces the depleted layer and the reading is right. In still water, the layer next to the membrane is depleted faster than diffusion refills it, and the sensor reads low. Manufacturers specify a minimum flow velocity across the membrane for this reason. A pond bund in the middle of the night, with the aerators off, is exactly the condition that violates the specification.
Three other problems showed up within the first week. The membrane fouled with a biofilm after a few days in nutrient-rich water, which slowed the response and shifted the reading. The electrolyte and membrane needed periodic replacement, which no one at the farm wanted to do. And the calibration drifted enough that readings needed correction against a handheld reference every few days.
A system that needs a technician to calibrate it twice a week will be calibrated twice in the first month and never again, and farm staff will quietly stop trusting the numbers.
What Optical Sensing Fixes
The second design used an optical dissolved oxygen sensor, which measures oxygen through a different physical principle. A luminescent dye on the sensing surface is excited by a pulse of light, and it emits light back; oxygen molecules quench the luminescence, shortening its decay time and reducing its intensity. The sensor measures the decay time or phase shift, which relates to oxygen concentration through a calibration, and applies temperature compensation using its built-in temperature sensor. Oxygen is not consumed in the measurement.
That one difference removes the flow dependence. An optical sensor in still water reads the same as one in moving water, because nothing is being used up at the surface. Vendors of optical DO sensors for aquaculture also describe lower calibration frequency compared with galvanic probes, and designs with a replaceable sensing cap or patch that can be swapped on site, which turns a recurring service task into a small consumable.
Property | Galvanic probe | Polarographic probe | Optical (luminescent) probe |
Principle | Consumes oxygen at an electrode; spontaneous current | Consumes oxygen; needs polarising voltage and warm-up | Oxygen quenches luminescence; measures decay time or phase |
Flow dependence | High; needs flow across the membrane | High | Low |
Warm-up | Short | Minutes | Short |
Calibration drift | Higher; frequent calibration | Higher | Lower; less frequent calibration |
Consumables | Membrane and electrolyte | Membrane and electrolyte | Sensing cap or patch, replaced periodically |
Biofouling sensitivity | Affects membrane response | Affects membrane response | Affects the sensing surface; antifouling guards help |
Typical interface | Analog or digital | Analog or digital | Frequently Modbus RTU over RS-485, plus analog options |
Cost | Lowest | Low | Highest upfront |
Optical sensors cost more, and the pilot's accounting resolved the question in the usual way: the sensor price is a small fraction of the loss from a single mortality event, and the cost of repeated site visits to service cheaper probes closes most of the remaining gap.
The optical sensor was not a complete answer, and the next problem was a configuration setting that nobody had thought to change.
The Salinity Setting Nobody Changed
Optical sensors typically report oxygen as percent saturation, and as a concentration in mg/L computed from the saturation, the water temperature, the salinity, and the atmospheric pressure. The sensor knows the temperature, because it measures it. It does not know the salinity unless someone enters it. Shipped with a factory default, the salinity is zero, which means fresh water.
In the pilot, the first week's data showed the optical sensor reading comfortably above 5 mg/L through nights when the handheld meter, entered with the correct salinity, showed numbers closer to 4. The two instruments agreed closely on percent saturation. They disagreed on mg/L, because one used the right salinity and the other used zero.
The size of the error follows from the saturation values above. At 28 degrees Celsius and 30 parts per thousand, a reading of 100 percent saturation is 6.6 mg/L. A sensor that assumes fresh water converts the same 100 percent to 7.8 mg/L, an overstatement of about 1.2 mg/L, or roughly 18 percent. In a margin where the difference between safe and stressful is a few tenths of a milligram per litre, 1.2 mg/L is the whole margin.
The fix is mechanical: configure the salinity for each pond, and recompute the concentration at the gateway from percent saturation if the sensor's own setting cannot be trusted. Salinity in a pond also changes with rain and evaporation, so it needs periodic re-measurement with a refractometer and an update in the system. The conversion uses the standard saturation equations from Standard Methods, which can be coded directly and checked against published tables:
# do_conversion.py -- oxygen saturation concentration and percent saturation -> mg/L
import math
def do_sat_mg_l(temp_c: float, salinity_ppt: float) -> float:
"""Saturation concentration (mg/L) at 1 atm, Benson-Krause form with salinity correction."""
T = temp_c + 273.15
ln_fresh = (-139.34411 + 1.575701e5 / T - 6.642308e7 / T**2
+ 1.243800e10 / T**3 - 8.621949e11 / T**4)
ln_sal = ln_fresh - salinity_ppt * (1.7674e-2 - 1.0754e1 / T + 2.1407e3 / T**2)
return math.exp(ln_sal)
def do_mg_l(pct_sat: float, temp_c: float, salinity_ppt: float) -> float:
return pct_sat / 100.0 * do_sat_mg_l(temp_c, salinity_ppt)
# Spot checks against published tables:
# do_sat_mg_l(25, 0) -> about 8.26 do_sat_mg_l(20, 0) -> about 9.09
# do_sat_mg_l(28, 0) -> about 7.83 do_sat_mg_l(28, 30) -> about 6.62
print(round(do_mg_l(100, 28, 0), 2), round(do_mg_l(100, 28, 30), 2))
Atmospheric pressure matters less at sea level than the other two inputs. For ponds near the coast it can be left at standard pressure, and for sites at significant altitude it needs correcting.
A concentration reading without a stated salinity is half a measurement. Store the salinity setting with every pond's configuration, show it on the dashboard, and flag any pond whose salinity has not been verified in the last few weeks.
Calibration That Farm Staff Will Actually Do
The cheapest calibration is one the staff can do in five minutes with equipment at hand. Two approaches cover most needs. Water-saturated air calibration places the probe in a container with a little water, so the air above it is humid and at a known temperature, and sets the sensor's 100 percent saturation point there. Two-point calibration adds a zero point using a solution of sodium sulfite, which scavenges oxygen. For ponds where the concern is the range from about 2 to 8 mg/L, a one-point calibration in water-saturated air, repeated every few weeks and after any cap replacement, is a reasonable routine, and a handheld cross-check against a trusted reference once a week catches drift between calibrations.
Record each calibration and each cross-check in the system, not in a notebook. A slow divergence between sensor and handheld, visible as a trend in the cross-check log, is the earliest sign of fouling or cap ageing.
Fail safe means bad data turns the aerators on, never off.
Where the Sensor Goes and What Grows on It
A single sensor measures a single point, and a pond is not uniform. Oxygen varies with depth, with distance from aerators, and with the circulation pattern, which paddlewheel aerators create deliberately.
Placement rules that held up in the pilot:
Depth. Mount the sensor at roughly 30 to 50 centimetres below the surface in a pond of conventional depth, where oxygen is representative of the water the animals occupy most of the time. Stay well above the bottom, where sediment respiration drives the concentration lower.
Position. Place it in the zone the aerators are meant to serve, and not directly in the aerator's discharge, where the water is oxygen-rich and the reading flatters the pond. A position at the far side of the aeration pattern, where oxygen is lowest, is the conservative choice for alarm purposes.
Support. Use a float or a fixed arm that holds the sensor at a constant depth through tide, rain, and level changes, rather than a cable hanging from the bund.
Cross-check. For larger ponds, a second sensor at a different location shows how uniform the water is and catches the case where one probe is fouled while the other is not.
Biofouling is the other practical enemy. In warm, nutrient-rich water, algae and biofilm colonise a sensing surface within days. A fouled optical surface reads low and responds slowly. Antifouling measures stack: a copper guard or copper-bearing mesh around the sensor reduces growth, a mechanical wiper or periodic manual cleaning removes what accumulates, and a cleaning schedule tied to the dashboard (every week, by default) keeps it from depending on someone's memory. The system can help detect fouling indirectly. A fouled sensor's readings become unnaturally smooth and lag behind aerator events, and a growing difference from the weekly handheld cross-check confirms it.
From the Bund to the Gateway
The sensors sit on ponds that may be hundreds of metres from the farm office, with no cellular coverage on some bunds and mains power available at the aerator panels but not at the sensor. The communications design that worked in the pilot used two tiers.
Sensor to bund node. Each optical sensor spoke Modbus RTU over RS-485 to a small node on the bund, powered by a solar panel and a lithium cell. The node polled the sensor, applied the salinity and temperature compensation, and sent a compact record over a low-power wide-area radio. RS-485 suited the cable run from the float to the bund, where a few metres of shielded twisted pair carried a differential signal through an electrically noisy environment with motors and relays around.
Node to farm gateway. A gateway in the farm office, on mains power with a backhaul to the internet, received the records. It stored them locally first, then forwarded them to the cloud, so that the pond controller kept working through internet outages.
The reporting rhythm followed the biology. During the day, when oxygen is high and changes slowly, the nodes reported every 15 minutes. From sunset to a couple of hours after sunrise, they reported every 5 minutes, because the risky hours are the ones where a fast-falling trend needs to be seen quickly. Each uplink carried a sequence number, so the gateway could detect gaps, and a compact authentication tag, for the reasons covered in our post on application-layer security for LoRa networks, since the same link would later be used to switch aerators on and off.
The payload was small: percent saturation, temperature, a salinity index, battery voltage, a sensor-status byte, and the sequence number, all within a dozen bytes.
Aerator Control a Farmer Will Trust
The cloud dashboard made the pilot visible, and the controller on the gateway made it useful. Switching aerators automatically is where the system touches the farm's money and risk, and where a farmer's trust is won or lost.
A paddlewheel aerator draws on the order of a kilowatt and a half or more per unit, and a pond may carry several. Running all of them all night wastes energy during hours when the water has more oxygen than the animals need. Running too few lets the pre-dawn minimum fall into the danger range. Control logic that serves both has four ingredients:
Staged response. Switch on the first group of aerators at an early-warning level (say 5.0 mg/L), the second at a stress level (say 4.0 mg/L), and keep a minimum overnight baseline whatever the reading says.
Forecasting. Estimate the slope of the last half hour and switch on early when the projection crosses a limit within the next hour. A pond falling at 0.5 mg/L per hour that reads 5.5 needs help before it reads 4.5.
Protection for the equipment. Enforce a minimum on-time and off-time so that contactors and motors are not cycled rapidly, which shortens their lives.
Fail-safe behaviour. If the sensor data is missing, implausible, or stuck, the controller falls back to a fixed schedule with the aerators running, not to "off."
The logic is short enough to read in one sitting, which is part of why a farm manager can be shown what it will do:
# pond_controller.py -- runs on the farm gateway; one instance per pond
import time
class PondController:
def __init__(self, stage1=5.0, stage2=4.0, off_level=6.0,
min_on_s=1200, min_off_s=600, night_baseline=1):
self.stage1, self.stage2, self.off_level = stage1, stage2, off_level
self.min_on_s, self.min_off_s = min_on_s, min_off_s
self.night_baseline = night_baseline # aerators always on at night
self.history = [] # (t, do_mg_l)
self.units_on, self.last_change = 0, 0.0
def plausible(self, do):
if not (0.0 <= do <= 20.0): return False
if self.history:
t0, d0 = self.history[-1]
dt_min = max((time.time() - t0) / 60.0, 1e-3)
if abs(do - d0) / dt_min > 0.8: # > 0.8 mg/L per minute is not physical
return False
recent = [d for _, d in self.history[-24:]]
if len(recent) == 24 and max(recent) - min(recent) < 0.01:
return False # stuck value for ~2 h at 5-min cadence
return True
def slope_mg_l_per_h(self):
pts = [(t, d) for t, d in self.history if t > time.time() - 1800]
if len(pts) < 4: return 0.0
n = len(pts); tm = sum(t for t, _ in pts) / n; dm = sum(d for _, d in pts) / n
num = sum((t - tm) * (d - dm) for t, d in pts)
den = sum((t - tm) ** 2 for t, _ in pts) or 1e-9
return num / den * 3600.0
def decide(self, do, is_night):
now = time.time()
if not self.plausible(do):
return 3 if is_night else 2 # FAIL-SAFE: bad data means aerate
self.history.append((now, do))
slope = self.slope_mg_l_per_h()
projected = do + slope * 1.0 # one hour ahead
want = self.night_baseline if is_night else 0
if do < self.stage1 or projected < self.stage2 + 0.5: want = max(want, 1)
if do < self.stage2 or projected < self.stage2: want = max(want, 2)
if do < 3.0: want = 3
# honour min on/off times, except when oxygen is dangerously low
if want != self.units_on and (now - self.last_change) < (self.min_on_s if want < self.units_on else self.min_off_s) and do >= 3.0:
return self.units_on
if want < self.units_on and do < self.off_level:
return self.units_on # hysteresis: stay on until comfortably high
if want != self.units_on:
self.units_on, self.last_change = want, now
return self.units_on
Two design decisions reflect lessons from the pilot. First, manual override is always available at the aerator panel, with a physical selector switch that takes the controller out of the loop. Farm staff will not accept a system they cannot overrule, and a controller that cannot be bypassed in an emergency is a liability. Second, the controller's decisions are logged with the reasons: "unit 2 ON: DO 4.3, slope -0.6 mg/L/h, projected 3.7 in 1 h." A farm manager who can read why the aerators started at 1:10 in the morning will trust the system; one who finds the aerators running and has no explanation will turn it off.
Alerts reach people as well as machines. A call or message to the farm manager's phone when any pond falls below the stress threshold, escalating to a second person if the alert is not acknowledged within a few minutes, covers the case where the aerators run and the oxygen still falls, which is the case that needs a human.
Logging every controller decision with its reason is what lets a farm manager trust the system.
What the Pilot Showed
The pilot ran across a cluster of ponds for a full crop cycle. The numbers below are illustrative of the order of magnitude such pilots produce and are not a published result; they are given so that the shape of the outcome is clear.
Measure | Before | After | What changed it |
Nights with pre-dawn DO below 4 mg/L (per pond, per crop) | Frequent, mostly unobserved | Rare, and each one caught and handled | Continuous monitoring, forecast-triggered aeration |
Aerator run-hours per night | Fixed schedule, all units | Reduced on nights with high oxygen | Staged, demand-based control |
Sensor service visits | Several per week with galvanic probes | Weekly cleaning and monthly calibration | Optical sensor, antifouling guard |
Disagreement between dashboard and handheld | Large on mg/L (salinity) | Within instrument tolerance | Correct salinity per pond |
The energy saving is easy to estimate for any farm from its own numbers:
# energy_saving.py -- estimate nightly aerator energy saved by demand-based control
def saving_per_pond(units, kw_per_unit, hours_saved_per_night, nights, tariff_per_kwh):
kwh = units * kw_per_unit * hours_saved_per_night * nights
return kwh, kwh * tariff_per_kwh
# Example structure only: 6 units x 1.5 kW, 3 hours saved on 100 nights.
# Use YOUR electricity tariff; it varies by state and consumer category.
kwh, cost = saving_per_pond(6, 1.5, 3, 100, tariff_per_kwh=0.0) # fill in the tariff
print(f"{kwh:.0f} kWh saved per pond per crop") # 2700 kWh in this example
Energy is the saving that is easiest to count, and it is the smaller one. The larger value is the mortality event that did not happen, and the farm decides how much one is worth.
When Not to Automate the Aerators
When the pond has no reliable power. Automation needs a switching path and a supply. Where aerators are diesel-driven or power is intermittent, a monitoring-and-alert system still helps, but closed-loop control does not.
When one sensor is the only evidence. Controlling a million-rupee crop on a single probe without a cross-check is a bet on that probe. Use a second sensor on larger ponds, or a periodic handheld check built into the routine.
When the farm is small and the manager lives on site. A disciplined manual routine with night measurements may serve a small farm better than a system that needs its own maintenance. Do the arithmetic honestly for each site.
When the controller cannot fail safe. If a failure of the controller or its radio link could leave aerators off, the design is not ready. The default state on any fault is aeration on.
When staff cannot override it. A system that farm staff cannot take out of the loop quickly will be disconnected the first time it surprises them.
Starting on Monday
Measure the cycle first. Before buying a system, put a logging sensor in one pond for a week and look at the 24-hour curve. The pre-dawn minimum is the specification for everything else.
Choose an optical sensor with a replaceable sensing cap, an antifouling guard, and a digital interface such as Modbus RTU, and confirm the salinity and temperature compensation behaviour in its manual.
Configure salinity per pond, verify it with a refractometer, and put a re-verification interval in the maintenance calendar.
Set up weekly cleaning and a handheld cross-check with a logged result, and a calibration routine staff can do in five minutes.
Design the two-tier network: sensor to bund node over RS-485, node to gateway over a low-power radio, gateway with local storage and cloud forwarding. Add sequence numbers and authentication on the radio link.
Write the control policy on one page in plain language: levels, stages, minimum times, night baseline, fail-safe behaviour. Review it with the farm manager before it is switched on.
Install a manual override at every aerator panel, and log every controller decision with its reason.
Run the system in advisory mode for two weeks before it controls anything: it recommends, the manager acts, and the team compares recommendations with outcomes.
Four in the Afternoon, Again
The afternoon reading is the one that comforts, and the pre-dawn reading is the one that matters. Everything in this story serves that gap: a sensor that reads correctly in still water, a conversion that uses the right salinity, a controller that looks ahead and fails safe, and a farm manager who can read what the system did and why. A pond that is watched at 3:40 in the morning by something that never sleeps, and is right when it speaks, is a pond whose owner can sleep through the night.
EurthTech is an engineering-first product design and embedded systems company with a development center in Hyderabad. To discuss a project, write to connect@eurthtech.com or see https://www.eurthtech.com/case-studies.




Comments