Industry Microcontroller

The Landlocked Sea Bass: Building an Automated Feeding System for Marine Fish Far From the Ocean

Chanthaburi has a coastline. Nakhon Ratchasima does not — the nearest seawater is a five-hour drive away. And yet if you wanted to, you could raise Asian sea bass (Lates calcarifer) in a shed outside Korat and sell it as fresh, real, sea-grown fish. The biology has never been the obstacle. Sea bass is euryhaline — it tolerates the swing from full-strength seawater to freshwater without blinking, which is exactly why it’s farmed in Thai river-mouth cages today. The obstacle is that a tank doesn’t have an ocean’s worth of water quietly diluting every mistake.

In the open sea, a feeding error, an ammonia spike, or a dissolved-oxygen dip gets buffered by millions of liters moving past the cage. In a 120 sq.m. recirculating tank system, it doesn’t. It just gets worse until someone notices — usually a control engineer’s version of "someone notices," which is a mortality event, not a dashboard alert.

Why this is a control-systems problem, not a farming problem

Thailand’s NSTDA/MTEC has already proven the biology and the economics: an automated recirculating aquaculture system (RAS) running sea bass at 40 kg/cu.m across six tanks produces roughly 340 kg/month, while cutting water use by 94% versus flow-through systems. The Southeast Asia RAS market is projected to grow from around USD 1.7 billion in 2026 to USD 2.6 billion by 2035. The demand and the biology are both settled questions.

What isn’t settled — what fails, quietly, on most inland RAS deployments — is the control layer. Four parameters have to hold inside a narrow band, continuously, with no ocean to forgive a lapse:

  • Dissolved oxygen, which crashes fast at high stocking density and takes the whole tank down with it
  • Ammonia/nitrite, which climbs invisibly between feedings and doesn’t show up in a manual test until it’s already stressing the fish
  • Salinity, which drifts as makeup water and evaporation shift the mix, and which sea bass tolerate — up to a point, and not gracefully past it
  • Feed timing and quantity, where overfeeding fouls the water chemistry that items one through three depend on, and underfeeding just leaves money in the bag

This is the same shape of problem as a factory floor: distributed sensors, a control loop that has to run on schedule regardless of whether anyone’s watching, and a failure mode that’s invisible until it’s expensive. It’s the same reason we ended up writing about OT protocol fragmentation on the shop floor and thermal monitoring on a vacuum furnace — sensors, control logic, and alerting are the same discipline whether the vessel holds molten metal or seawater.

What the system actually has to do

