Temtop S1+ BLE Integration for Home Assistant

Temtop S1+ BLE Integration for Home Assistant (No App Required)

I reverse-engineered the BLE protocol of the Temtop S1+ air quality monitor and built a Python-based integration that sends sensor data directly to Home Assistant — without the official app, without MQTT, without any cloud.

:point_right: GitHub: GitHub - dmosler/temtop-s1plus-homeassistant: Read Temtop S1+ air quality data via BLE and send it to Home Assistant — no app required

Features

  • :droplet: PM2.5 – fine particle measurement (µg/m³)
  • :leaves: AQI – Air Quality Index
  • :thermometer: Temperature (°C)
  • :sweat_drops: Humidity (%)
  • :repeat: Updates every 60 seconds via BLE notify
  • :electric_plug: No MQTT broker needed – uses HA REST API directly
  • :gear: Autostart via systemd service
  • :iphone: No cloud, no app – fully local

How It Works

The Temtop S1+ exposes its data via a BLE GATT characteristic with notify support:

Characteristic UUID: 00010203-0405-0607-0809-0a0b0c0d2b10

The device sends a 46-byte data packet every few seconds when a client is subscribed. The relevant byte positions were determined by reverse engineering — comparing known display values to the raw hex payload:

Sensor Byte Position Calculation Example
PM2.5 22–23 int.from_bytes(data[22:24], 'big') / 10 0x000e = 14 → 1.4 µg/m³
Temperature 25 data[25] / 10 0xc9 = 201 → 20.1°C
Humidity 26–27 int.from_bytes(data[26:28], 'big') / 10 0x01fc = 508 → 50.8%
AQI 29 data[29] 0x088

The script connects every 60 seconds, subscribes to notifications for 15 seconds, reads the values, then disconnects. This keeps the BLE connection time minimal and is battery-friendly.


Requirements

Hardware:

  • Temtop S1+ air quality monitor
  • Raspberry Pi with Bluetooth (built-in or USB dongle)
  • Tested with: Raspberry Pi 1 Model B (2011) + TP-Link UB500

Software:

  • Raspberry Pi OS (Bullseye or newer)
  • Python 3
  • bleak library
  • Home Assistant with Long-Lived Access Token

Installation

sudo apt install -y bluetooth bluez python3-pip
pip3 install bleak --break-system-packages

Find your S1+ MAC address (close the Temtop app first!):

sudo hcitool lescan
# Look for: S1+_...

Create config file:

HA_TOKEN=your_long_lived_access_token
HA_URL=http://your_ha_ip:8123

Enable autostart:

sudo systemctl enable temtop
sudo systemctl start temtop

Lovelace Card

type: entities
title: Temtop S1+ Air Quality
entities:
  - entity: sensor.temtop_pm25
    name: PM2.5
    icon: mdi:air-filter
  - entity: sensor.temtop_aqi
    name: AQI
    icon: mdi:leaf
  - entity: sensor.temtop_temperature
    name: Temperature
    icon: mdi:thermometer
  - entity: sensor.temtop_humidity
    name: Humidity
    icon: mdi:water-percent

Important Notes

  • Close the Temtop app before starting the script — BLE only allows one active connection at a time. If the app is opened later, the script reconnects automatically within ~90 seconds.
  • First reading takes ~20 seconds after script start.

Feedback and pull requests welcome! :raised_hands:

1 Like

This looks great. It doesn’t work on the M10+ as I’ve just tested it, but I’m now wondering how I can work out the UUID for it, guessing its different from the S1.

Any tips on scanning for its UUID?

After reaching out to Temtop, they advised to use an app for debugging BLE called “Lightblue” which did indeed spit out a UUID.

Unfortunately it seems the references used by this model are different from the S1+

$ python3 temtop.py
Temtop M10+ monitor started
Connection error: BleakCharacteristicNotFoundError: Characteristic 00001800-0000-1000-8000-00805f9b34fb was not found!
No data received, retrying…

Next step I spose it to work out what each of the responses means.

A few things to clarify:

The UUID 00001800-0000-1000-8000-00805f9b34fb is a standard Generic Access Service
All BLE devices have this, it only contains the device name. Not what we need.

When looking for sensor data, focus on manufacturer-specific UUIDs.
These don’t start with 00001800, 00001801 or 0000180a. They look more like 00010203-0405-0607-0809-... on the S1+.

Try this script to list all characteristics:

import asyncio
from bleak import BleakClient

MAC = "YOUR_M10_MAC"

async def explore():
    try:
        async with BleakClient(MAC) as client:
            for service in client.services:
                print(f"\nService: {service.uuid}")
                for char in service.characteristics:
                    print(f"  {char.uuid} - {char.properties}")
    except EOFError:
        pass

asyncio.run(explore())

Look for a characteristic that:

  1. Has notify in its properties
  2. Belongs to a manufacturer-specific service (unusual UUID, not starting with 00001800 etc.)

For reference, here’s the output from my S1+.
The highlighted service is the one containing sensor data:

Service: 0000180a-0000-1000-8000-00805f9b34fb  ← standard, skip
Service: 00001800-0000-1000-8000-00805f9b34fb  ← standard, skip
Service: 00001801-0000-1000-8000-00805f9b34fb  ← standard, skip

Service: 00010203-0405-0607-0809-0a0b0c0d1910  ← manufacturer-specific ✅
  00010203-0405-0607-0809-0a0b0c0d2b10 - ['read', 'notify']  ← this is the one! ✅
  00010203-0405-0607-0809-0a0b0c0d2b11 - ['read', 'write-without-response']

Service: 00010203-0405-0607-0809-0a0b0c0d1912  ← manufacturer-specific
  00010203-0405-0607-0809-0a0b0c0d2b12 - ['read', 'write-without-response', 'notify']

The M10+ will likely have a similar pattern. A manufacturer-specific service with a notify characteristic.