G14: Smart Food Freshness Monitor - shalan/CSCE4301-WiKi GitHub Wiki
| Name | GitHub |
|---|---|
| Amal Fouda | amalfouda |
| Omar Saleh | omaranwar1 |
GitHub Repo: https://github.com/amalfouda/Smart-Food-Freshness-Monitor.git
The Smart Food Freshness Monitor is a small device you put inside or next to a food container that tells you when food is starting to go off, before you can see or smell it. It reads the gases that spoiling food gives off using two gas sensors (an MQ-135 for ammonia and general VOCs, and an MQ-3 for ethanol), plus a DHT22 for temperature and humidity, and turns all of that into one freshness score from 0 to 100 on a small LCD screen.
We built it because food waste is a growing problem in Egyptian households, made worse by rising food prices and frequent power cuts that quietly raise refrigerator temperatures without the owner noticing. People end up tossing things only once they already look or smell bad. By then it's too late. We wanted a way to catch food while it's still on the way down, so it can be used or frozen in time.
To make the score actually meaningful, you press a button to pick what kind of food you're checking (meat, bread, fruit, or dairy). Each option uses different sensor weights, since for example ethanol matters a lot for fruit but barely matters for meat. The screen then shows FRESH, WARN, or SPOIL as things change. It all runs on an STM32L432KC (NUCLEO-L432KC) board with FreeRTOS, and we power it from two 18650 batteries so it doesn't need to be plugged into anything.
Presentation Slides:
- Proposal: View on Canva
- First Milestone: View on Canva
- Final: View on Canva
- Read ammonia and VOC levels continuously using MQ-135
- Read ethanol and fermentation gases using MQ-3
- Read temperature and humidity using DHT22
- Allow the user to select a food type (meat, bread, fruit, or dairy) using a push button, loading the correct gas-weight profile for that food
- Compute a weighted freshness score (0–100) updated every 5 s
- Display live score, temperature, humidity, freshness label, and active food mode on a 16×2 LCD
- Self-calibrate in clean air and snap the score to 100 when sensors are near baseline
- Power the system from two series-connected 3.7V lithium-ion cells (7.4V nominal) stepped down to 5V via a Mini-360 buck converter
- Wireless alerting to push a notification when a food drops to WARN/SPOIL while the user is away
- On-device data logging and a companion app to chart freshness trends over time
- Per-sensor calibration against reference gases for true (lab-grade) PPM readings instead of relative estimates
- Expanded food library beyond the four base profiles (e.g. fish, cooked leftovers, vegetables)
Each requirement below is testable and is traced to the evidence in §6.3 Performance Metrics and the testing tables in §5.
| ID | Requirement |
|---|---|
| FR-1 | The system shall continuously sample MQ-135 (NH₃/VOC) and MQ-3 (ethanol) without blocking the UI |
| FR-2 | The system shall read temperature and humidity from the DHT22 |
| FR-3 | The user shall be able to select one of four food types (meat, bread, fruit, dairy) via a push button |
| FR-4 | Selecting a food type shall load that food's gas-weight profile |
| FR-5 | The system shall compute a 0–100 freshness score and refresh it at least every 10 s |
| FR-6 | The LCD shall display the active food mode, freshness label, score %, and sensor readings |
| FR-7 | The score shall classify freshness into FRESH / WARN / SPOIL bands |
| FR-8 | In clean air the system shall report a score of 100 (FRESH) |
| FR-9 | The system shall run on its 18650 battery circuit |
Each food type loads its own profile. The numbers below are what we expect the MQ-135 (ammonia/VOCs), MQ-3 (ethanol), and DHT22 (temperature/humidity) to read at each stage of spoilage inside a small sealed enclosure. They come from published food-science studies and the sensor datasheets, adapted to our setup. See the sources note after the weight table.
| Food | Primary Spoilage Gases | MQ-135 Fresh (ppm) | MQ-135 Borderline (ppm) | MQ-135 Spoiled (ppm) | MQ-3 Fresh (ppm) | MQ-3 Borderline (ppm) | MQ-3 Spoiled (ppm) | Safe Temp | Danger Temp | Safe RH | Danger RH |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Meat | NH₃, TMA, H₂S, amines | < 25 | 25–100 | > 100 | < 10 | 10–50 | > 50 | ≤ 4 °C | > 4 °C | 75–85% | > 85% |
| Bread | Ethanol (yeast), mold VOCs | < 50 | 50–150 | > 150 | < 20 | 20–100 | > 100 | 15–20 °C | > 25 °C | < 75% | > 85% |
| Fruit | Ethanol, acetaldehyde, esters | < 50 | 50–150 | > 150 | < 30 | 30–150 | > 150 | 0–13 °C | > 20 °C | 80–95% | > 95% |
| Dairy | Ethanol, acetaldehyde, DMS, NH₃ | < 30 | 30–100 | > 100 | < 15 | 15–80 | > 80 | ≤ 4 °C | > 7 °C | 85–90% | > 90% |
Each food gets its own set of sensor weights, because the gas that signals spoilage in one food barely matters in another. The score is a weighted average of the four spoilage readings, inverted so that 100 means fresh:
freshness = 100 − (Σ wᵢ · sᵢ) / (Σ wᵢ)
where each sᵢ is that sensor's 0–100 spoilage reading. The weights below are
the exact values from profiles[] in Food_Score.c. They're relative, so
only the ratios matter, since the score divides by their sum.
| Food | MQ-135 (NH₃/VOC) | MQ-3 (Ethanol) | Temp | RH | Why it's weighted this way |
|---|---|---|---|---|---|
| Meat | 56 | 17 | 27 | 10 | Ammonia from bacterial breakdown is the clearest sign meat has turned, so the MQ-135 leads. Temperature comes next, since warm meat spoils fast. |
| Bread | 25 | 42 | 33 | 40 | Bread goes off by growing mold in damp, warm air, so humidity and temperature matter nearly as much as the ethanol given off by yeast. |
| Fruit | 22 | 50 | 28 | 10 | Overripe fruit gives off ethanol, so the MQ-3 dominates. Warm storage speeds ripening, so temperature is second. |
| Dairy | 32 | 37 | 31 | 5 | Souring milk shows up first as ethanol and acetaldehyde, with ammonia later, so both gas sensors share the load. Keeping it cold is what matters most after that. |
Where the numbers come from. The thresholds and weights are adapted from published food-science work and the sensor datasheets (full citations in §8.2), then tuned for our enclosure. They're meant for relative scoring, not lab-calibrated limits.

