Skip to main content

naneos-devices

GitHub Issues GitHub Pull Requests Ruff License

Projektlogo

Python package for the naneos particle solutions measurement devices (Partector 1, Partector 2, Partector 2 Pro). It connects to the devices over USB and Bluetooth Low Energy, delivers the measurements as pandas DataFrames, and can upload them to the naneos IoT service.

Installation

You can install the naneos-devices package using pip. Python 3.11 to 3.14 is supported. Open a terminal and run the following command:

pip install naneos-devices

Usage

Naneos Device Manager

NaneosDeviceManager is a tiny, fire-and-forget thread that auto-manages Naneos devices over Serial and BLE, periodically gathers data, and (optionally) uploads it. You can enable/disable transports at construction time and at runtime, adjust the gathering interval, and/or pipe data into your own code via a user-provided queue.Queue. Clean start/stop APIs make integration trivial.

Highlights

  • ✅ Easy on/off switches for Serial and BLE (before or during runtime)
  • 🔗 BLE is connection-only: data comes from linked devices, advertisements are used for discovery only
  • 🎯 Optional BLE allow-list (ble_serial_numbers) and link cap (ble_max_links, default 7)
  • ⏱️ Configurable gathering interval (clamped to 10–600 s)
  • 📤 Optional auto-upload (enable/disable anytime)
  • 📦 Queue hand-off: receive dict[int, pandas.DataFrame] snapshots and process them in your app
  • 🧵 Daemon thread with graceful shutdown

Quick Start (fire and forget upload from all devices in reach to naneos IoT service)

import time

from naneos import NaneosDeviceManager, enable_console_logging
from naneos.logger import LEVEL_INFO

enable_console_logging(LEVEL_INFO)  # the library is silent by default, see Logging

manager = NaneosDeviceManager(
    use_serial=True,
    use_ble=True,
    upload_active=True,
    gathering_interval_seconds=30,  # clamped to [10, 600]
    ble_serial_numbers=None,  # or e.g. [8617, 8764] to link only to your own devices
    ble_max_links=7,  # BlueZ handles about seven links reliably
)
manager.start()

try:
    while True:
        remaining = manager.get_seconds_until_next_upload()
        print(f"Next upload in: {remaining:.0f}s")
        time.sleep(remaining + 1)

        print("Serial:", manager.get_connected_serial_devices())
        print("BLE   :", manager.get_connected_ble_devices())
        print()
except KeyboardInterrupt:
    pass

manager.stop()
manager.join()
print("Stopped.")

Runtime Controls (toggle anytime during execution)

# Turn Serial on/off during runtime
manager.use_serial_connections(True)  # or False
print("Serial enabled:", manager.get_serial_connection_status())

# Turn BLE on/off during runtime
manager.use_ble_connections(False)  # or True
print("BLE enabled:", manager.get_ble_connection_status())

# Enable/disable uploads on the fly
manager.set_upload_status(False)  # keep gathering, but don't upload
print("Upload active:", manager.get_upload_status())

# Update the gathering interval at runtime (10–600 s)
manager.set_gathering_interval_seconds(45)
print("Interval (s):", manager.get_gathering_interval_seconds())

Queue-Based Data Handoff (use your own processing)

Register a queue to receive each gathered snapshot (no uploads required):

import queue
import time

from naneos import NaneosDeviceManager

out_q: queue.Queue = queue.Queue()

manager = NaneosDeviceManager(
    upload_active=False,  # we'll handle data ourselves
    gathering_interval_seconds=15,
)
manager.register_output_queue(out_q)
manager.start()

try:
    while True:
        # Wait until a snapshot is ready, then pull all pending ones
        time.sleep(manager.get_seconds_until_next_upload() + 1)

        while not out_q.empty():
            snapshot = out_q.get()
            # snapshot: dict[int, pandas.DataFrame] keyed by device serial
            print(f"Received snapshot for {len(snapshot)} device(s)")
            for serial, df in snapshot.items():
                print(f"  - {serial}: {len(df)} rows")
                # >>> Your processing here (store, analyze, forward, etc.)
except KeyboardInterrupt:
    pass

manager.stop()
manager.join()

Make sure to modify the code according to your specific requirements. Refer to the documentation and comments within the code for detailed explanations and usage instructions.

