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:
--config /path/to/devices.yaml(explicit, highest priority)./devices.yaml(current working directory)~/.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
--forceto write anyway (aGAPmarker 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytp357s-0.1.0.tar.gz.
File metadata
- Download URL: pytp357s-0.1.0.tar.gz
- Upload date:
- Size: 21.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a4085f3e367b7d3fcd0290d49caee6347d07e5d296333a28fde9b0e308d49549
|
|
| MD5 |
ca62be7bfbecb6bea8f62dd812749b21
|
|
| BLAKE2b-256 |
ff2ffb58506cee5a13a8a1e53359610ff9548067d3869dd4c3e10a344571c58b
|
File details
Details for the file pytp357s-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pytp357s-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f2246f90bd21ef8c6bee0d06c409527fa5ea5f8e955e971f26bd698086dc3c80
|
|
| MD5 |
0e2f0476a04010085afc8a6579d119ba
|
|
| BLAKE2b-256 |
b41989a318621fc2d7446b361c4b15cbb5298cdec7e1f590f1cc82a395ebb801
|