The system splits into four subsystems, coordinated by FreeRTOS tasks and a few shared global variables on the STM32L432KC:
-
Sensing. The MQ-135 and MQ-3 analog outputs go into ADC1, which runs in
DMA circular-buffer mode: the DMA controller keeps writing fresh readings
into
adc_buffer[]in the background, so the CPU never stalls waiting for a conversion. The DHT22 is read separately by toggling PA6 in software once per scoring cycle. -
Scoring.
Food_Score.cturns the raw ADC counts into a per-sensor spoilage percentage with a sigmoid curve, adds temperature and humidity as linear functions, and combines them with the food's weights into the 0–100 score. - User interface. A 16×2 LCD on a PCF8574 I²C backpack shows the food mode, status label, score, live PPM estimates, and temperature. A push button on PB0 cycles through the four food profiles, with debouncing.
- Power. Two 18650 cells in series (7.4 V) feed a Mini-360 buck converter that drops the voltage to 5 V for the sensors, LCD, and the Nucleo's VIN pin. The Nucleo's onboard regulator then supplies 3.3 V to the STM32 core and the DHT22.
The subsystems stay decoupled through the shared globals (adc_buffer[],
lastScore, currentFoodMode) and FreeRTOS task notifications, which keep each
task simple and let the scoring loop run on a fixed 5-second cycle without
blocking the display.
Controller (STM32L432KC) 32-bit ARM Cortex-M4 at 80 MHz with FPU for floating-point scoring, fast multi-channel ADC for both gas sensors, low-power modes for always-on monitoring, and onboard ST-LINK for USB flashing and debug.
Ammonia / VOC sensor (MQ-135) Detects ammonia and volatile organic compounds released by spoiling meat and dairy. Analog output connects directly to an ADC pin with no extra circuitry beyond the voltage divider.
Ethanol sensor (MQ-3) Tuned for ethanol produced by overripe fruit, fermenting bread, and souring dairy. Paired with the MQ-135 to distinguish different spoilage signatures rather than relying on a single gas reading.
Temperature & humidity sensor (DHT22) Provides both temperature and humidity over a single data line with accuracy sufficient to detect a fridge warming up.
Display (16×2 LCD + PCF8574 I²C backpack) I²C interface reduces wiring to two lines (SDA and SCL) instead of seven parallel pins, keeping GPIO usage and breadboard clutter minimal.
Food-mode button Single momentary push button wired to an EXTI interrupt with software debounce, allowing the user to instantly cycle through the four food profiles (meat, bread, fruit, dairy).
Power (2× Li-Ion 3.7V + Mini-360 buck converter) Two lithium-ion cells in series provide 7.4V nominal. The Mini-360 buck converter steps this down to a regulated 5V for the sensors, LCD, and Nucleo VIN pin. The Nucleo's onboard LDO then supplies 3.3V to the DHT22 and STM32 core. Batteries are charged externally.