Logging

The package follows the usual library convention: it logs to loggers below naneos and prints nothing unless the application configures logging. To see what the managers are doing:

from naneos.logger import LEVEL_INFO, enable_console_logging, enable_file_logging

enable_console_logging(LEVEL_INFO)  # coloured output on stderr
enable_file_logging("logs/", LEVEL_INFO)  # appends to logs/naneos-devices.log

Applications that configure logging themselves need neither; the naneos logger propagates to the root logger like any other library.

Documentation

The documentation for the naneos-devices package can be found in the package's documentation page.

Protobuf

The upload format is defined in src/naneos/protobuf/protoV1.proto (shared with the backend, never renumber fields). Regenerate the Python module and the stub in that directory with:

protoc -I=. --python_out=. --pyi_out=. ./protoV1.proto

Testing

I recommend working with uv. The default test run only contains tests that need no hardware:

uv run pytest

Tests that need a Partector connected via USB or BLE are marked hardware, tests that need internet access and an IoT token are marked network:

uv run pytest -m hardware
IOT_GUEST_TOKEN=... uv run pytest -m network

Testing every supported python version:

nox -s tests

Lint, format and type checks (also run in CI):

uv run ruff check .
uv run ruff format --check .
uv run mypy

Building executables

Sometimes you want to build an executable for a customer with your custom script. The build must happen on the same OS as the target OS. For example if you want to build an executable for windows you need to build it on Windows.

pyinstaller examples/demo.py --console --noconfirm --clean --onefile

Raspberry Pi as an always-on uploader

Flash Raspberry Pi OS (Bookworm or newer) with the official Raspberry Pi Imager, headless or with a display, and run the installer on the Pi:

curl -fsSL https://raw.githubusercontent.com/naneos-org/python-naneos-devices/master/installers/install.sh | sudo bash

It creates a virtual environment in ~/naneos-uploader, installs the package from the master branch, and sets up the naneos_uploader systemd service that starts on every boot. The service runs the naneos-uploader command, which gathers from every Partector on USB and BLE and uploads every 30 s. Re-running the installer upgrades the installation.

To install a specific branch or tag, for example a release or the hardware test branch:

curl -fsSL https://raw.githubusercontent.com/naneos-org/python-naneos-devices/master/installers/install.sh | sudo bash -s -- --ref v1.2.0
curl -fsSL https://raw.githubusercontent.com/naneos-org/python-naneos-devices/master/installers/install.sh | sudo bash -s -- --ref release_test

Useful afterwards:

journalctl -u naneos_uploader.service -f          # live log
sudo systemctl status naneos_uploader.service
sudo systemctl stop naneos_uploader.service
~/naneos-uploader/.venv/bin/naneos-uploader --no-upload --interval 10   # run by hand, no upload
~/naneos-uploader/.venv/bin/naneos-uploader --ble-allow 8617,8764 --ble-max-links 2

BLE on the Pi is connection-only and scans passively: the installer starts bluetoothd with --experimental, which BlueZ needs for passive scanning, so the shared WiFi/BLE antenna is not loaded with scan requests. The log line BLE scanning (passive). confirms it; (active) plus a warning means BlueZ refused and the uploader fell back to active scanning.

Examples

The examples/ folder contains runnable scripts: demo.py (device manager with queue hand-off), serial_device.py (connect to one USB device), send_commands.py and download_iotweb.py. The Raspberry Pi service runs the naneos-uploader command, implemented in src/naneos/uploader.py.

Ideas for future development

  • P2 bidirectional BLE implementation that allows to send commands to the P2
  • Automatically activate Bluetooth or ask when BLE is used

Contributing

Hardware testing before a merge

Changes that touch the serial or BLE code are tested on real devices before they reach master:

  1. Point the release_test branch at the feature branch: git branch -f release_test <feature> && git push -f origin release_test.
  2. Raspberry Pi: curl -fsSL .../installers/install.sh | sudo bash -s -- --ref release_test (see above), then watch journalctl -u naneos_uploader.service -f.
  3. Windows / macOS: in any virtual environment pip install "https://github.com/naneos-org/python-naneos-devices/archive/release_test.tar.gz" and run pytest -m hardware from a checkout with the devices attached. To switch an existing environment to another branch with the same version number, add --force-reinstall --no-deps; pip otherwise keeps what is installed.
  4. When it works, open the pull request from the feature branch to master, merge, tag the release.

