scentience
v2.0.0
Published
Connect to Scentience olfaction instruments via BLE Bluetooth
Maintainers
Readme
scentience
npm-installable package for interacting with Scentience olfaction instruments via BLE Bluetooth.
Installation
npm install scentienceFor Node.js environments, also install the optional BLE polyfill:
npm install webbluetoothBrowser and React environments use the native Web Bluetooth API — no extra dependencies needed.
Quick Start
import { ScentienceDevice } from 'scentience';
const device = new ScentienceDevice('<YOUR_API_KEY>');
await device.connectBLE({ charUuid: '<YOUR_CHAR_UUID>' });
// Single reading
const sample = await device.sampleBLE({ async: true });
console.log(sample);
// Continuous stream
device.on('data', (data) => console.log(data));
device.on('error', (err) => console.error(err));
await device.streamBLE({ async: true });React Example
import { useEffect, useState } from 'react';
import { ScentienceDevice } from 'scentience';
export default function App() {
const [reading, setReading] = useState(null);
useEffect(() => {
const device = new ScentienceDevice('<YOUR_API_KEY>');
device
.connectBLE({ charUuid: '<YOUR_CHAR_UUID>' })
.then(() => {
device.on('data', setReading);
return device.streamBLE({ async: true });
})
.catch(console.error);
return () => device.disconnect();
}, []);
return <pre>{JSON.stringify(reading, null, 2)}</pre>;
}API
new ScentienceDevice(apiKey)
| Parameter | Type | Description |
|-----------|----------|-------------------------|
| apiKey | string | Your Scentience API key |
device.connectBLE(options)
Scan for and connect to a Scentience peripheral.
| Option | Type | Default | Description |
|-------------|----------|--------------------------------------------|--------------------------------------|
| charUuid | string | '0000abcd-0000-1000-8000-00805f9b34fb' | GATT characteristic UUID |
| deviceUid | string | null | Filter scan to a specific device UID |
Returns Promise<ScentienceDevice>.
device.sampleBLE(options)
Request a single sensor reading.
| Option | Type | Default | Description |
|---------|-----------|---------|-----------------------------------------------|
| async | boolean | false | Return a Promise resolving with the data payload |
Returns Promise<ScienceData>.
device.streamBLE(options)
Begin continuous sensor streaming. Use .on('data', cb) to receive readings.
| Option | Type | Default | Description |
|---------|-----------|---------|----------------------------------------------------------|
| async | boolean | false | Return a Promise that stays pending until stopStream() |
Returns Promise<void>.
device.on(event, callback) / device.off(event, callback)
Subscribe or unsubscribe from stream events.
| Event | Callback signature |
|---------|-------------------------|
| data | (data: ScienceData) => void |
| error | (err: Error) => void |
device.stopStream()
Stop an active stream and release the BLE notification subscription.
device.disconnect()
Stop the stream and close the GATT connection.
device.isConnected
boolean — true when a GATT connection is active.
Data Payload
Sensor readings are delivered as a JSON object. Chemical compound fields are only present when their detected magnitude is greater than zero.
{
"UID": "device_identifier",
"TIMESTAMP": "2026-03-23T12:00:00.000Z",
"ENV_temperatureC": 22.4,
"ENV_humidity": 45.1,
"ENV_pressureHpa": 1013.2,
"STATUS_opuA": 120,
"BATT_health": 98,
"BATT_v": 3.85,
"BATT_charge": 87,
"BATT_time": 14.2,
"CO2": 412,
"VOC": 0.03
}| Prefix | Description |
|----------|---------------------------|
| ENV_ | Environmental parameters |
| BATT_ | Battery metrics |
| (none) | Chemical compound readings |
Connect to a Specific Device
await device.connectBLE({
charUuid: '<YOUR_CHAR_UUID>',
deviceUid: '<DEVICE_UID>',
});Olfaction-Vision-Language Embeddings
ScentienceEmbedder provides access to the four COLIP OVL models — a joint multimodal embedding space for olfaction, vision, and language data.
Install the required peer dependency:
npm install @huggingface/inference| Variant | Description |
|-------------------|--------------------------------------------------------|
| colip-small-base| Fast inference; edge robotics, mobile |
| colip-small-gat | Graph-Attention variant; edge with higher accuracy |
| colip-large-base| Best accuracy for online/server tasks |
| colip-large-gat | Graph-Attention variant; highest accuracy |
Example 1 — Embed a live sensor reading and match against text labels
import { ScentienceDevice, ScentienceEmbedder } from 'scentience';
const device = new ScentienceDevice('<SCENTIENCE_API_KEY>');
const embedder = new ScentienceEmbedder({
model: 'colip-large-base',
apiToken: '<HF_TOKEN>',
});
await device.connectBLE({ charUuid: '<CHAR_UUID>' });
const reading = await device.sampleBLE({ async: true });
// Embed the sensor reading and a set of candidate text labels
const [scentVec, ...labelVecs] = await Promise.all([
embedder.embedOlfaction(reading),
embedder.embedText('fresh coffee'),
embedder.embedText('pine forest'),
embedder.embedText('ocean breeze'),
]);
const ranked = ScentienceEmbedder.rankBySimilarity(scentVec, labelVecs);
const labels = ['fresh coffee', 'pine forest', 'ocean breeze'];
console.log('Best match:', labels[ranked[0].index], `(score: ${ranked[0].score.toFixed(3)})`);Example 2 — Load the small-GAT model and embed text + image
import { ScentienceEmbedder } from 'scentience';
const embedder = new ScentienceEmbedder({
model: 'colip-small-gat',
apiToken: '<HF_TOKEN>',
});
const textVec = await embedder.embedText('smoky oak barrel');
const imageVec = await embedder.embedImage('https://example.com/barrel.jpg');
const similarity = ScentienceEmbedder.cosineSimilarity(textVec, imageVec);
console.log('Text–image similarity:', similarity.toFixed(4));ScentienceEmbedder API
| Method / Property | Description |
|-------------------------------------------------|--------------------------------------------------------------------|
| new ScentienceEmbedder({ model, apiToken, modelId? }) | Create a client for the given model variant. |
| embedder.embedText(text) | Embed a text string. Returns Promise<number[]>. |
| embedder.embedImage(blobOrUrl) | Embed an image (Blob, ArrayBuffer, or URL). Returns Promise<number[]>. |
| embedder.embedOlfaction(sensorData) | Embed a ScienceData sensor reading. Returns Promise<number[]>. |
| embedder.modelId | The resolved HF repo ID being used. |
| ScentienceEmbedder.cosineSimilarity(a, b) | Cosine similarity between two vectors. Returns number in [-1, 1].|
| ScentienceEmbedder.rankBySimilarity(q, vecs) | Rank candidates by similarity to query. Returns sorted array. |
Note on model IDs: The COLIP_MODELS map assumes repos at kordelfrance/colip-{size}-{variant}. If the models are published under different IDs, pass modelId directly:
new ScentienceEmbedder({ apiToken: '<HF_TOKEN>', modelId: 'kordelfrance/my-custom-id' })Logging & Export
ScentienceLogger collects readings and exports them to JSON or CSV.
Stream to CSV (Node.js)
import { ScentienceDevice, ScentienceLogger } from 'scentience';
const device = new ScentienceDevice('<YOUR_API_KEY>');
const logger = new ScentienceLogger();
await device.connectBLE({ charUuid: '<YOUR_CHAR_UUID>' });
device.on('data', (d) => logger.log(d));
await device.streamBLE({ async: true });
// Later — write readings.csv to disk
await logger.exportCSV('readings.csv');Stream to CSV (React / browser download)
import { useEffect, useRef } from 'react';
import { ScentienceDevice, ScentienceLogger } from 'scentience';
export default function App() {
const loggerRef = useRef(new ScentienceLogger());
const deviceRef = useRef(null);
useEffect(() => {
const device = new ScentienceDevice('<YOUR_API_KEY>');
deviceRef.current = device;
device
.connectBLE({ charUuid: '<YOUR_CHAR_UUID>' })
.then(() => {
device.on('data', (d) => loggerRef.current.log(d));
return device.streamBLE({ async: true });
})
.catch(console.error);
return () => device.disconnect();
}, []);
const downloadCSV = () => loggerRef.current.exportCSV('readings.csv');
const downloadJSON = () => loggerRef.current.exportJSON('readings.json');
return (
<>
<button onClick={downloadCSV}>Download CSV</button>
<button onClick={downloadJSON}>Download JSON</button>
</>
);
}ScentienceLogger API
| Method / Property | Description |
|---------------------------------|------------------------------------------------------|
| logger.log(data) | Add a reading to the log. Returns this. |
| logger.data | Array of all logged records. |
| logger.size | Number of records in the log. |
| logger.clear() | Remove all records. Returns this. |
| logger.toJSON(indent?) | Serialize records to a JSON string. |
| logger.toCSV() | Serialize records to a CSV string. |
| logger.exportJSON(filename?) | Write/download JSON file. Default: scentience-data.json |
| logger.exportCSV(filename?) | Write/download CSV file. Default: scentience-data.csv |
CSV column handling: the header row is the union of all keys across every record. Chemical compound columns are included whenever at least one reading detected them — missing values in other rows appear as empty cells.
License
MIT