| STM32 Pin | Connected To |
|---|---|
| PB0 | Push button (to GND) |
| PA0 | MQ-135 analog out |
| PA1 | MQ-3 analog out |
| PA6 | DHT22 data |
| PB6 | LCD SCL (I²C) |
| PB7 | LCD SDA (I²C) |
| VIN | 5 V from Mini-360 |
| 3V3 | DHT22 |
| GND | Common ground |
| Component | Part Number | Qty | Unit Cost (EGP) | Datasheet |
|---|---|---|---|---|
| STM32 dev board | NUCLEO-L432KC (STM32L432KCU6) | 1 | 1,250 | Datasheet |
| Ammonia / VOC gas sensor | MQ-135 | 1 | 90 | Datasheet |
| Ethanol gas sensor | MQ-3 | 1 | 80 | Datasheet |
| Temp + humidity sensor | DHT22 / AM2302 (3-pin) | 1 | 260 | Datasheet |
| 16×2 LCD + I²C backpack | HD44780 + PCF8574 | 1 | 150 | LCD, PCF8574 |
| Push button | 12×12 mm tactile switch | 1 | 5 | N/A |
| 18650 Li-ion cell | 18650 (3.7 V, ~2600 mAh) | 2 | 200 | N/A |
| Step-down buck converter | Mini-360 | 1 | 35 | Datasheet |
| Breadboard + jumper wires | N/A | N/A | 80 | N/A |
| Total | 2,150 EGP |
Prices are local retail prices from Makers Electronics and FUT Electronics.
| Rail | Component | Typical (mA) | Peak (mA) |
|---|---|---|---|
| 5 V | MQ-135 heater | 150 | 150 |
| 5 V | MQ-3 heater | 150 | 150 |
| 5 V | LCD + backlight | 17 | 20 |
| 3.3 V | STM32L432KC | 7 (run 80MHz) | 8.5 |
| 3.3 V | DHT22 | 0.2 | 1.5 |
| 3.3 V | PCF8574 logic | 0.1 | 0.5 |
| Total | ≈ 324 mA | ≈ 331 mA |
At peak the system draws 331 mA, almost all of it the two MQ heaters. This load runs off the 5 V rail, which the Mini-360 buck converter supplies from the 7.4 V battery. The Mini-360 is rated for 1.8 A, so at peak it runs at under a quarter of its limit. On the battery side, the 5 V rail draws 324 mA in normal use. Referred to the 7.4 V battery, that is 324 mA * (5 V / 7.4 V) = 219 mA drawn from the cells. A 2,600 mAh pack therefore lasts 2,600 mAh / 219 mA = 11.9 hours per charge, slightly less in practice once converter losses are counted.
Where these figures come from. The two MQ heater currents are calculated from the Hanwei datasheets: each heater is about 33 Ω at 5 V, so it draws 5 V / 33 Ω ≈ 150 mA. The STM32L432KC run current is the ST datasheet figure for Run mode at 80 MHz, and the DHT22 (measuring vs standby) and PCF8574 numbers are from their datasheets.
The firmware is built on FreeRTOS (CMSIS-RTOS v2) running on top of the ST HAL. CubeMX generates the peripheral init code; application logic lives in four tasks:
| Task | Stack | Priority | Period | Responsibility |
|---|---|---|---|---|
defaultTask |
128 W | Normal | 1 s tick | Idle / heartbeat |
SensorReadTask |
256 W | Normal | 5 s loop | Read DMA buffer + DHT22, call ComputeFreshnessScore(), update lastScore
|
DisplayTask |
256 W | BelowNormal | 1 s loop | Render the LCD from lastScore and currentFoodMode under lcdMutex
|
ButtonTask |
128 W | AboveNormal | event | Sleeps on a task notification from the EXTI0 ISR, debounces, advances currentFoodMode, re-enables the IRQ |
Shared state (adcBuffer[], lastScore, currentFoodMode) is plain global
memory. LCD access is protected by lcdMutex; all writes in DisplayTask
and ButtonTask are wrapped in osMutexAcquire / osMutexRelease.
Boot sequence
flowchart TD
A[Reset] --> B[HAL_Init &<br/>SystemClock_Config<br/>80 MHz MSI+PLL]
B --> C[MX_GPIO / DMA / ADC1 /<br/>USART2 / I2C1 / TIM2 Init]
C --> D[osKernelInitialize<br/>create 4 tasks]
D --> E[osKernelStart]
E --> F[SensorReadTask warm-up<br/>33 s heater stabilisation]
F --> G[ADC DMA continuous +<br/>DisplayTask shows<br/>'Warming sensors / Wait: XXs']
G --> H[Steady state:<br/>scoring loop @ 5 s<br/>LCD refresh @ 1 s]
Food-mode state machine (driven by button press)
stateDiagram-v2
[*] --> MEAT
MEAT --> BREAD: press
BREAD --> FRUIT: press
FRUIT --> DAIRY: press
DAIRY --> MEAT: press
Freshness state machine (output of every scoring cycle)
flowchart TD
S([score]) --> Q{score ≥ 70?}
Q -- yes --> G{MEAT or DAIRY<br/>and temp in<br/>danger zone?}
G -- no --> F[STATUS_FRESH<br/>LCD: 'FRESH']
G -- yes --> W[STATUS_WARN<br/>LCD: 'WARN ']
Q -- no --> Q2{score ≥ 40?}
Q2 -- yes --> W
Q2 -- no --> SP[STATUS_SPOIL<br/>LCD: 'SPOIL']
Each gas sensor's raw ADC reading is turned into a 0–100 spoilage value with a sigmoid (S-curve) that sits between the food's fresh and borderline thresholds:
half_band = (border_adc - fresh_adc) / 2
mid = (fresh_adc + border_adc) / 2
k = ln(19) / half_band // ≈ 2.944 / half_band
spoilage = 100 / (1 + exp(-k * (raw - mid)))A sigmoid is used here because spoilage doesn't track gas concentration in a straight line. What matters is the crossing from fresh into spoiling: a rise from 50 to 100 ppm is the food actually turning, while a rise from 350 to 400 ppm just means it is even more spoiled than it already clearly was. A linear mapping would treat those two jumps the same, which is wrong. The sigmoid instead puts most of its sensitivity around the fresh-to-borderline band and flattens out at both ends, so it reacts strongly right where freshness is changing and barely moves once a reading is already very low (clearly fresh) or very high (clearly spoiled).
k is chosen so the curve reads about 5 % spoilage at the fresh threshold and
95 % at the borderline one, climbing smoothly in between.
Temperature and humidity don't need an S-curve; they map straight onto spoilage with a simple ramp:
// temperature: 0% at the safe limit, 100% once 15 C past it
if (temp <= safe_max) temp_spoilage = 0;
else if (temp >= safe_max + 15) temp_spoilage = 100;
else temp_spoilage = (temp - safe_max) * 100 / 15;
// humidity: 0% at the safe limit, 100% at the danger limit
if (rh <= rh_safe) rh_spoilage = 0;
else if (rh >= rh_danger) rh_spoilage = 100;
else rh_spoilage = (rh - rh_safe) * 100 / (rh_danger - rh_safe);Temperature reads 0 % spoilage at or below the food's safe limit and 100 % once it is 15 °C past it, climbing in a straight line between the two. Humidity works the same way, ramping from the food's safe level up to its danger level.
weighted_sum = w_MQ135*s_MQ135 + w_MQ3*s_MQ3 + w_Temp*s_Temp + w_RH*s_RH
score = 100 - weighted_sum / sum(w_*)This is the final step: the four spoilage readings are blended with the food's weights into one number, then flipped so that 100 means fresh and 0 means spoiled. Dividing by the sum of the weights keeps the result on a 0–100 scale whatever the weights happen to add up to.
That formula has one side effect. The sigmoid from §4.3.1 never quite reaches 0, so even in clean air the gas readings sit a little above zero and drag the score down to 97–98 % instead of a clean 100. To fix that, when both gas readings are within 10 ADC counts of their clean-air baselines the score is snapped straight to 100.
Alongside the score, the LCD also shows a PPM figure so the user sees the actual gas level, not just the 0–100 number. We get it by scaling the raw ADC count linearly onto the sensor's full-scale range. It is rough: a true PPM reading needs the sensor's logarithmic resistance curve from the Hanwei datasheet. For showing relative freshness, though, the estimate is good enough.
Meat and dairy are the two foods here that nedd to stay cold, and the most common reason a home fridge warming up unnoticed is a power cut. And food doesn't start giving off spoilage gases the moment it warms; that lag can be hours, so waiting on the gas sensors would mean catching the problem far too late.
So for these two modes we keep an eye temperature on its own aswell. If the temperature reading alone climbs deep into the danger zone (more than 80% of the way to full temperature spoilage, roughly 16–17 °C for meat and 19–20 °C for dairy), the status is bumped up to at least WARN even when the gases still read perfectly fresh, so a warming fridge is flagged before the food has had time to start giving off spoilage gases.
// meat and dairy only, and only when temperature alone is deep in the danger zone
if ((mode == MEAT || mode == DAIRY) && temp_spoilage > 80) {
if (status == FRESH) status = WARN; // escalate only; never softens SPOIL
}The guard only ever pushes the status the safe way: it can turn FRESH into WARN, but it never softens an existing SPOIL back to WARN. Bread and fruit are left out: a warm room isn't an emergency for them the way it is for raw meat or milk.
- MCU: STM32L432KCU6 (NUCLEO-L432KC dev board)
- IDE: Keil µVision (MDK-ARM V5)
- Configurator: STM32CubeMX (peripheral init + FreeRTOS setup)
- HAL: STM32Cube FW_L4
- RTOS: FreeRTOS via CMSIS-RTOS v2
- Debugger: ST-LINK V2
- Version control: Git + GitHub
| Module | Test | Outcome |
|---|---|---|
lcd_i2c |
Write static strings to row 0 and row 1 immediately after boot | Pass: all 32 cells rendered correctly |
food_score |
Bench-fed synthetic ADC values for each food profile and verified score | Pass: all four profiles return 100 % at baseline, 0 % at saturation |
dht22 |
Read sensor in known room conditions (24 °C, 55 % RH measured with reference thermometer) | Pass: readings within 0.5 °C and 2 % RH of reference |
| Button ISR | Tapped button 20 times in succession, counted mode changes | Pass: no phantom presses after 50 ms debounce window |
| ADC + DMA | Verified adcBuffer[] updates continuously by printing raw values over UART at 115200 baud |
Pass: both channels updating independently |
End-to-end behaviour was validated using known stimuli placed inside the enclosure with the lid closed to concentrate gases:
| Stimulus | Mode | Expected outcome | Result |
|---|---|---|---|
| Clean room air after 33 s warmup | any | score = 100, FRESH | Pass |
| Hand sanitizer (ethanol-rich) held 5 cm from sensors | BREAD | score drops to SPOIL within 30 s | Pass |
| Hand sanitizer held 5 cm from sensors | MEAT | score remains FRESH or WARN; MEAT profile down-weights ethanol | Pass |
| Spoiled dill (real sample, 4 days old) placed inside enclosure | FRUIT | score drops to SPOIL within 60 s | Pass: see demo video |
| Button cycled through all 4 modes while sensors active | all | LCD updates mode label instantly, score recalculated on next 5 s cycle | Pass |
| DHT22, breath blown directly onto sensor | any | temperature rises 2–3 °C, humidity rises visibly on LCD | Pass |
| Power cycle mid-operation | any | system reboots, runs warmup countdown, returns to MEAT default | Pass |
MQ sensor warm-up. Both MQ-135 and MQ-3 produce unstable readings for
approximately 30 seconds after power-on as the internal heater stabilises.
The readings drift downward from an inflated starting value and settle near
the clean-air baseline only after the heater reaches operating temperature.
Solution: SensorReadTask blocks for 33 seconds before its first scoring
iteration. During this window the LCD displays a countdown banner
("Warming sensors / Wait: XXs") so the user knows the system is not ready.
Button bounce caused phantom mode changes. A single tap could cycle
through two or three modes because the mechanical contacts bounce for several
milliseconds after release, generating multiple EXTI edges.
Solution: the EXTI0 ISR immediately disables itself and notifies
ButtonTask via a direct task notification. ButtonTask waits 50 ms,
samples the pin to confirm it is still low, waits for full release, waits
a further 50 ms, then re-enables the IRQ. This eliminates all observed
phantom presses across 20 consecutive tests.
LCD tearing during concurrent access. DisplayTask refreshes the LCD
every second while ButtonTask may write a mode-change banner at any time.
Without synchronisation the two tasks could interleave writes mid-string.
Solution: all LCD calls in both tasks are wrapped in
osMutexAcquire(lcdMutex) / osMutexRelease(lcdMutex), ensuring atomic
screen updates.
ADC readings affected by sensor placement. Early tests showed the MQ-135 and MQ-3 responding to ambient lab air rather than the food sample, giving inconsistently low spoilage scores. Solution: the sensors were repositioned inside a small enclosed enclosure alongside the food sample, concentrating the emitted gases and giving consistent, repeatable readings.
PPM values are estimates, not lab-grade. The MQ sensors are mapped linearly from ADC counts onto a nominal full-scale PPM range. A true concentration reading requires per-sensor Rs/R0 calibration against reference gases using the logarithmic curve from the Hanwei datasheet. The values are reliable for relative trend monitoring, not absolute air-quality measurement.
Single-point calibration. Clean-air baselines are captured once after warm-up. The system does not compensate for long-term sensor drift, ambient temperature effects on the MQ heaters, or sensor aging over months of use.
MQ heater warm-up delay. The system requires 33 seconds after power-on before the first valid score is produced, making it unsuitable for instantaneous spot checks.
No data logging or connectivity. Readings are displayed live on the LCD only. There is no historical logging, wireless alerting, or companion application in the current build.
Thresholds are literature-derived, not empirically fitted per food item. The gas-weight profiles are based on published food-science ranges. Individual products such as a specific hard cheese versus fresh milk may spoil at somewhat different gas concentrations and signatures.

