Industrial Emission Monitor — HackTU 4.0
Prototype
Hackathon · IoT

Industrial Emission Monitor — HackTU 4.0

Firmware / Sensor Integration · 2023

Seven-probe industrial effluent monitor built for HackTU 4.0. One Arduino sketch drives an MQ135 (air quality — labelled CO2 in the demo, which an air-quality resistor is not), an MQ7 (CO) with its own RS/R0 curve fit, a DHT22, a GP2Y1010AU0F optical dust sensor timed by hand for PM2.5, a Gravity TDS probe with temperature compensation, a DS18B20 water probe on OneWire, and a 10-sample median-filtered pH electrode, each with its own read routine, alongside an ESP8266 uploader targeting a Firebase realtime database named industry-monitor. None of it ever ran: loop() is empty, so every read function and the uploader are defined and none of them are called, and the sketch as committed would not compile. SOx and NOx are derived from weighted CO/CO2 ratios rather than measured, and BOD is written back as "No Sensor Availaible" — an honest gap left in the demo. A companion Android shell (com.hacktu.emission) was scaffolded but never built out.

Built with
ArduinoArduino
C++C++
ESP8266ESP8266
Firebase Realtime DatabaseFirebase Realtime Database
MQ135MQ135
MQ7MQ7
DHT22DHT22
JavaJava
AndroidAndroid
Project Details

STATUS
Prototype
ROLE

Firmware / Sensor Integration

YEAR

2023

TYPE

Hackathon · IoT

TAGS
Hackathon
Sensors
Environmental Monitoring
Embedded

Industrial effluent monitoring is a paperwork problem pretending to be a sensing problem. A plant reports its own numbers on its own schedule, and by the time anybody checks, whatever was in the air or the outfall channel has moved downstream. The pitch for HackTU 4.0 in February 2023 was to put the readings somewhere nobody at the plant controls: a rig that samples air and water continuously and pushes every value straight into a cloud database as it takes it.

This is the earliest hardware he built. Everything else on this portfolio from before it is web and backend work, and it is the only environmental-sensing build here — the hardware thread picks up again with SafetySync's helmet rig at the end of the same year and does not return to instrumenting a process until the Water Turret in 2026. The sketch is worth reading for what it commits to rather than for polish.

One board, seven probes, one read loop

The integrated sketch is 256 lines in hacktu rough/hacktu rough.ino, and it drives everything off a single Arduino with an ESP8266 hanging off it as a WiFi coprocessor through WiFiEsp, talking to industry-monitor-default-rtdb.firebaseio.com through FirebaseArduino. Seven sensing elements are wired to it, five of them on the analog bank and two on digital pins:

  • MQ135 on A0, read through the MQ135 library's getPPM(), standing in for CO2 and general air quality.
  • MQ7 on A1 for carbon monoxide, with no library at all. The sketch does the curve fit by hand: convert the ADC reading to volts, derive RS_gas = (5.0 - v) / v, divide by a calibration constant R0 = 7200.0, then ppm = pow(1538.46 * ratio, -1.709).
  • pH electrode on A2, with a median filter described below.
  • Gravity TDS probe on A3 through the GravityTDS library, with setAref(5.0) and a 10-bit ADC range declared, and temperature compensation fed from the water probe rather than left at the library's 25 C default.
  • GP2Y1010AU0F optical dust sensor on A5, with its infrared LED driven from pin 12.
  • DHT22 on digital pin 2 for air humidity and air temperature.
  • DS18B20 on a OneWire bus on digital pin 3 for water temperature, through DallasTemperature.

The per-sensor folder beside it holds eight reference sketches kept as .txt files, one per probe plus a Firebase example. Those are vendor and tutorial code, visibly so, complete with the Circuit Digest LCD splash screen in the pH sketch and the waveshare wiki header in the dust one. The original work is the integration: taking seven separately-written read loops, each assuming it owned the board and the serial port and a two-second delay(), and turning them into functions that return a float and can be called in sequence.

The two probes that needed real work

The dust sensor cannot simply be read. The GP2Y1010AU0F measures infrared scattered off airborne particles, and its output is only valid inside a narrow window after the LED fires, so the datasheet specifies a duty cycle in microseconds. The sketch implements it literally: pull ledPower low, wait 280 microseconds of sampling time, take the analog reading, wait another 40, pull the LED high, sleep 9,680, then convert. dustDensity = 0.17 * calcVoltage - 0.1, clamped at zero because the linear fit goes negative in clean air. Getting that sequence wrong does not produce an error, it produces plausible garbage, which is the failure mode that makes hand-timed sensors unpleasant.

