ADS1015 analog sensing and PCF8574AT I/O library for APA pool automation
Sensing (ADS1015, I2C)
- Pool pressure — parametric range (any 0.5–4.5 V ratiometric transducer), auto zero-cal
- Pump current — true AC RMS via non-blocking 32-sample accumulator (ACS712)
- Pump apparent power —
getPower()returns V × I_rms; supply mains voltage once in setup - Solar irradiance — 0–100 % using user-supplied calibration constants
- AUX voltage — raw 0–5 V input for any future sensor
Digital I/O (PCF8574AT, I2C)
- 4 × chemical tank-empty sensor inputs — per-sensor configurable polarity
- 4 × status LED outputs (active-low open-drain)
Indicators
- Buzzer — single beep, or three severity-matched rhythm patterns (
BUZZER_INFO / WARNING / ALARM) - Alert patterns repeat until
stopAlert()— designed for integration with APAPUMP alarm callbacks
Integration
- Designed as the sensor layer for APAPUMP and APADOSE
- Callback bridges connect readings to APAPUMP safety engine in four lines
- Pressure re-zero automatically triggered after every pump stop
- Pure
Wire.hdependency — no Adafruit or external libraries required
Arduino IDE
- Download the latest ZIP from the Releases page
- Sketch → Include Library → Add .ZIP Library
PlatformIO
; Replace with the actual path to your local copy of APA-SENSE_LIB
lib_extra_dirs = ../APA-SENSE_LIB ; relative (sibling folder)
; lib_extra_dirs = C:/projects/APA-SENSE_LIB ; absolute (Windows)
; lib_extra_dirs = N:\kecup\PlatformIO\APA-SENSE_LIB ; NAS driveAPASENSE is the hardware abstraction layer for the analog sensing side of an APA pool controller. It reads four analog inputs via an ADS1015 12-bit ADC, manages binary tank-empty sensors and status LEDs through a PCF8574AT I/O expander, and drives a buzzer. All operations are non-blocking — the library cycles through ADC conversions one at a time in update() so the main loop never stalls.
Pressure and current readings are delivered to APAPUMP through callbacks. When the pump stops, APASENSE automatically re-zeroes the pressure transducer after a 30-second settle period and saves the result to EEPROM, so pressure readings are accurate from the very first update() cycle on every subsequent boot. If mains voltage is supplied to enableCurrent(), getPower() returns apparent power in VA. Three built-in buzzer patterns (BUZZER_INFO, BUZZER_WARNING, BUZZER_ALARM) can repeat continuously until stopAlert() is called — intended for APAPUMP alarm callbacks.
update() drives one ADS1015 single-shot conversion per call, cycling round-robin through enabled channels. The 1 ms conversion time is tracked with a lightweight 16-bit millis() comparison — no blocking delays anywhere in the library.
update() called every loop():
┌────────────────────────────────────────────────────────────┐
│ 1. Buzzer sequencer — advance pattern step / expire beep │
│ 2. Settle timer — re-zero pressure if 30 s elapsed │
│ 3. ADC cycle: │
│ conversion pending? │
│ yes + 1 ms elapsed → read result → process │
│ no pending conversion? │
│ → find next enabled sensor → start conversion │
└────────────────────────────────────────────────────────────┘
Channels cycle in order: pressure → current → AUX → LDR (skipping disabled ones). At a 1 ms update() rate, each enabled channel is sampled approximately every 4 ms.
The pressure transducer outputs 0.5 V at zero pressure and 4.5 V at maxBar. The library measures the ADC count at zero pressure (pump off) and stores it as _pressureZero. All subsequent readings are referenced to this offset.
onPumpState(false) called
│
▼ wait 30 s (APASENSE_PRESSURE_SETTLE_MS)
│
calibratePressureZero() ── snapshot current ADC reading as zero-pressure reference
│
└── save to EEPROM (survives reboot)
If onPumpState(true) is called before the 30 s completes, the settle is cancelled — the pump restarted and readings are no longer stable.
The ACS712 outputs instantaneous voltage proportional to instantaneous current, centred at VCC/2 = 2.5 V. APASENSE accumulates (sample − zero)² over 32 samples, then computes sqrtf(mean_square) × scale / sensitivity.
begin() → 8-sample average → zero-current offset (the 2.5 V centre)
Each current sample in update():
centered = raw − zero offset
sum_sq += centered²
count++
if count == 32:
current_rms = sqrtf(sum_sq / 32) × (6.144 / 2048) / sensitivity
reset accumulator
getCurrent() returns −1.0 until the first full 32-sample cycle completes.
The LDR circuit uses a hardware pot (R16) as the primary sensitivity adjustment. Software calibration captures the raw ADC count at two known extremes, then interpolates. Run example 00_ldr_calibration, note the two values, and hardcode them in your sketch:
adc.enableLDR(3, 42, 1520); // rawDark=42, rawSun=1520 (your measured values)getSolarPct() maps the current raw count linearly to 0–100 % using those references.
#include <APASENSE.h>
ApaSense adc;
void setup() {
// Configure channels BEFORE begin() — current auto-zero runs in begin()
adc.enablePressure(); // AIN0, 0–6.9 bar
adc.enableCurrent(); // AIN1, ACS712-20A
adc.begin();
}
void loop() {
adc.update(); // call every loop() — never use delay()
float pressure = adc.getPressure(); // bar, or -1.0 until calibrated
float current = adc.getCurrent(); // amps RMS, or -1.0 until ready
}// In setup(), after adc.begin():
pump.enablePressure([]() { return adc.getPressure(); }, 4.0f);
pump.setCurrentCallback([]() { return adc.getCurrent(); });
pump.setPumpStateCallback([](bool on) { adc.onPumpState(on); });
pump.setPumpAlarmCallback([](PumpAlarm a) {
bool alarm = (a != PUMP_ALARM_NONE);
adc.setLed(0, alarm);
if (alarm) adc.alert(BUZZER_ALARM, true); // repeat until cleared
else adc.stopAlert();
});adc.enableTankSensor(0); // PCF P0, active-low (default)
dose_ph.setTankEmptyCallback([]() { return adc.isTankEmpty(0); });See docs/API.md for the complete method reference.
| Group | Methods |
|---|---|
| Core | begin(), update() |
| ADS channels | enablePressure(), enableCurrent(), enableAux(), enableLDR() |
| Getters | getPressure(), getCurrent(), getPower(), getAuxVoltage(), getSolarPct(), getRawLDR() |
| Pressure cal | calibratePressureZero(), onPumpState() |
| Tank sensors | enableTankSensor(), isTankEmpty() |
| LEDs | setLed(), getLed() |
| Buzzer | enableBuzzer(), setBuzzer(), beep(), alert(), stopAlert() |
Measured with examples/01_minimal (pressure + current enabled).
| Platform | RAM used | Flash used |
|---|---|---|
| Arduino Mega 2560 | 483 B / 8 192 B (6 %) | 9 064 B / 253 952 B (4 %) |
| Arduino Uno | 483 B / 2 048 B (24 %) | 8 316 B / 32 256 B (26 %) |
| ESP32 | 21.9 KB / 320 KB (7 %) | 297 KB / 1.3 MB (23 %) |
| ESP8266 | 28.7 KB / 80 KB (35 %) | 274 KB / 1.0 MB (26 %) |
| STM32 (Nucleo F411RE) | 9.7 KB / 128 KB (7 %) | 24 KB / 512 KB (5 %) |
| Use | Terms |
|---|---|
| Personal, private, educational, hobby | Free — use, copy, modify, distribute |
| Commercial (selling hardware, paid services, OEM, revenue-generating) | Requires separate written agreement — strictly prohibited without it |
Contact for commercial licensing: [email protected]
APASENSE — APA Devices · [email protected]