Spoiled dill, score drops to SPOIL
586831491-ac265d47-4b94-411a-9478-dec1e55d7a4c.mp4
Changing food modes
586831572-2d888a54-9fe3-4e32-b70f-12c1d8b9b7e7.mp4
Very ripened banana (FRUIT mode)
very.ripened.banana.mp4
| Metric | Target | Measured |
|---|---|---|
| Score-update latency | ≤ 10 s | 5 s |
| LCD refresh rate | ≥ 0.5 Hz | 1 Hz |
| Cold-boot to first valid score | ≤ 60 s | ~33 s |
| Score differentiation MEAT vs BREAD on ethanol | distinct | MEAT stays FRESH, BREAD drops to SPOIL |
Each functional requirement from 1 and the test or result that demonstrates it:
| Req | Evidence | Met |
|---|---|---|
| FR-1 | ADC + DMA test: both channels update continuously in the background while the UI keeps running | Yes |
| FR-2 | DHT22 test: readings within 0.5 °C a reference | Yes |
| FR-3 | Button ISR test and the "button cycled through all 4 modes" integration test | Yes |
| FR-4 | Did a test using Hand-sanitizer: the same stimulus gives different results in MEAT vs BREAD, so the right profile is being loaded | Yes |
| FR-5 | Score-update latency measured at 5 s | Yes |
| FR-6 | LCD test and prototype photos: mode, label, score, and readings all shown | Yes |
| FR-7 | Test results span FRESH / WARN / SPOIL; freshness state machine | Yes |
| FR-8 | Clean-air test: score = 100, FRESH after warm-up | Yes |
| FR-9 | Power budget: runs off the two-cell 18650 pack | Yes |
| Member | Primary responsibilities |
|---|---|
| Amal Fouda | LCD integration in code, sensor calibration, connecting sensors and debugging, freshness-score algorithm, power subsystem with batteries, wiring & enclosure |
| Omar Saleh | DHT22 integration in code, sensor calibration, freshness-score algorithm, connecting sensors and debugging, wiring & enclosure, scientific-literature review for thresholds |
Both members co-authored the proposal, the milestone report, and this wiki.

