Skip to content

Script for analyzing data from a temperature and humidity sensor

About

This script is a monitoring and alerting tool designed for the Papouch TH2E temperature and humidity sensor. It periodically retrieves live measurement data from the device’s XML endpoint (fresh.xml) and evaluates whether the readings remain within predefined safe ranges.

THIS IS NOT SPONSORED!

This script is tested with:

  • Device type: TH2E
  • Firmware version: 7.2/19 (Created 06.03.2023 09:20:01)
  • Core:TH2E; v0436.04.08; f66 97; t1; h1; rtc

If a temperature or humidity value goes outside the allowed limits, the script:

  • detects the abnormal reading,
  • sends an email alert to the configured recipients,
  • logs the event locally,
  • continues monitoring until the value returns to normal.

TH2E: Ethernet thermometer and hygrometer:

https://en.papouch.com/th2e-ethernet-thermometer-and-hygrometer-p4825

TH3: Temperature and humidity sensor:

https://en.papouch.com/th3-temperature-and-humidity-sensor-p4605

When the sensor readings stabilize again, the script sends a “back to normal” notification.

The script supports:

  • Temperature monitoring
  • Humidity monitoring
  • Automatic detection of XML namespaces (Papouch TH2E sometimes returns XML with or without namespaces)
  • State tracking (normal → alert → verify → normal)
  • Email notifications via SMTP
  • Local logging of all readings and events
  • Automatic startup on system boot (Windows Task Scheduler or Linux systemd)
  • Virtual environment isolation to avoid dependency conflicts

It is intended for environments where continuous monitoring of temperature and humidity is required, such as:

  • server rooms
  • network cabinets
  • laboratories
  • storage rooms

The script is lightweight, requires minimal system resources, and is suitable for running on Windows or Linux.


General & disclaimer

This script has been tested and verified on Windows 10/11 systems. A Linux version of the installation guide is provided for convenience (not tested), but the script has not been formally tested on Linux, and behavior may differ depending on distribution, environment configuration, and system security policies.

Real‑world deployments may vary. Network configurations, Python versions, system permissions, SMTP settings, and sensor firmware differences can all affect how the script behaves in production environments.

All commands, configuration changes, and system modifications described in this guide are performed at your own risk. Before deploying the script in a production environment, ensure that:

  • you understand each step,
  • you have appropriate permissions,
  • you have tested the script in a safe environment,
  • and you have backups or rollback options if needed.

This script is provided “as is,” without guarantees. Use it responsibly and verify that it meets your operational requirements before relying on it for critical monitoring.


Example data from endpoint

<?xml version="1.0" encoding="iso-8859-2"?>
<root xmlns="http://www.papouch.com/xml/th2e/act">
<sns id="1" type="1" status="0" unit="0" val="20.7" w-min="17.0" w-max="26.0" e-min-val="      15.4" e-max-val="      26.4" e-min-dte="11/21/2025 00:47:57" e-max-dte="11/25/2025 08:55:03" /><sns id="2" type="2" status="0" unit="3" val="12.7" w-min="0.0" w-max="55.0" e-min-val="      11.7" e-max-val="      63.4" e-min-dte="02/02/2026 08:52:08" e-max-dte="11/25/2025 09:39:07" /><sns id="3" type="3" status="0" unit="0" val="-8.9" w-min="" w-max="" e-min-val="      -9.6" e-max-val="      17.8" e-min-dte="02/02/2026 08:40:12" e-max-dte="11/25/2025 08:54:56" /><status frm="1" location="EXAMPLE-NAME" time="02/02/2026 10:18:45" typesens="3" /></root>

Example manual downloaded data

<root>
    <sns id="1" type="1" status="0" unit="0" val="20.7" w-min="17.0" w-max="26.0" e-min-val=" 15.4" e-max-val=" 26.4" e-min-dte="11/21/2025 00:47:57" e-max-dte="11/25/2025 08:55:03"/>
    <sns id="2" type="2" status="0" unit="3" val="12.3" w-min="0.0" w-max="55.0" e-min-val=" 11.7" e-max-val=" 63.4" e-min-dte="02/02/2026 08:52:08" e-max-dte="11/25/2025 09:39:07"/>
    <sns id="3" type="3" status="0" unit="0" val="-9.3" w-min="" w-max="" e-min-val=" -9.6" e-max-val=" 17.8" e-min-dte="02/02/2026 08:40:12" e-max-dte="11/25/2025 08:54:56"/>
    <status frm="1" location="EXAMPLE-NAME" time="02/02/2026 9:00:11" typesens="3"/>
</root>

Script

import time
import datetime
import pytz
import smtplib
import requests
import xml.etree.ElementTree as ET
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText


# === SMTP CONFIGURATION ===

URL = "http://IP/fresh.xml"
USERNAME = "admin"
PASSWORD = "PASSWORD"

SENDER_EMAIL = "SENDER_EMAIL"
SENDER_PASSWORD = "SENDER_PASSWORD" 
RECEIVER_EMAILS = ["[email protected]", "[email protected]"]
SMTP_SERVER = "SMTP_SERVER"
SMTP_PORT = SMTP_PORT