The pH electrode gets a median filter, and it is the one piece of signal processing in the sketch. get_water_ph() takes ten readings 30 milliseconds apart into buffer_arr, bubble-sorts them in place, discards the two lowest and the two highest, and averages the middle six. That average is converted with volt = avg * 5.0 / 1024 / 6 and mapped through ph = -5.70 * volt + 21.34, where 21.34 is a calibration constant that has to be trimmed against a buffer solution per electrode. Throwing away the tails is the right instinct for a high-impedance probe sitting next to a WiFi radio, where the noise is spikes rather than drift.

The numbers that are not measurements

The most honest thing in this project is what it does when it runs out of sensors. Sulphur oxides and nitrogen oxides both matter for an emissions story and neither had a probe on the bench, so the sketch derives them from what it does have:

Air SOX = ((0.57 * CO) + (0.33 * CO2)) / 2
Air NOX = ((0.87 * CO) + (humidity * CO2 / 100)) / 2

Those weightings are not traceable to anything. They produce a number that moves in roughly the right direction when combustion products rise, and they are a demo standing in for an electrochemical cell nobody had. Biological oxygen demand did not even get that treatment. The field is written with Firebase.setString("Water BOD", "No Sensor Availaible"), spelling included, which is a more useful thing to leave in a hackathon build than a fabricated float would have been. A judge reading the database sees exactly which channels are real.

What the firmware does

  • Connects over an ESP8266 through WiFiEsp, blocking on WL_CONNECTED before doing anything else, then opens a Firebase session with a host and auth token compiled into the binary.
  • Reads eight measured values across air and water: humidity, air temperature, CO2, CO, PM2.5, pH, TDS and water temperature.
  • Derives two more for SOx and NOx from weighted CO and CO2 ratios.
  • Writes eleven fields into the realtime database with setFloat and one setString, each followed by a Firebase.failed() check that prints the error and bails out of the update.
  • Compensates TDS for temperature by calling the DS18B20 read inside get_TDS() and pushing the result into gravityTds.setTemperature() before sampling.
  • Median-filters the pH channel over ten samples with the outer four discarded.
  • Times the dust sensor by hand against the datasheet's sampling, settling and sleep intervals.
  • Prints everything to serial at 9600 baud alongside the upload, so the rig is debuggable with a cable when the WiFi is not cooperating.

The app that was not built

EmissionMeasuremrntAI/ is an Android Studio project under the package com.hacktu.emission, compileSdk 33, minSdk 24. It contains one MainActivity whose entire body is setContentView, one layout file, and a dependency list of appcompat, material and constraintlayout with no Firebase client anywhere in it. It is the template, generated and committed and then abandoned when the weekend ran out. Naming it here rather than describing a dashboard that does not exist is the point.

Honest limitations

loop() is empty. Every read function and the Firebase uploader are defined and none of them are called, so the sketch as committed connects to WiFi and then does nothing forever. It would not compile either: firebase_update_data() calls get_tem_air() where the function is get_temp_air(), returns false eleven times from a void, and int buffer_arr[10], temp; redeclares temp, which is already a global float. The Firebase auth secret and the database host are hardcoded in the source, and the WiFi credentials are still the example's literal "SSID" and "PASSWORD".

Beyond compilation, the design has real gaps. Each Firebase setter calls its getter fresh, so get_co_ppm() and get_gas_ppm() each run three times per update and the DS18B20 is read twice, which means the CO figure in the SOx field is a different sample from the one in the CO field. The keys are flat and unstamped, so every write overwrites the last and there is no history in the database at all, only a current-value snapshot. R0 = 7200.0 for the MQ7 and 21.34 for the pH probe are both borrowed from the reference sketches rather than calibrated against these particular sensors, which makes the absolute values indicative and the trends the only part worth reading. And an MQ135 is an air-quality resistor, not a CO2 meter; labelling its output Air CO2 is the sort of shortcut a 24-hour build makes and a real deployment could not.

What it demonstrates is the chain: seven probes with incompatible read patterns, one board, one calibrated read loop, and a cloud record the plant does not own.

Project Details

STATUS
Prototype
ROLE

Firmware / Sensor Integration

YEAR

2023

TYPE

Hackathon · IoT

TAGS
Hackathon
Sensors
Environmental Monitoring
Embedded