https://github.com/amalfouda/Smart-Food-Freshness-Monitor
[1] Y. Xu, Z. Liu, J. Lin, J. Zhao, N. D. Hoa, N. V. Hieu, A. A. Ganeev, V. Chuchina, A. Jouyban, D. Cui, Y. Wang, and H. Jin, “Integrated Smart Gas Tracking Device with Artificially Tailored Selectivity for Real-Time Monitoring Food Freshness,” Sensors, vol. 23, no. 19, p. 8109, Sep. 2023. [Online]. Available: https://doi.org/10.3390/s23198109
[2] H. Jayan, R. Zhou, C. Sun, C. Wang, L. Yin, and X. Zou, “Intelligent Gas Sensors for Food Safety and Quality Monitoring: Advances, Applications, and Future Directions,” Foods, vol. 14, no. 15, p. 2706, Aug. 2025. [Online]. Available: https://doi.org/10.3390/foods14152706
[3] D. M. G. Preethichandra, M. D. Gholami, E. L. Izake, A. P. O'Mullane, and P. Sonar, “Conducting Polymer Based Ammonia and Hydrogen Sulfide Chemical Sensors and Their Suitability for Detecting Food Spoilage,” Advanced Materials Technologies, vol. 8, p. 2200841, Nov. 2022. [Online]. Available: https://doi.org/10.1002/admt.202200841
[4] A. E.-D. A. Bekhit, B. W. B. Holman, S. G. Giteru, and D. L. Hopkins, “Total Volatile Basic Nitrogen (TVB-N) and Its Role in Meat Spoilage: A Review,” Trends in Food Science & Technology, vol. 109, pp. 280–302, 2021. [Online]. Available: https://www.sciencedirect.com/science/article/abs/pii/S092422442100011X
[5] “When Does Milk Spoil? The Use of Rejection Threshold Methodology to Investigate the Influence of Total Microbial Numbers on the Acceptability of Fresh Chilled Pasteurised Milk,” Beverages, vol. 9, no. 2, art. 53, 2023. [Online]. Available: https://www.mdpi.com/2306-5710/9/2/53
[6] T. Alpers, R. Kerpes, M. Frioli, A. Nobis, K. I. Hoi, A. Bach, M. Jekle, and T. Becker, “Impact of Storing Condition on Staling and Microbial Spoilage Behavior of Bread and Their Contribution to Prevent Food Waste,” Foods, vol. 10, no. 1, 2021. [Online]. Available: https://pmc.ncbi.nlm.nih.gov/articles/PMC7824337/
[7] “Ethanol Effects on the Ripening of Climacteric Fruit,” Postharvest Biology and Technology. [Online]. Available: https://www.sciencedirect.com/science/article/abs/pii/S0925521497000318
[8] Hanwei Electronics Co., MQ-135 and MQ-3 Gas Sensor Technical Data (datasheets), linked in the Bill of Materials (§3.3).
[9] Component suppliers (BOM price references): Makers Electronics, https://makerselectronics.com/; FUT Electronics, https://store.fut-electronics.com/.