# === SCRIPT CONFIGURATION ===

NORMAL_TIME_INTERVAL = 1200
ERROR_TIME_INTERVAL = 300
CHECK_TIME_INTERVAL = NORMAL_TIME_INTERVAL

MIN_TEMP_VALUE = 13.0
MAX_TEMP_VALUE = 26.0

MIN_HUM_VALUE = 8.0
MAX_HUM_VALUE = 50.0

SUBJECT_ALERT = "SENSOR ALERT {atmospheric_parameter}"
SUBJECT_NORMAL = "SENSOR INFO {atmospheric_parameter}"

BODY_ALERT = "{atmospheric_parameter} has exceeded the allowed range {permissible_value}! Current value is {val}"
BODY_NORMAL = "{atmospheric_parameter} has returned to the allowed range {permissible_value}. Current value is {val}"

LOG_RUN_FILE = "log_launch.txt"
LOG_DATA_FILE = "log_data.txt"

ATMOSPHERIC_PARAMETERS = {
    "1": "Temperature",
    "2": "Humidity"
}

LIMITS = { 
    "1": (MIN_TEMP_VALUE, MAX_TEMP_VALUE), 
    "2": (MIN_HUM_VALUE, MAX_HUM_VALUE) 
}

SENSOR_STATES = {} # normal / alert / verify
ALERT_TYPE = {} # min / max


# === FUNCTIONS ===

def str_actual_date_time():
    return datetime.datetime.now().strftime("%D %T")

def save_to_log(data):
    with open(LOG_DATA_FILE, "a") as f:
       f.write(f"[{str_actual_date_time()}]\n{data}\n")

def send_email(subject, body):
    try:
        timezone = pytz.timezone('Europe/Warsaw')
        local_time = datetime.datetime.now(timezone)
        date_str = local_time.strftime("%a, %d %b %Y %H:%M:%S %z")

        with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as smtp:
            smtp.starttls()
            smtp.login(SENDER_EMAIL, SENDER_PASSWORD)

            for receiver in RECEIVER_EMAILS:
                msg = MIMEMultipart()
                msg["From"] = SENDER_EMAIL
                msg["To"] = receiver
                msg["Subject"] = subject
                msg["Date"] = date_str
                msg.attach(MIMEText(body, "plain"))
                smtp.send_message(msg)

    except Exception as e:
        print(f"Error: {e}")

def values_in_range(sns_id, val):
    if sns_id not in LIMITS: 
        return True     
    low, high = LIMITS[sns_id] 
    return low < val < high

def fetch_and_process_data():
    global CHECK_TIME_INTERVAL

    try:
        response = requests.get(URL, auth=(USERNAME, PASSWORD), timeout=180)
        response.raise_for_status()

        root = ET.fromstring(response.text)

        # detect namespace (in downloaded XML it may be missing, via browser it may be present)
        tag = root.tag
        if tag.startswith("{"):
            # XML with namespace
            ns_uri = tag.split("}")[0].strip("{")
            ns = {"n": ns_uri}
            sns_list = root.findall(".//n:sns", ns)
        else:
            # XML with out namespace
            sns_list = root.findall(".//sns")

        CHECK_TIME_INTERVAL = NORMAL_TIME_INTERVAL

        for sns in sns_list:
            sns_id = sns.get("id")

            if sns_id == '3': # skip dew point
                break

            sns_val = float(sns.get("val"))
            is_ok = values_in_range(sns_id, sns_val)
            prev_state = SENSOR_STATES.get(sns_id, "normal")
            if sns_id in ATMOSPHERIC_PARAMETERS:
                save_to_log(f"{ATMOSPHERIC_PARAMETERS[sns_id]} = {sns_val}")

            min_val, max_val = LIMITS[sns_id]
            ap = ATMOSPHERIC_PARAMETERS[sns_id]

            # === ALERT ===
            if not is_ok:
                SENSOR_STATES[sns_id] = "alert"

                if sns_val < min_val:
                    ALERT_TYPE[sns_id] = "min"
                    subject = SUBJECT_ALERT.format(atmospheric_parameter=ap)
                    body = (
                        f"{ap} dropped below the allowed minimum {min_val}. " 
                        f"Current value is {sns_val}" 
                    ) 

                elif sns_val > max_val:
                    ALERT_TYPE[sns_id] = "max"
                    subject = SUBJECT_ALERT.format(atmospheric_parameter=ap)
                    body = (
                        f"{ap} exceeded the allowed maximum {max_val}. "
                        f"Current value is {sns_val}"
                    )

                send_email(subject, body)

            # === FIRST OK READING AFTER ALERT ===
            elif is_ok and prev_state == "alert":
                SENSOR_STATES[sns_id] = "verify"

            # === SECOND OK READING → BACK TO NORMAL ===
            elif is_ok and prev_state == "verify":
                if ALERT_TYPE.get(sns_id) == "min": 
                    pv = min_val
                else:
                    pv = max_val

                send_email(
                    SUBJECT_NORMAL.format(atmospheric_parameter=ap),
                    BODY_NORMAL.format(atmospheric_parameter=ap, permissible_value=pv, val=sns_val)
                )

                SENSOR_STATES[sns_id] = "normal"

            # === NORMAL READING ===
            elif is_ok and prev_state == "normal":
                continue

    except Exception as e:
        CHECK_TIME_INTERVAL = ERROR_TIME_INTERVAL
        send_email("CZUJKA ERROR", f"{str_actual_date_time()} something went wrong\nException:\n{e}")