Contributions are welcome! If you encounter any issues or have suggestions for improvements, please submit an issue on the issue tracker.

Please make sure to adhere to the coding style and conventions used in the repository and provide appropriate tests and documentation for your changes.

License

This repository is licensed under the MIT License.

Contact

For any questions, suggestions, or collaborations, please feel free to contact the project maintainer:

Download files

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

Source Distribution

naneos_devices-1.2.0.tar.gz (181.1 kB view details)

Uploaded Source

Built Distribution

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

naneos_devices-1.2.0-py3-none-any.whl (69.0 kB view details)

Uploaded Python 3

File details

Details for the file naneos_devices-1.2.0.tar.gz.

File metadata

  • Download URL: naneos_devices-1.2.0.tar.gz
  • Upload date:
  • Size: 181.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.12 {"installer":{"name":"uv","version":"0.12.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for naneos_devices-1.2.0.tar.gz
Algorithm Hash digest
SHA256 73a978f71a6ee7248d001a2a6897698917d8bd6d1df7f8f03cd562cec7863cca
MD5 3d7b7cd3ece99d224bbcf5779920b965
BLAKE2b-256 90777c5f68ff309492a4d22b86fcf392892a827fc974a4a6b3efa2982ceaf801

See more details on using hashes here.

File details

Details for the file naneos_devices-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: naneos_devices-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 69.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.12 {"installer":{"name":"uv","version":"0.12.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for naneos_devices-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c31bbc8eb31e19af6bb59e123cb67c38dba3dc78cbabcd68df4afb45cb0f42f4
MD5 18ff3a862f320faded92f276693c5eea
BLAKE2b-256 d9073fd5a520892d3cdc2f537b3f9859f9220013136e0651f82e4afd29334464

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 files

1.1.22

2 files

1.1.21

2 files

1.1.20

2 files

1.1.19

2 files

1.1.18

2 files

1.1.17

2 files

1.1.16

2 files

1.1.15

2 files

1.1.14

2 files

1.1.13

2 files

1.1.12

2 files

1.1.11

2 files

1.1.10

2 files

1.1.9

2 files

1.1.8

2 files

1.1.7

2 files

1.1.6

2 files

1.1.5

2 files

1.1.4

2 files

1.1.3

2 files

1.1.2

2 files

1.1.1

2 files

1.1.0

2 files

1.0.62

2 files

1.0.61

2 files

1.0.60

2 files

1.0.59

2 files

1.0.57

2 files

1.0.56

2 files

1.0.55

2 files

1.0.54

2 files

1.0.53

2 files

1.0.51

2 files

1.0.50

2 files

1.0.49

2 files

1.0.48

2 files

1.0.47

2 files

1.0.46

2 files

1.0.45

2 files

1.0.44

2 files

1.0.43

2 files

1.0.42

2 files

1.0.41

2 files

1.0.40

2 files

1.0.39

2 files

1.0.38

2 files

1.0.37

2 files

1.0.36

2 files

1.0.35

2 files

1.0.34

2 files

1.0.33

2 files

1.0.32

2 files

1.0.31

2 files

1.0.30

2 files

1.0.29

2 files

1.0.28

2 files

1.0.25

2 files

1.0.24

2 files

1.0.23

2 files

1.0.22

2 files

1.0.21

2 files

1.0.20

2 files

1.0.19

2 files

1.0.18

2 files

1.0.17

2 files

1.0.16

2 files

1.0.15

2 files

1.0.14

2 files

1.0.13

2 files

1.0.12

2 files

1.0.11

2 files

1.0.10

2 files

1.0.9

2 files

1.0.8

2 files

1.0.4

2 files

1.0.3

2 files

0.7.9

2 files

0.7.8

2 files

0.7.7

2 files

0.7.6

2 files

0.7.5

2 files

0.7.4

2 files

0.7.3

2 files

0.7.2

2 files

0.7.1

2 files

0.7.0

2 files

0.6.3

2 files

0.6.2

2 files

0.6.1

2 files

0.5.1

2 files

0.5

2 files

0.4

1 file

0.3

2 files

0.2

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page