Skip to main content

Python client for Scentience olfaction instruments over BLE Bluetooth

Project description

scentience

Python client for Scentience olfaction instruments over BLE Bluetooth.

Installation

BLE only:

pip install scentience

BLE + COLIP embedding models:

pip install "scentience[models]"

Requirements

  • Python 3.8+
  • Bluetooth 4.0+ adapter
  • A Scentience developer API key (obtain from the Scentience portal)
  • For embedding models: torch, torchvision, transformers, huggingface-hub, Pillow (installed via [models] extra)

Connecting a single device

Auto-discover

Scans for the first Scentience device in range and connects to it.

import scentience as scn

device = scn.ScentienceDevice(api_key="YOUR_API_KEY")
device.connect_ble(char_uuid="YOUR_CHAR_UUID")

Target a specific device

Pass the device UID (serial number or BLE name) to connect to a particular instrument.

device.connect_ble(char_uuid="YOUR_CHAR_UUID", device_uid="A00022")

Take a single reading

data = device.sample_ble()   # returns dict
print(data)

Stream continuously

def on_sample(data: dict) -> None:
    print(data["UID"], data.get("CO2"), data.get("ENV_temperatureC"))

device.stream_ble(callback=on_sample)

# ... your application runs here ...

device.stop_stream()
device.disconnect()

Context manager

disconnect() is called automatically on exit.

with scn.ScentienceDevice(api_key="YOUR_API_KEY") as device:
    device.connect_ble(char_uuid="YOUR_CHAR_UUID", device_uid="A00022")
    data = device.sample_ble()
    print(data)

Connecting multiple devices

Pass a list of device UIDs to connect_ble. All devices are scanned for in a single BLE scan and connected concurrently. The API key and characteristic UUID are the same for every device.

Snapshot from all devices

import scentience as scn

device = scn.ScentienceDevice(api_key="YOUR_API_KEY")
device.connect_ble(
    char_uuid="YOUR_CHAR_UUID",
    device_uids=["A00022", "A00010"],   # as many UIDs as needed
)

readings = device.sample_ble()   # returns List[dict], one entry per device
for r in readings:
    print(r)

Stream from all devices simultaneously

The same callback receives packets from every device. Use the UID field to identify the source.

def on_sample(data: dict) -> None:
    print(f"[{data['UID']}]  CO2={data.get('CO2')}  temp={data.get('ENV_temperatureC')}°C")

device.stream_ble(callback=on_sample)

# ... your application runs here ...

device.stop_stream()
device.disconnect()

Context manager (multiple devices)

with scn.ScentienceDevice(api_key="YOUR_API_KEY") as device:
    device.connect_ble(
        char_uuid="YOUR_CHAR_UUID",
        device_uids=["A00022", "A00010"],
    )
    readings = device.sample_ble()

Discovering devices

Before connecting, use scan_devices() to see exactly what BLE devices are visible and confirm the names your instruments advertise.

nearby = scn.ScentienceDevice.scan_devices(timeout=10.0)
for d in nearby:
    print(d["name"], d["address"], d["rssi"])

Pass any value from the name column as device_uid / device_uids.


Logging and export

All readings from sample_ble() and stream_ble() are automatically buffered in memory across all connected devices.

print(device.log)             # list of dicts

device.export_json("readings.json")
device.export_csv("readings.csv")

device.clear_log()

CSV column order: UID and TIMESTAMP first, then all remaining fields alphabetically. Missing fields are written as empty values.


Response payload

Each reading dict may contain:

Key Description
UID Device serial number
TIMESTAMP Reading timestamp
ENV_temperatureC Ambient temperature (°C)
ENV_humidity Relative humidity (%)
ENV_pressureHpa Barometric pressure (hPa)
BATT_health Battery health
BATT_v Battery voltage
BATT_charge Battery charge (%)
BATT_time Estimated battery time remaining
STATUS_opuA Operational status
CO2, NH3, NO, NO2, CO, C2H5OH Chemical compounds (non-zero only)
H2, CH4, C3H8, C4H10, H2S, HCHO, SO2, VOC Chemical compounds (non-zero only)

COLIP embedding models

ColipModel integrates the Olfaction-Vision-Language Embeddings to produce joint embeddings across olfaction, vision, and language modalities.

Four variants are available:

Variant Embed dim Architecture Best for
colip-small-base 512 base GNN Fast inference / edge devices
colip-small-gat 512 GAT Higher accuracy on edge devices
colip-large-base 2048 base GNN Accuracy-critical tasks
colip-large-gat 2048 GAT Highest accuracy, slower inference

Weights are downloaded from HuggingFace Hub and cached locally on first use.

Example — small base model

import scentience as scn
from PIL import Image

model = scn.ColipModel.from_pretrained("colip-small-base")
# ColipModel(variant='colip-small-base', embed_dim=512, device='cpu')

image   = Image.open("scene.jpg")
olf_vec = [0.0] * 138          # 138-dimensional olfactory descriptor

embedding = model.embed(image, olf_vec)       # torch.Tensor, shape (512,)
arr       = model.embed_numpy(image, olf_vec) # numpy array

Example — large GAT model

model = scn.ColipModel.from_pretrained("colip-large-gat")
embedding = model.embed(image, olf_vec)       # torch.Tensor, shape (2048,)

Example — BLE streaming + real-time embedding

import scentience as scn
from PIL import Image

model  = scn.ColipModel.from_pretrained("colip-small-base")
device = scn.ScentienceDevice(api_key="YOUR_API_KEY")
device.connect_ble(char_uuid="YOUR_CHAR_UUID", device_uid="A00022")

scene = Image.open("scene.jpg")

def on_sample(data: dict) -> None:
    olf_vec   = [data.get(k, 0.0) for k in sorted(data) if k not in ("UID", "TIMESTAMP")]
    embedding = model.embed(scene, olf_vec)
    print(f"embedding shape: {embedding.shape}, norm: {embedding.norm():.4f}")

device.stream_ble(callback=on_sample)

Targeting a specific compute backend

model = scn.ColipModel.from_pretrained("colip-large-base", device="cuda")  # GPU
model = scn.ColipModel(variant="colip-small-gat", device="mps")             # Apple Silicon

API reference

See the full BLE API documentation.

License

Apache 2.0

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

scentience-2.1.0.tar.gz (22.0 kB view details)

Uploaded Source

Built Distribution

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

scentience-2.1.0-py3-none-any.whl (19.4 kB view details)

Uploaded Python 3

File details

Details for the file scentience-2.1.0.tar.gz.

File metadata

  • Download URL: scentience-2.1.0.tar.gz
  • Upload date:
  • Size: 22.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.0

File hashes

Hashes for scentience-2.1.0.tar.gz
Algorithm Hash digest
SHA256 c0fe599f3dcb5dc6da74368e1763a630eca3487b2cf0d9d504402b3e3940886a
MD5 6a2b4defa204370fafed353ef2a22b3d
BLAKE2b-256 55a053a45b4433c68d53913b6a000517323e36518ae8da0a9c6ad775cd4e2158

See more details on using hashes here.

File details

Details for the file scentience-2.1.0-py3-none-any.whl.

File metadata

  • Download URL: scentience-2.1.0-py3-none-any.whl
  • Upload date:
  • Size: 19.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.0

File hashes

Hashes for scentience-2.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e83394cbf5d3c074441940dab37149a025ab7dc2c461b6112fcc3e2d19d36575
MD5 ff30b229e7288c9ecc184f4e7dc36fc5
BLAKE2b-256 1ca465e2f391c67a8a85f08282deb34b92e3a3961066c397bcc4f6b63c77ad39

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