# === START LOG ===
with open(LOG_RUN_FILE, "a") as f:
    send_email("SENSOR INFO Script started", (f"{str_actual_date_time()} script started"))
    f.write("Script started: " + str_actual_date_time() + "\n")

# === MAIN LOOP ===
if __name__ == "__main__":
    while True:
        fetch_and_process_data()
        time.sleep(CHECK_TIME_INTERVAL)


Windows instruction

Check if Python is installed

python3 --version

If Microsoft Store opens: Install Python from the Store.


Prepare the directory structure

Create a folder for the script, e.g.: alerts_sensor Enter the folder Extract/copy the script files into this folder

Important: the script must NOT be loose on the Desktop — it must be inside a folder.


Create a virtual environment (venv)

Create the venv:

python3 -m venv venv

Activate the venv:

.\venv\Scripts\activate

If activation fails, run:

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Explanation:

  • RemoteSigned allows all local scripts and only signed scripts from the internet
  • Scope CurrentUser applies only to your user account

Then activate again:

.\venv\Scripts\activate

After activation, your prompt should start with (venv).


Install required Python modules

Inside the activated venv:

pip install requests pytz

Verify installation:

pip show requests

It should show a path inside the venv folder.


Run the script manually

Ensure venv is active:

.\venv\Scripts\activate

Run the script:

python3 alerts.py

or, if needed (see section 6):

python alerts.py

Stop the script: CTRL + C.


Error ModuleNotFoundError

This means you are running the system Python, not the venv Python.

Check which Python is being used:

where python
where python3

You should see:

...\alerts_sensor\venv\Scripts\python.exe

If not, run the script explicitly with venv Python:

venv\Scripts\python.exe alerts.py

or

python alerts.py

Automatically start the script after reboot (Task Scheduler)

Open Task Scheduler and create a new task (not a basic task)

General tab:

  • Name: any name
  • Run whether user is logged on or not
  • Do NOT store password
  • Run with highest privileges
  • Configure for: Windows 10

Triggers → New…

  • Begin the task: At startup
  • Enabled: Yes

Actions → New…

  • Action: Start a program
  • Program/script: C:\Users\USER\PATH\TO\alerts_sensor\venv\Scripts\python.exe
  • Add arguments (optional): alerts.py
  • Start in (optional): C:\Users\USER\PATH\TO\alerts_sensor

Conditions Leave default settings.

Settings Enable ONLY:

  • Allow task to be run on demand
  • If the task fails, restart every 5 minutes
  • Attempt up to 3 times
  • If the task is already running: Do not start a new instance

Linux instruction

Check if Python is installed

python3 --version

If Python is NOT installed

(Ubuntu/Debian)

sudo apt update
sudo apt install python3 python3-venv python3-pip -y

(CentOS/RHEL)

sudo dnf install python3 python3-pip -y

Prepare the directory structure

Create a directory and enter, e.g.: alerts_sensor:

mkdir alerts_sensor && cd alerts_sensor

Extract/copy the script files into this directory:

unzip alerts_sensor.py

Make the script executable:

chmod +x alerts.py

Create a virtual environment (venv)

Inside the script directory:

python3 -m venv venv

Activate the environment:

source venv/bin/activate

After activation you should see something like:

(venv) user@host:~/alerts_sensor$

Install required Python modules

Install dependencies:

pip install requests pytz

Verify installation (should show a path inside venv):

pip show requests

Run the script manually

In the project directory:

source venv/bin/activate
python3 alerts.py

Stop the script CTRL + C


Error ModuleNotFoundError

If you still get “ModuleNotFoundError” after installing modules

The most common cause: You are running the system Python instead of the venv Python.

Check which Python is being used:

which python3
which python

It should point to ~/alerts_sensor/venv/bin/python3

If it does NOT, run the script explicitly with venv Python:

venv/bin/python3 alerts.py

Automatically start the script after reboot (systemd)

Create a systemd service file:

sudo vim /etc/systemd/system/alerts.service

Paste the following:

[Unit]
Description=Scripty for sensor alerts
After=network.target

[Service]
Type=simple
User=YOUR_USERNAME
WorkingDirectory=/home/YOUR_USERNAME/alerts_sensor
ExecStart=/home/YOUR_USERNAME/alerts_sensor/venv/bin/python3 /home/YOUR_USERNAME/alerts_sensor/alerts.py
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Reload systemd:

sudo systemctl daemon-reload

Enable service at boot:

sudo systemctl enable alerts.service

Start the service:

sudo systemctl start alerts.service