flowchart TD
    SENS["Sensors  
DO · pH · Ammonia · Salinity · Temp"] --> CTRL["Control Loop  
Threshold logic · Feed scheduler"]
    CTRL --> FEED["Auto Feeder  
Timed / demand dosing"]
    CTRL --> AER["Aerator / Pump  
DO correction"]
    CTRL --> ALERT["Alerting  
SMS / LINE to operator"]
    SENS --> LOG[("Time-series log  
Read-only")]
    CTRL --> LOG
    FEED --> LOG

    classDef sense fill:#0f172a,stroke:#eab308,stroke-width:1px,color:#fde68a;
    classDef core fill:#082f49,stroke:#38bdf8,stroke-width:1px,color:#bae6fd;
    classDef act fill:#052e16,stroke:#22c55e,stroke-width:1px,color:#bbf7d0;
    classDef data fill:#134e4a,stroke:#14b8a6,stroke-width:1px,color:#99f6e4;
    class SENS sense;
    class CTRL core;
    class FEED,AER,ALERT act;
    class LOG data;

Nothing here is exotic. It’s a sensor array (DO, pH, ammonia, salinity, temperature — the same class of probe used across water-treatment and industrial process monitoring), a control loop that decides when to feed and when to correct, an actuation layer (feeder motor, aerator, pump), and an alerting path that reaches an operator’s phone before a mortality event, not after. The part that’s easy to get wrong is treating the feeder as a standalone timer instead of one output of a control loop that’s reading water chemistry continuously — that’s the difference between an automated feeder and an automated aquaculture system.

The software stack, concretely

Every layer in the diagram above maps to a specific, boring, well-understood Python toolchain — this is deliberately not a place to reach for anything novel, because novel is how you find out about a control-loop bug via a mortality event.

Edge layer (per tank, running on-device). Industrial DO, pH, and salinity probes almost always speak analog (4–20 mA) or Modbus RTU rather than a clean digital bus, so the edge controller is either a Raspberry Pi running standard Python or an ESP32 running MicroPython, depending on probe grade:

  • pymodbus — polls Modbus RTU/TCP probes (the common output for industrial-grade DO/pH transmitters)
  • smbus2 / adafruit-circuitpython-* drivers — for cheaper I²C sensor boards where Modbus isn’t available
  • gpiozero or RPi.GPIO — drives the feeder motor, aerator relay, and dosing pump outputs
  • asyncio — runs sensor polling, the control loop, and actuator writes concurrently on one device without blocking

Transport. Sensor readings and actuation events are published over MQTT rather than polled point-to-point — the same pattern we’d use on a factory floor, and the same fix for the "five dialects" protocol-fragmentation problem: normalize everything to one bus at the edge, don’t make the backend speak Modbus.

  • paho-mqtt — publishes readings from each tank controller to a local Mosquitto broker
  • orjson — fast serialization for the sensor payloads going over the wire

Control loop. This is the part that has to run locally and can’t wait on a network round-trip:

# runs on the tank's edge controller, every 30s
async def control_tick(tank: TankState) -> None:
    reading = await read_sensors(tank)          # pymodbus / smbus2
    if reading.do_mg_l < tank.thresholds.do_min:
        await actuate("aerator", tank.id, on=True)
    if reading.salinity_ppt < tank.thresholds.sal_min:
        await actuate("brine_dose", tank.id, ml=tank.dose_step)
    if feed_scheduler.due(tank):                 # APScheduler-driven
        await actuate("feeder", tank.id, grams=tank.feed_ration)
    await publish(reading, tank)                 # paho-mqtt → broker
    if reading.breaches_critical(tank.thresholds):
        await alert(tank, reading)                # LINE Notify / Twilio
  • APScheduler — drives feed timing per tank (time-of-day and demand-based schedules, not just a fixed interval)
  • pydantic — validates every sensor payload and threshold config before it’s allowed to trigger an actuator

Backend and dashboard. Django 5, matching the stack simpliDepot already runs, with the time-series side kept separate from the transactional side rather than jammed into the same tables:

  • Django + djangorestframework — tank config, thresholds, feed schedules, farm/site management
  • Django Channels — pushes live tank readings to the operator dashboard over WebSocket
  • influxdb-client (or TimescaleDB via psycopg2/asyncpg) — stores the high-frequency sensor time series that a relational OLTP schema shouldn’t have to carry
  • celery — scheduled reports, daily feed-conversion-ratio rollups, and anything that doesn’t need to run on the 30-second control tick

Alerting. requests against the LINE Notify or Twilio API — the same channel simpliDepot already uses for farmer/buyer notifications, so an operator gets a tank alert on the same app they already have open.

Before any of this touches real fish, the control loop gets exercised against a simulated tank — simpy for discrete-event simulation of feed/DO/ammonia dynamics, or just a numpy-driven mock of the sensor readings — so threshold logic is proven out against a season’s worth of simulated drift before it’s trusted to run unattended overnight.

Why this fits next to simpliDepot, not instead of it

simpliDepot started as a durian intake system and generalized into any graded commodity — rubber, palm, recycled material — because the underlying pattern (intake, grading, FIFO, payout record) didn’t actually depend on the fruit. The same generalization applies here in the other direction: an inland RAS control system isn’t really an "aquaculture" product, it’s an industrial monitoring and control system (the same category as IMCS) aimed at a vessel full of seawater instead of a factory floor. The sensors, the control loop, the alerting, and the historical logging are the same primitives we already build for OT environments — just wired to a different set of actuators.

For an operator in a landlocked province, the pitch isn’t "buy an aquaculture platform." It’s: the biology already works, the market already wants inland-farmed sea bass at a lower cost basis than coastal cage farming, and the only thing standing between a shed full of tanks and a functioning business is a control system that doesn’t let anyone find out about a problem by counting dead fish in the morning.

FAQ

Does this only work for sea bass?
No — sea bass is the obvious first species because it’s already commercially proven in Thailand and tolerates the salinity range a tank system will realistically hold. The same sensor/control/feed architecture applies to other euryhaline or brackish-tolerant species with a local market.

Do you need synthetic sea salt, or can you use diluted brine trucked in?
Both are used in practice; it’s a cost-and-logistics decision per site, not a technical constraint on the control system. The system needs to hold salinity in range regardless of source — that’s a sensor-and-dosing problem either way.

What happens if the internet goes down at a rural site?
The control loop (sensors → thresholds → actuation) has to run locally regardless of connectivity — it can’t depend on a cloud round-trip to decide whether to run an aerator. Connectivity is for logging, alerting, and remote monitoring, not for the safety-critical loop itself. This is the same offline-first constraint we plan around for depot sites with unreliable rural internet.

Is this a real product yet?
This is a concept we’re scoping based on proven RAS economics and our existing IMCS/OT monitoring work — not a shipped platform. If you’re running or planning an inland RAS operation and want to talk through what a control system would actually need to do for your site, get in touch.


Sources: NSTDA/MTEC automated RAS for intensive farming; Southeast Asia RAS Market, GM Insights; Aquaculture of Asian sea bass/barramundi, Global Seafood Alliance.