Skip to content

sputhenofficial/TempLog-Automation

Repository files navigation

Temperature Monitoring System

This system reads temperatures from wireless sensors attached to 60+ lab refrigerators and freezers every morning at 8:00 AM, logs each reading to an Excel workbook, and emails alerts for out-of-range conditions. Before automation, staff walked to each unit, read a thermometer, and wrote on a paper log — roughly 200 hours of labor per year across the fleet. The system runs on a Raspberry Pi sitting on the lab network and requires no daily interaction. The core engineering constraint is FDA 21 CFR Part 11: every electronic record must be attributable to a specific system identity, tamper-evident, and accurately timestamped. That constraint shaped most of the non-obvious design decisions described below.


Data Integrity

This is the most technically consequential part of the system, so it gets the most space.

Dual-record design

Every temperature reading produces two records simultaneously:

Temperature Log sheet (visible to anyone who opens the file): date, operator initials (always AUTO), and temperature. Out-of-range readings are highlighted red. The layout is three columns of 31 rows — 93 entries per workbook — matching the physical paper form QA already uses for manual inspection sign-off.

Audit sheet (password-protected): 15 fields per entry — entry number, ISO 8601 timestamp, OS username, hostname, script version, sensor ID, equipment ID, location, temperature, range min, range max, in-range flag, battery level, sensor online status, and a SHA-256 record hash. The sheet is protected at the openpyxl level; the protection is removed only for the duration of the write operation, then immediately re-applied.

SHA-256 hashing

Before writing, the system serializes the 14 non-hash audit fields to canonical JSON (json.dumps(..., sort_keys=True)) and SHA-256 hashes the result. That hash goes into the 15th field. Any post-write modification to field values will produce a different hash, detectable by verify_audit_integrity.py.

Independent hash registry

In addition to the hash embedded in the Excel audit sheet, every entry's hash is also appended to a separate CSV file at audit_verification/hash_registry_YYYY-MM.csv (one file per month). This is written independently of the Excel operation. If someone modifies the Excel audit sheet, the hashes will disagree with the registry — and vice versa. Neither file alone is sufficient to cover a tampering attempt against both.

Hardware-derived audit password

The audit sheet password is never stored anywhere. It is recomputed on each run from the Pi's hardware identity:

password = SHA-256( f"{uuid.getnode()}-{socket.gethostname()}-{salt}-Audit-2025" )[:16].upper()

uuid.getnode() returns the MAC address. The salt comes from settings.json. The first 16 hex characters of the hash become the password. Because it depends on hardware identity, the same password cannot be regenerated on a different machine, and it cannot be guessed without physical access to the Pi. To recover it for an FDA inspection:

python recover_audit_password.py

Every recovery attempt is logged with the username, timestamp, and reason.

Config integrity

On first run, the system SHA-256 hashes settings.json and sensors.json and stores the results in config/checksum_registry.json. On every subsequent run, it recomputes and compares before doing anything else. In production, a mismatch halts execution and sends an alert email. In development, it logs a warning and updates the registry automatically.


System Flow

08:00 AM — cron triggers main.py as svc_tempmonitor

  1. Access control check (OS group: tempmonitor_ops)
  2. NTP sync check via ntpstat
       → production: abort + email alert if not synced
       → development: warn and continue
  3. Config integrity check (SHA-256 vs checksum_registry.json)
       → production: abort + email alert on mismatch
       → development: auto-update registry
  4. Load and validate sensors.json

  For each sensor ID (sorted, deterministic order):
    a. Read temperature via SensorInterface
    b. Compare to temp_range [min, max]
    c. Write to Temperature Log sheet (red highlight if out of range)
    d. Write 15-field audit record + SHA-256 hash to Audit sheet
    e. Append hash to audit_verification/hash_registry_YYYY-MM.csv
    f. If out of range → send email alert
    g. If entry count reaches 93:
         - Move workbook to data/completed/
         - Create new workbook from template
         - Email QA: form complete, pending review
    h. On PermissionError saving Excel:
         - Retry 3× with 5-second delay
         - If still locked → write read-only CSV to data/fallback/
         - Hash the CSV entry with the same algorithm
         - Send email alert with instructions

  5. Write run summary JSON to logs/summaries/summary_YYYYMMDD_HHMMSS.json

