Skip to main content

Fetch and visualize history from ThermoPro TP357S BLE temperature/humidity sensors

Project description

pytp357s

Fetch and visualize history from ThermoPro TP357S Bluetooth Low Energy temperature/humidity sensors, without the official app.

The TP357S uses an undocumented, reverse-engineered protocol (different from the original TP357) -- see PROTOCOL.md for the technical details, open questions, and known limitations.

Features

  • Fetch full history or live readings over BLE
  • Incremental fetching with overlap verification and gap detection
  • SQLite storage, ready to use as a Grafana data source
  • Built-in plotting (interactive or PNG) as a lightweight alternative to Grafana
  • Multi-device, with configurable concurrency

Installation

pip install pytp357s

Or from source:

git clone https://github.com/giovannipizzi/pytp357s.git
cd pytp357s
pip install .

Requires Python 3.9+. Depends on bleak (BLE), PyYAML, and matplotlib.

Configuration

All commands need a config file describing your devices. Copy the example and edit it:

cp examples/devices.example.yaml devices.yaml
defaults:
  max_count: 20000
  overlap: 15
  timeout: 120
  parallel: 2
  interval_minutes: 1 # do not change

devices:
  S1:
    mac: "AA:BB:CC:11:22:33"
    name: "Sensor Kitchen"
    room: "Kitchen"
  S2:
    mac: "AA:BB:CC:44:55:66"
    name: "Sensor Bedroom"
    room: "Bedroom"

Finding your devices' MAC addresses -- run a BLE scan:

python -c "from bleak import BleakScanner; import asyncio; \
           print(asyncio.run(BleakScanner.discover(timeout=20)))"

Look for entries named TP357S (XXXX). (The last 4 characters XXXX in brackets are typically the last 4 characters of the MAC address, e.g. 5566 if the MAC is AA:BB:CC:44:55:66).

Config file location

Commands look for the config file in this order:

  1. --config /path/to/devices.yaml (explicit, highest priority)
  2. ./devices.yaml (current working directory)
  3. ~/.config/pytp357s/devices.yaml

If none of these exist, the command exits with an error explaining all three options.

The defaults: section sets global defaults for --count, --overlap, --timeout, and --parallel; every value can still be overridden per command via CLI flags.

Usage

Fetch live readings

(Note: This only gets current reading, not the historical data, and uses a different protocol, but it's useful e.g. to check which device is in range).

pytp357s fetch-data S1 --live
pytp357s fetch-data all --live

Fetch full history (print to stdout, no storage)

pytp357s fetch-data S1

Fetch full history into a SQLite database

pytp357s fetch-data all --db sensors.db

If sensors.db already has data for a device, this is refused (to avoid accidental duplicate full fetches). Use instead --incremental or a different --db path.

Incremental updates (for cron/scheduled runs)

pytp357s fetch-data all --db sensors.db --incremental

This fetches only records newer than the last stored entry for each device (plus a small overlap, used to verify continuity), and:

  • refuses to write if the overlap doesn't match what's stored (possible clock issue: if you run the command from different computers, make sure your clocks are synchronized!) or if a gap is detected (e.g. if the device was out of range too long and data is not stored anymore in the device). Use --force to write anyway (a GAP marker row is inserted when forcing past a detected gap)
  • performs a full first fetch automatically if the device has no prior data in this database
# Force past a mismatch/gap (e.g. after the device was out of range for a while)
pytp357s fetch-data all --db sensors.db --incremental --force

# Debug an overlap mismatch in detail
pytp357s fetch-data S1 --db sensors.db --incremental --debug-overlap

# Run devices serially instead of 2-at-a-time (helps avoid BLE adapter contention)
pytp357s fetch-data all --db sensors.db --incremental --parallel 1

Update room names / device metadata only

fetch-data already syncs device metadata (mac, name, room) into the database on every run, so this is rarely needed -- but if you've just edited devices.yaml and want the database updated immediately without a BLE fetch, you can run:

pytp357s update-rooms --db sensors.db

Plot

# Interactive window
pytp357s plot --db sensors.db

# Save to PNG
pytp357s plot --db sensors.db --output plot.png

# Last 24 hours only
pytp357s plot --db sensors.db --hours 24

Using as a Python library

import asyncio
from pytp357s import protocol, storage
from pytp357s.config import load_config

cfg = load_config("devices.yaml")
device = cfg["devices"]["S1"]

# Live reading
temp, hum = asyncio.run(protocol.ble_fetch_live(device["mac"]))
print(f"{temp:.1f} C, {hum}%")

# Full history
readings, fetch_time = asyncio.run(
    protocol.ble_fetch_history(device["mac"], count=20000)
)
timestamped = protocol.assign_timestamps(readings, fetch_time)

# Store it
storage.init_db("sensors.db")
storage.append("sensors.db", "S1", timestamped)

For the higher-level fetch pipeline (incremental logic, overlap/gap verification), see pytp357s.fetcher.fetch_history().

Scanning devices

This is useful to see how many devices are in range. Note that this can only see the instantaneous temperature, to download the historical data you have to use the functions described above.

import asyncio
from bleak import BleakScanner

async def main():
    # Note: 20 seconds might be needed e.g. on macOS
    # to see all devices
    print("Scanning for 20 seconds...")
    devices = await BleakScanner.discover(timeout=20)
    print(f"\nFound {len(devices)} device(s):\n")
    for d in devices:
        print(f"  {d.address}  {d.name!r}")

    tp357 = [d for d in devices if d.name and "TP357" in d.name]
    print(f"\nTP357 devices found: {len(tp357)}")
    for d in tp357:
        print(f"  {d.address}  {d.name!r}")

asyncio.run(main())

Protocol details

See PROTOCOL.md for the full reverse-engineered protocol specification, including open questions and known limitations.

Acknowledgements

This project builds on the work of others:

Inspiration:

  • tpy357 and pasky/tp357 -- prior TP357 implementations that were an invaluable starting point and reference while reverse-engineering the TP357S's different protocol
  • The BTSnoop format documentation on the Wireshark wiki, used while parsing Android HCI snoop logs during protocol analysis

Dependencies:

  • bleak -- the Bluetooth LE library this project is built on
  • PyYAML and matplotlib -- configuration and plotting

Thanks to all of the above for making this possible.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytp357s-0.1.1.tar.gz (21.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytp357s-0.1.1-py3-none-any.whl (21.4 kB view details)

Uploaded Python 3

File details

Details for the file pytp357s-0.1.1.tar.gz.

File metadata

  • Download URL: pytp357s-0.1.1.tar.gz
  • Upload date:
  • Size: 21.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for pytp357s-0.1.1.tar.gz
Algorithm Hash digest
SHA256 9da6738f07c2746b81da55f926f84c816c98fae6235362de70f6abc14a60706b
MD5 e8e9003a4e58dd1ed9b5ff129215a299
BLAKE2b-256 7567638f2723cbc4c7c79a2ec8cf0ecbfd413b6d54a7e7590b35ea792f78de9d

See more details on using hashes here.

File details

Details for the file pytp357s-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: pytp357s-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 21.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for pytp357s-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 030cebcc0039d730884b9c91f85f144be455d8e62de0c83d54d417de9c68a594
MD5 da62e3d6166c8c0db55ec9ef67159be3
BLAKE2b-256 0eddf8b3c413e85926cb1ecc2f24b8217c261dd091324977a0b2395f5867fbc3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page