Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 

Repository files navigation

BMES Tools

BMES Tools (bmes) is a cross-platform Python library and command-line interface for managing BMES100 environmental sensor devices over Bluetooth Low Energy. The CLI front-end is built with Click and Rich.

It brings scanning, live readings, the device clock, history offload, firmware update and the BLE shell under one command.

Tip

The CLI is self-documenting. Use --help on any command for what it does and examples of how to use it.

Features

  • Scan for devices, decoding the BTHome advertisement live into a table — temperature, humidity, battery and device time, all without connecting.
  • Read live temperature and humidity from the Environmental Sensing Service, alongside the Device Information and battery characteristics.
  • Get and set the device clock over the Current Time Service.
  • Offload buffered readings into a CSV, resuming from where the last offload stopped.
  • Update firmware over MCUmgr/SMP, with a progress bar, alongside the rest of MCUboot's image handling: listing the slots, confirming an image and resetting the device.
  • Shell into the device over the Nordic UART Service, with keystroke-level forwarding and automatic reconnection across a device reset.

Installation

Requires Python 3.11 or newer.

# Clone the repository
$ git clone <bmes-tools-repository-url>
$ cd bmes-tools

# Create a virtual environment. Use `python3` if `python` is not found.
$ python -m venv .venv

# Activate it in the current shell
$ source .venv/bin/activate

# Install. Use `pip install -e .` for an editable install while developing.
(.venv) $ pip install .

# Check it starts
(.venv) $ bmes --help

On macOS the first run prompts for Bluetooth permission for your terminal. Grant it under System Settings → Privacy & Security → Bluetooth, or nothing will ever be discovered.

Commands

bmes scan

Watch what is advertising. The table updates as advertisements arrive, and the readings come straight out of the BTHome payload, so nothing needs to connect.

$ bmes scan                      # every BMES100 in range, until Ctrl-C
$ bmes scan --all --details      # every BLE device, with raw advertising data

bmes info

Connect and watch the live readings, after printing what the device says about itself.

$ bmes info                      # live, until Ctrl-C
$ bmes info --once               # a single snapshot

bmes time get / bmes time set

$ bmes time get                            # read the clock and compare it with the host
$ bmes time set                            # synchronise it with this machine

The device keeps Unix time in UTC and loses it across a reset that stops its counter, so a freshly programmed device reads 1970 until something sets it. time get says so when it sees that.

bmes offload

Pull the buffered readings into a CSV.

$ bmes offload readings.csv           # append everything new
$ bmes offload --all fresh.csv        # take the whole buffer again
$ bmes offload --discard readings.csv # offload, then free the space on the device

Readings live in a RAM ring buffer. Reading does not consume them, so an interrupted offload can simply be run again; only --discard frees anything. Pointed at an existing CSV, the offload resumes from the last sequence number in the file.

Every row carries the device serial from the Device Information Service, so readings can be traced back to the device that took them. Since sequence numbers are per device, offloading into a CSV another device wrote is called out rather than silently resumed.

Sequence numbers restart from zero when the device resets, since the buffer does not survive one. That is detected and reported, but across a long enough gap the timestamp column is what to trust.

bmes dfu

Everything that talks to the device's MCUmgr/SMP server: firmware update and MCUboot's images.

$ bmes dfu update build/dfu_application.zip     # upload, mark, reset
$ bmes dfu list                                 # what the device is holding
$ bmes dfu confirm                              # keep the image it is running
$ bmes dfu reset                                # reboot it

bmes dfu update

Send a firmware image over MCUmgr's Simple Management Protocol.

$ bmes dfu update build/dfu_application.zip
$ bmes dfu update --no-confirm build/dfu_application.zip

Accepts a signed binary, an Intel HEX file, or the dfu_application.zip. The uploaded image is marked permanent by default. --no-confirm marks it for test instead: MCUboot swaps it in, and reverts to the previous one on the next reset unless the new firmware confirms itself, or bmes dfu confirm is run once it is up.

bmes shell

Open the device's shell over the BLE Nordic UART Service.

$ bmes shell # interactive

On a terminal, keystrokes are forwarded one at a time, so the device's own tab completion, history and line editing all work. That means Ctrl-] ends the session, leaving Ctrl-C free to interrupt whatever the device is running.

The session survives the device going away: resetting the board is the usual way to exercise firmware from here, so a dropped link is waited out and picked back up. --no-reconnect turns that off.

Selecting a device

Every command that talks to a device takes the same three options:

Option Meaning
-n, --name Advertised name prefix, BMES100 by default.
-a, --address Address prefix, for pinning one device when several are in range.
-t, --timeout How long to scan before giving up.

Both name and address match on a prefix, so the short address bmes scan prints can be pasted straight back into --address. With several devices matching and nothing to choose between them, the tool asks.

Library use

The CLI is a front-end over an importable library. Everything a command does can be done directly:

import asyncio

from bmes import connect, ess, find_device


async def main():
    discovered = await find_device()          # scans for BMES100*

    async with connect(discovered) as client:
        reading = await ess.read(client)
        print(f"{reading.temperature_c} C, {reading.humidity_pct} %")


asyncio.run(main())
Module What it covers
bmes.device Discovery, connection, Device Information, Battery Service.
bmes.bthome BTHome v2 advertisement decoding.
bmes.ess Environmental Sensing Service reads and notifications.
bmes.clock Current Time Service.
bmes.history Sensor History Service and its CSV format.
bmes.dfu MCUboot image loading and the SMP update sequence.
bmes.shell Nordic UART Service shell sessions.
bmes.uuids Every GATT UUID the device exposes.

Device services

What the tool talks to:

Service UUID
Environmental Sensing 0x181A
Current Time 0x1805
Device Information 0x180A
Battery 0x180F
Sensor History 8e7c1000-4a3b-4d5e-9f21-6b0d2c8a5e40
Nordic UART shell 6e400001-b5a3-f393-e0a9-e50e24dcca9e
SMP (MCUmgr) 8d53dc1d-1db7-4cd3-868b-8a527460aa84

Not every device exposes all of them. When one is missing, the command that needs it says so.

Troubleshooting

Run with -v for warnings, up to -vvvv to log everything including the Bluetooth stack and the SMP client, and -o log.txt to write it to a file as well:

$ bmes -vvvv -o log.txt offload readings.csv
Symptom Likely cause
Nothing found Bluetooth permission not granted, or the device is not advertising.
Found, but connect fails Another host is already connected; the firmware accepts one.
info shows - for readings The device has not taken its first reading yet.
Offload gets data notifications not enabled Something else is mid-transfer on that device.
dfu cannot open an SMP session The device does not expose the SMP service.
Shell connects but stays silent The device does not have a shell on the Nordic UART Service.

Development

(.venv) $ pip install -e ".[dev]"
(.venv) $ ruff check src/
(.venv) $ black src/

About

Python-based CLI for managing BMES100 devices

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages