JalSense integrates six field-hardened subsystems in one ruggedised node. Each is independent and replaceable, but operates as a unified intelligent water management device.
19 components — each with role, interface, specifications, and INR unit cost at prototype scale.
| Cores | Dual Xtensa LX7 @ 240 MHz |
| RAM | 512 KB SRAM + 8 MB PSRAM |
| AI accel | ESP-NN, 7.2× speedup, int8 ops |
| Active | ~78 mW (23.9 mA @ 3.3V) |
| Deep sleep | ~27 µW (7–150 µA) |
| TFLite arena | 512 KB PSRAM tensor arena |
| Inference | 5–60 ms per model |
| Sensitivity | −148 dBm @ SF12 |
| TX power | +20 dBm max |
| Airtime SF7 | ~328 ms for 100B payload |
| Airtime SF12 | ~2,636 ms for 100B payload |
| TX current | ~120 mA peak |
| Sleep current | <1 µA |
| Max payload | 251 bytes (raw LoRa) |
| India bands | B28 (Jio/Airtel), B3, B8 |
| PSM sleep | 3.8 µA (T3412=1hr, T3324=5min) |
| TX current | ~230 mA active |
| MQTT cmd | AT+SMQTCONN, AT+SMQTPUB |
| Rural range | 50+ km at Band 28 |
| Range | 0.001–14.000 pH |
| Accuracy | ±0.02 pH |
| Active | 22 mA @ 3.3V |
| Sleep | 2.6 mA |
| Read seq | Write 'R' → 900ms → read 20B ASCII |
| Calibration | Cal,7 → Cal,4 → Cal,10 (3-point) |
| EC range | 0.07–500,000 µS/cm |
| Accuracy | ±2% |
| Outputs | EC, TDS (PPM), Salinity, SG |
| Budget alt | DFRobot SEN0244 (~₹600) |
| Range | 0–saturation (~14 mg/L) |
| Response | ~8 seconds |
| WHO limit | >6 mg/L drinking water |
| Range | 0.5–500 NTU |
| Pure water | 4.1±0.3V output |
| Current | ~10 mA |
| Pipe size | DN15–DN100 |
| Accuracy | ±1–2% |
| Reg 0x0000 | Flow rate (m³/h × 0.001) |
| Reg 0x0002 | Cumulative total (m³) |
| Supply | 24 VDC, IP68 |
| Range | 0–10 bar gauge |
| Signal | 4mA=0bar, 20mA=10bar |
| Accuracy | ±0.5% FS |
| Wetted | 304 stainless steel, 1/4" BSP |
| Resolution | 0.15 mbar via 16-bit ADS1115 |
| Range | 30–450 cm |
| Accuracy | ±3 cm |
| Packet | 0xFF, distH, distL, checksum |
| Power | 5V, ~10 mA |
| Resolution | 16-bit (65,535 steps) |
| PGA range | ±256mV to ±6144mV |
| A0 | Pressure 4–20mA shunt |
| A1 | Turbidity 0–3.3V |
| A2 | Valve feedback 4–20mA |
| Accuracy | ±2 ppm (±1.7 sec/month) |
| Backup life | 5+ years on CR2032 |
| Alarms | 2× programmable + interrupt |
| Bus voltage | 0–36V, 1.25 mV resolution |
| With 10mΩ | 10 mA current resolution |
| Calibration | CAL = 0.00512 / (I_max × R_shunt) |
| Input range | 4.5–28V DC |
| Max charge | 2A |
| Efficiency | ~85–90% |
| Battery reg | 4.2V ±1% |
| Capacity | 10,000 mAh @ 3.7V |
| Cycles | 500+ to 80% retention |
| Offline | 72 hours @ 46 mA avg |
| Constellations | GPS+GLONASS+BeiDou+Galileo |
| Accuracy | ±2.5m cold, ±0.8m warm |
| Cold TTFF | ~25 seconds |
| Active | ~60 mA acquisition |
| Usage | 30s ON once/week then sleep |
| Control | 0V=closed, 10V=fully open |
| Rotation | ≤7 seconds |
| Accuracy | ±1% via potentiometer |
| Pressure | 10–15 bar rated |
| Channels | 4 independent |
| Rating | 10A @ 250VAC / 30A @ 28VDC |
| Control | <5 mA per channel from 3.3V GPIO |
| Log format | CSV: ts,pH,EC,DO,turb,flow,press,lvl |
| Buffer | 7-day window before overwrite |
| Write power | ~100 mA, ~1 µA sleep |
Every sensor uses a different electrical interface. This is the complete path from physical phenomenon in the water to a digital number inside the ESP32.
ESP32 is master. All sensors share the same bus — unique addresses prevent conflicts. Atlas sensors need 900ms after "R" command before reading back ASCII. I2C mutex semaphore in FreeRTOS prevents concurrent bus access from multiple tasks.
Wire.beginTransmission(0x63); Wire.write('R'); Wire.endTransmission();
delay(900); Wire.requestFrom(0x63, 20); // returns "7.245\r"4–20mA loops are immune to noise on cable runs 100m+. A 100Ω shunt converts current to voltage. ADS1115 at 16-bit resolution detects 0.15 mbar pressure changes — enough to find slow leaks. Protection: 1N4148 diodes clamp the shunt from reverse current damage.
V_shunt = I × 100Ω | 4mA→0.4V (0 bar) 20mA→2.0V (10 bar)
Pressure_bar = (V_shunt − 0.4) × 10 / 1.6Turbidity sensor max output is 5V but ADS1115 accepts 3.3V. A voltage divider scales it: V_out = 5V × 20k / (10k + 20k) = 3.33V max. Non-linear NTU lookup table stored in ESP32 flash maps voltage to NTU values calibrated during deployment.
V_out = V_in × 20k/(10k+20k) = 3.33V max at ADS1115 A1
NTU = firmware_lookup_table(V_out) // calibrated non-linear curveRS485 differential signalling (A/B pair) is immune to noise over 1200m. MAX485 transceiver chip converts to single-ended UART. ESP32 sends Modbus RTU request frame, waits ~50ms, receives 9-byte response with flow rate and cumulative total. DE/RE pins on MAX485 toggled for half-duplex TX/RX switching.
TX: 01 04 00 00 00 02 71 CB // slave=1, func=04, reg=0x0000, count=2, CRC
RX: 01 04 04 00 0A 00 00 FA 33 // flow=10×0.001=0.01m³/h, total=0m³Sensor continuously sends distance measurements. ESP32 reads on UART1. Header byte 0xFF identifies packet start. Distance in mm from two data bytes. Checksum validation in firmware. Far simpler than JSN-SR04T trigger/echo which needs precise timing from two GPIO pins.
Packet: [0xFF][dist_high][dist_low][checksum]
dist_mm = (dist_high × 256) + dist_low | level = tank_height − dist_mmLoRa (UART0), NB-IoT (UART1), GPS (UART2) each have their own hardware UART peripheral. A binary semaphore per UART prevents task collisions. Responses are ASCII (AT) or binary (GPS NMEA). GPIO interrupt on DIO0 pin signals LoRa TX complete to release semaphore.
LoRa TX: AT+SEND=0,20,hex_payload\r\n → +OK\r\n
NB-IoT: AT+SMQTPUB="topic",45,"payload"\r\n → +SMQTPUB:0\r\nRelay module is active-LOW: GPIO output LOW activates relay coil via optocoupler. Valve proportional control: ESP32 PWM (8-bit, 10 kHz) → RC low-pass filter (R=10kΩ, C=10µF, fc=1.6Hz) averages to 0–3.3V DC → LM324 op-amp unity+gain buffer ×3 amplifies to 0–10V. Valve feedback 4–20mA read back on ADS1115 A2 confirms position.
Valve 50%: 50% PWM duty → 1.65V after RC → ×3 → 4.95V → valve 49.5% open
Feedback: 4–20mA → 100Ω → 0.4–2.0V → ADS1115 A2 → position%TFLite Micro with ESP-NN hardware acceleration. INT8 quantised models, 512KB PSRAM tensor arena, <50ms total inference. Works completely offline — no cloud needed for real-time decisions.
Priority 5, 4KB stack. Polls all sensors every 30 seconds. Acquires I2C mutex semaphore → reads Atlas pH/EC/DO → reads ADS1115 (pressure, turbidity) → reads flow meter via Modbus → reads DS3231 timestamp. Writes to ring buffer in PSRAM.
xTaskCreate priority 5 · Core 0Per-channel 1D Kalman filter removes sensor noise spikes. State estimate updated each reading. pH spike from dirty probe → covariance increase → spike discarded. Runs in <1ms per channel.
1D Kalman, O(1) per channelRing buffer of last 5 filtered readings per channel. Smooths transient artefacts after Kalman. Output: one representative value per sensor per 30s polling cycle.
Ring buffer, O(1)12 normalised values assembled into input tensor: [pH/14, EC/500000, DO/20, turbidity/1000, temp/100, flow/100, pressure/10, Δpressure/10, level/100, Δlevel/100, power/50, tod/24]. Shape: float32[1,12].
Input tensor float32[1,12]Dense neural network: Input[12] → Dense(64, ReLU) → Dense(32, ReLU) → Dense(1, Sigmoid). INT8 quantised, 45KB, 15ms inference. Output: anomaly_score [0.0–1.0]. Threshold: >0.75 = anomaly flag.
MLP INT8 · 45KB · 15ms3-layer MLP: Input[12] → Dense(32, ReLU) → Dense(16, ReLU) → Dense(3, Softmax). INT8, 30KB, 20ms. Output: confidence scores for [POTABLE, BORDERLINE, CONTAMINATED]. Threshold: CONTAMINATED >0.8.
MLP INT8 · 30KB · 20msDecision tree on 60-second pressure waveform (120 samples). Rapid drop (>3 bar in 10s) = burst pipe. Low-amplitude oscillation (±0.2 bar at 0.5Hz) = slow leak. INT8, 15KB, 8ms.
Decision Tree INT8 · 15KB · 8msPriority queue: Emergency > Safety > AI > Schedule > Manual. CONTAMINATED + anomaly >0.9 → close valve (0V) + relay CH3 alarm + QoS2 alert packet. Valve position confirmed via feedback within 10 seconds.
Rule + Model FusionEEPROM-stored CRON schedule survives power cycles. Sources: manual (dashboard), cloud-pushed (MQTT downlink), sensor-triggered (e.g., open valve when level <20%). Operates fully offline. Audit log per actuation.
CRON in EEPROM · Offline-first15 fields packed into ~45 bytes: [uint32 timestamp, float32×8 sensors, uint8×3 classes, int16×2 RSSI/SNR, uint8 flags]. Total inference overhead: ~50ms per 30s cycle = 0.28% CPU time on Core 1.
MessagePack ~45B · 0.28% CPU72-hour demand prediction per deployment. Retrained monthly with latest TimescaleDB data. Output: hourly valve scheduling pushed back to edge as CRON update via MQTT downlink.
LSTM · TimescaleDB featuresNRW balance weekly per zone. Sensor drift scoring via rolling regression — predicts calibration failure 7–14 days ahead. Battery discharge curve analysis. Outputs maintenance work orders to field app.
Gradient Boost + Water Balance10 layers from physical measurement to push notification. Every protocol, every data format, every timing value, every component — micro-level detail. Scroll each layer independently.
8 steps from first site visit to node going GREEN on the government GIS map.
From EMQX message receipt to TimescaleDB storage to ML inference to alert routing. Every stage designed for 10→10,000 node scale.
Receives MessagePack from JalSense via LoRa gateway or NB-IoT. Topics: jalsense/{device_id}/telemetry (QoS 1, 5-min interval), jalsense/{device_id}/alert (QoS 2, immediate). LWT: if TCP drops without DISCONNECT, broker publishes jalsense/{id}/status {"status":"offline"} after 30s. X.509 per-device certificates for mutual TLS 1.3 auth. Persistent sessions (CleanStart=false) queue messages during connectivity gaps.
EMQX rule engine forwards all messages to Kafka topics: raw-telemetry (5 partitions, partition key = device_id hash → ordered processing per device), alerts (3 partitions), valve-commands. Snappy compression: ~4:1 ratio. Idempotent producer prevents duplicate writes. Replay window: 30 days allows reprocessing historical data with updated ML models.
Hypertable telemetry partitioned by time (1-day chunks) and device_id (hash, 2 spaces). Raw data retained 90 days → continuous aggregates: hourly averages for 2 years, daily averages forever. Partial index on anomaly_score > 0.5 keeps anomaly queries fast. Typical query latency: <50ms for 7-day raw range, <20ms for 2-year hourly aggregate.
Python ML workers consume from Kafka. LSTM demand forecasting (02:00 AM daily per deployment): reads 7-day hourly history from TimescaleDB, outputs 72-hour demand forecast stored back to demand_forecasts table. NRW analysis (03:00 Sunday): flow_produced − flow_billed − losses = NRW% per zone. Predictive maintenance (04:00 daily): rolling regression on calibration residuals detects sensor drift 7–14 days ahead.
Kafka consumer evaluates every incoming reading in real-time: (a) operator-configured thresholds per device, (b) edge AI anomaly_score forwarded in packet, (c) rate-of-change rules (pH drop >0.5 in 1 minute). Redis bloom filter deduplicates: same alert type suppressed 30 minutes. GPS zone lookup assigns field engineer. FCM HTTP v1 → push notification in <2 seconds. Escalation: 15min unack → supervisor; 30min → ops manager; 60min CRITICAL → government nodal officer SMS.
Step-by-step timeline from edge AI detection to push notification on a field engineer's Android phone.
TFLite quality classifier: CONTAMINATED confidence 94% >0.80 threshold. Decision engine: close valve (GPIO PWM→0V), relay CH3 alarm ON, QoS2 flag set in TX queue.
Motorised valve receives 0V → closes. Relay CH3 activates siren. DS3231 timestamps event. SD card logs: all 15 sensor values, anomaly score, quality confidence, valve command.
LoRaWAN FPort=11 (alert). Confirmed uplink (MHDR=0x80). MIC appended with NwkSKey AES-CMAC. If no ACK in 5s: NB-IoT activates as fallback. AT+SMQTPUB with QoS 2.
ChirpStack deduplicates (200ms window, best RSSI gateway selected). Decrypts FRMPayload with AppSKey. EMQX ACKs QoS 2 (4-step handshake). Kafka routes to alert-engine consumer.
Schema: (id, device_id, timestamp, type=CONTAMINATION, severity=CRITICAL, sensor_values JSON, ai_scores JSON, zone_id, assigned_engineer_id, status=OPEN, created_at).
Zone lookup: GPS coordinates → zone_id → assigned field engineer. FCM HTTP v1 POST to topic zone_{zone_id}. Webhook fires to WhatsApp Business API for supervisor channel.
"CRITICAL: Contamination at Tank B7, Zone 3. pH 5.2, Turbidity 180 NTU. Valve auto-closed. Tap to view." Deep link → device detail screen with full sensor timeline.
WebSocket event alert_created pushes to all connected dashboard clients. Node icon RED. Alert in triage queue. Supervisor sees full sensor history, AI confidence, and valve status.
Every milliamp accounted for. Total average ~46 mA = 9-day battery life + 10× solar margin.
| Component | State | mA | Duty % | Avg mA | Notes |
|---|---|---|---|---|---|
| ESP32-S3 | Active (sensing + AI) | 24 | 10% | 2.40 | Core 0: sensors; Core 1: comms |
| ESP32-S3 | Deep sleep | 0.01 | 90% | 0.009 | ULP coprocessor only |
| Atlas EZO-pH | Active read | 22 | 3% | 0.66 | Sleep 2.6 mA between reads |
| Atlas EZO-EC | Active read | 22 | 3% | 0.66 | Same sleep pattern |
| Atlas EZO-DO | Active read | 22 | 3% | 0.66 | 8s response, awake 10s/read |
| DFRobot Turbidity | Always on | 10 | 100% | 10.00 | Power-gate via transistor to save |
| Ultrasonic Flow Meter | Active poll | 80 | 5% | 4.00 | 24V via boost converter |
| Pressure Transducer | Always on | 20 | 100% | 20.00 | 4–20mA loop — biggest draw |
| ADS1115 | Convert bursts | 0.15 | 5% | 0.008 | Negligible |
| DS3231 RTC | Always on | 0.5 | 100% | 0.50 | CR2032 backup on outage |
| INA226 | Always on | 0.33 | 100% | 0.33 | Continuous battery monitoring |
| LoRa (TX burst) | Transmitting | 120 | 0.5% | 0.60 | 5-min interval, ~2s per TX |
| LoRa (idle) | Sleep | 1 | 99.5% | 1.00 | Not in continuous RX |
| SIM7080G NB-IoT | PSM T3412=1hr | 0.004 | 99% | 0.004 | Fallback only, 3.8µA PSM |
| NEO-M8N GPS | Weekly 30s fix | 50 | 0.05% | 0.025 | 30s ON once/week |
| MicroSD | Write burst | 100 | 1% | 1.00 | ~0.5s write per 5-min cycle |
| Relay (1 active) | Pump control | 80 | 5% | 4.00 | Not always active |
| TOTAL AVERAGE | ~46 mA | 10,000 mAh / 46mA = 217hrs (9 days) |
34 line items, full Bill of Materials with INR unit costs at prototype (1–10 units) and production (100+ units) scale. Sources: Robu.in, Mouser India, IndiaMART, JLCPCB.
| # | Component | Supplier / Source | Qty | Proto (₹) | Prod 100+ (₹) | Line Total |
|---|---|---|---|---|---|---|
| COMPUTE SUBSYSTEM | ||||||
| 1 | ESP32-S3 Development Module | Espressif / Robu.in | 1 | 300 | 200 | 300 |
| 2 | DS3231 RTC Module (with CR2032) | Maxim / Robu.in | 1 | 250 | 150 | 250 |
| 3 | MicroSD Card Module (SPI) | Generic / Robu.in | 1 | 300 | 180 | 300 |
| 4 | MicroSD Card 32 GB Samsung | Amazon India | 1 | 450 | 350 | 450 |
| Compute Subtotal | 1,300 | |||||
| WATER QUALITY SENSORS | ||||||
| 5 | Atlas Scientific EZO-pH Circuit + Probe | Atlas Scientific (direct) | 1 | 4,000 | 3,400 | 4,000 |
| 6 | Atlas Scientific EZO-EC Circuit + Probe | Atlas Scientific (direct) | 1 | 4,300 | 3,600 | 4,300 |
| 7 | Atlas Scientific EZO-DO Circuit + Probe | Atlas Scientific (direct) | 1 | 4,700 | 4,000 | 4,700 |
| 8 | DFRobot SEN0189 Turbidity Sensor | DFRobot / Robu.in | 1 | 650 | 400 | 650 |
| 9 | DS18B20 Waterproof Temperature Probe | Maxim / Robu.in | 1 | 100 | 60 | 100 |
| Water Quality Subtotal | 13,750 | |||||
| FLOW & PRESSURE SUBSYSTEM | ||||||
| 10 | Ultrasonic Clamp-on Flow Meter (DN25) | Industrial / IndiaMART | 1 | 10,000 | 7,500 | 10,000 |
| 11 | 4–20mA Pressure Transducer 0–10 bar | Industrial / Moglix | 1 | 2,500 | 1,800 | 2,500 |
| 12 | A02YYUW Ultrasonic Level Sensor | DFRobot / Robu.in | 1 | 1,000 | 650 | 1,000 |
| 13 | ADS1115 16-bit ADC Module | Adafruit / Robu.in | 1 | 400 | 250 | 400 |
| 14 | MAX485 RS485 Transceiver Module | Generic / Robu.in | 1 | 80 | 50 | 80 |
| Flow & Pressure Subtotal | 13,980 | |||||
| TRANSMISSION SUBSYSTEM | ||||||
| 15 | RYLR998 LoRa Module (SX1276) | Reyax / Robu.in | 1 | 550 | 380 | 550 |
| 16 | SIM7080G NB-IoT/GNSS Module | SIMCom / Waveshare | 1 | 1,400 | 1,100 | 1,400 |
| 17 | u-blox NEO-M8N GPS Module | u-blox / Robu.in | 1 | 1,200 | 900 | 1,200 |
| 18 | NB-IoT SIM Card (Airtel IoT) | Airtel Business | 1 | 200 | 150 | 200 |
| 19 | LoRa Antenna 868 MHz 3 dBi | Generic / Robu.in | 1 | 150 | 100 | 150 |
| Transmission Subtotal | 3,500 | |||||
| POWER SUBSYSTEM | ||||||
| 20 | 10W Monocrystalline Solar Panel | Loom Solar / Amazon | 1 | 700 | 500 | 700 |
| 21 | CN3791 MPPT Charger Module | Generic / Robu.in | 1 | 500 | 300 | 500 |
| 22 | Li-Ion Battery Pack 10,000 mAh 3.7V | Generic 18650 pack | 1 | 1,200 | 900 | 1,200 |
| 23 | INA226 Power Monitor Module | TI / Robu.in | 1 | 400 | 250 | 400 |
| 24 | USB-C Port + Cable Gland Set | Generic | 1 | 300 | 180 | 300 |
| Power Subtotal | 3,100 | |||||
| ACTUATOR SUBSYSTEM | ||||||
| 25 | DN25 Motorized Ball Valve (0–10V, 24V) | Industrial / IndiaMART | 1 | 6,500 | 4,500 | 6,500 |
| 26 | 4-Channel Opto-isolated Relay Module | Generic / Robu.in | 1 | 400 | 250 | 400 |
| 27 | LM324 Op-Amp + RC filter passives | LCSC / Robu.in | 1 | 100 | 60 | 100 |
| Actuator Subtotal | 7,000 | |||||
| ENCLOSURE & MECHANICAL | ||||||
| 28 | IP67 ABS Enclosure 200×150×80mm | Fibox / IndiaMART | 1 | 2,000 | 1,400 | 2,000 |
| 29 | DIN Rail + Wall Mount Bracket | Generic | 1 | 300 | 200 | 300 |
| 30 | IP67 Cable Glands (set of 6) | Hawke / IndiaMART | 1 | 400 | 250 | 400 |
| 31 | Terminal Blocks + DIN Rail (set) | Phoenix Contact | 1 | 500 | 350 | 500 |
| Enclosure Subtotal | 3,200 | |||||
| PCB & MISCELLANEOUS | ||||||
| 32 | Custom PCB 2-layer 100×80mm | JLCPCB / PCBWay | 1 | 800 | 350 | 800 |
| 33 | Passive components (R, C, connectors) | LCSC India | 1 | 400 | 200 | 400 |
| 34 | Wire, connectors, heat shrink set | Generic | 1 | 300 | 180 | 300 |
| PCB & Misc Subtotal | 1,500 | |||||
| TOTAL BOM COST PER UNIT | ₹ 47,330 | |||||