08:30 AM — cron triggers scripts/verify_execution.py
    Checks that a summary file exists for today, is valid JSON,
    covered at least one sensor, and reported no failures.
    Sends alert email if verification fails.

The 93-entry limit is not arbitrary: the paper form QA uses for manual checks has three columns of 31 rows. Using the same layout means a printed Excel sheet and a paper form are visually identical, which matters during inspections.


Quick Start

# Install dependencies
pip install -r requirements.txt

# Run the full daily monitoring cycle
python main.py

# Run with mock sensors (no hardware needed)
python demo.py

# Verify yesterday's execution succeeded
python scripts/verify_execution.py

# Run all tests
python -m pytest tests/

# Unlock audit sheets for inspection
python recover_audit_password.py

# Check audit trail for tampering
python verify_audit_integrity.py

Sensor Configuration

config/sensors.json maps sensor IDs to equipment. Each entry:

{
  "SENSOR-ID-001": {
    "unit_name": "Fridge 12",
    "location": "Room 204",
    "equipment_id": "00812",
    "temp_range": [2.0, 8.0],
    "unit_type": "Refrigerator",
    "mock_base_temp": 5.0
  }
}

unit_type must be one of: Refrigerator, Freezer, Ultra Low Freezer, Cold Room.

mock_base_temp is required in development mode and ignored in production. The mock sensor generates readings by adding ±0.5°C variation to this value.

After editing sensors.json, reset the checksum registry so the system accepts the change:

rm config/checksum_registry.json
python main.py

Architecture

src/sensor_interface.py — Abstract base class defining the get_temperature(), get_sensor_data(), and list_sensors() interface that all sensor drivers must implement.

src/sensor_factory.py — Instantiates MockSensor or YoLinkSensor based on sensor_mode in settings.json. YoLink is not yet implemented.

src/mock_sensor.py — Simulates sensor readings using mock_base_temp ± 0.5°C random variation. Used for development and demo runs.

src/excel_logger.py — All Excel I/O: Temperature Log writes, Audit sheet management, SHA-256 hashing, hash registry appends, form rollover at 93 entries, write verification (re-reads cell from disk after save), and CSV fallback on PermissionError.

src/email_alerts.py — SMTP email notifications for temperature alerts, sensor offline, config integrity violations, NTP failure, Excel locked (fallback used), and form completion.

src/access_control.py — OS-level group membership check (tempmonitor_ops) for 21 CFR Part 11 attributability, plus execution logging to logs/access_audit.log.


Deployment

See DEPLOYMENT.md for the full step-by-step walkthrough — flashing the SD card, first boot, NTP setup, firewall, copying files, running the setup script, creating user accounts, and testing.

One-time setup on the Raspberry Pi:

sudo bash scripts/setup_raspberry_pi.sh

This creates the svc_tempmonitor service account, tempmonitor_ops and tempmonitor_admin OS groups, the directory structure under /opt/temp_monitor/, file permissions, and two cron jobs: main.py at 08:00 and scripts/verify_execution.py at 08:30, both running as svc_tempmonitor.

After setup, create .env with SMTP credentials:

SMTP_USERNAME=your-alert-email@gmail.com
SMTP_PASSWORD=your-app-password

Switch settings.json to "environment": "production" and "sensor_mode": "yolink" when going live.


Adding or Removing Sensors

  1. Edit config/sensors.json — add or remove the sensor entry.
  2. Delete config/checksum_registry.json to clear the stale checksum.
  3. Run python main.py once — this initializes a fresh registry against the new config.
  4. If adding: install the physical sensor, verify it appears in readings, document the change in your change control log.

Exit Codes

Code Meaning
0 All sensors read and logged successfully
1 Partial failure — at least one sensor succeeded, at least one failed
2 Complete failure — system error, NTP failure, or config integrity violation
3 Unauthorized — running user is not in tempmonitor_ops group

Requirements

  • Python 3.10+
  • openpyxl — Excel read/write
  • python-dotenv — loads SMTP credentials from .env
  • ntpstat — required on the production system for time sync verification
pip install -r requirements.txt

About

Automated lab temperature monitoring deployed on a Raspberry Pi with FDA 21 CFR Part 11 compliant audit trails. Replaces ~250 hours/year of manual logging.

Topics

Resources

Stars

Watchers

Forks

Contributors