Open desktop app, CLI, and Python SDK for working with BLE-enabled Fluke meters from a shared architecture.
This is an unofficial community project. It is not affiliated with, endorsed by, or sponsored by Fluke Corporation. Fluke is a trademark of its respective owner.
The repo contains a more structured codebase built around:
- canonical domain models
- profile-based protocol decoding
- shared application services
- SQLite session logging
- desktop and CLI surfaces on top of the same services
- guided workflow execution
- an extension boundary for future contributed profiles and workflow packs
The current in-tree device focus is still the Fluke 376 FC.
The project is released under the MIT License.
For a structured documentation set instead of this high-level overview, start with docs/README.md.
What exists now:
- BLE scan, connect, and stream path through shared services
- normalized
Readingmodel and 376 FC profile decoder - SQLite session, marker, workflow-run, and export support
- desktop app with discovery, live view, charting, replay, markers, session export, and workflow runner
- live view supports rolling and derived chart modes without rewriting stored data
- session replay filters mixed-mode sessions into cleaner measurement views without rewriting raw data
- session replay supports elapsed-time, UTC, and per-segment chart axes
- desktop session exports include raw CSV/JSON plus analysis CSV and segment-summary JSON
- workflow steps support manual, stable-capture, countdown, and observe-and-confirm interaction modes
- workflow run history can be reviewed and exported as reports from the desktop UI
- CLI for scan, stream, watch, alert, log, sessions, workflows, plugins, fixture capture, and debug bundle export
- Python SDK on the same core stack
- plugin loader for contributed profiles and workflow JSON packs
- hardware-free test coverage using fake BLE adapters and replay frames
What still needs repeated real-hardware validation:
- longer-duration BLE sessions on the rebuilt stack
- reconnect stability over longer sessions
- packaged installer behavior
- additional model support beyond the 376 FC
This repo intentionally avoids "desktop-only" or "CLI-only" logic.
The development order has been:
- normalize protocol payloads into canonical readings
- persist sessions and make exports reliable
- expose the same behavior through CLI, desktop, and SDK
- add charts, workflows, and contributor tooling only after the core path was solid
That methodology matters. If you add a new capability, add it to the shared stack first unless there is a very strong reason not to.
apps/
cli/ Command-line entry points
desktop/ PySide6 desktop application
packages/
fluke_app/ Shared orchestration services
fluke_ble/ BLE transport abstraction + Bleak adapter
fluke_core/ Domain models, enums, statistics, workflow models
fluke_plugins/ Plugin discovery and extension loading
fluke_protocol/ Device profile registry and protocol decoding
fluke_sdk/ Public Python SDK
fluke_store/ SQLite schema and repositories
fluke_testing/ Fake adapters, replay tools, fixture capture helpers
plugins/
examples/ Disabled template plugin for contributors
workflows/ Built-in workflow definitions
scripts/
fixture_capture.py Raw fixture capture helper
export_debug_bundle.py
docs/
README.md Documentation hub
architecture.md
getting-started.md
desktop-guide.md
cli-guide.md
data-and-exports.md
sdk-guide.md
workflows-page.md
support-matrix.md
developer/
- Python 3.11+
- BLE-capable host
- virtual environment recommended
bleakfor BLE runtimePySide6for the desktop UI
Minimal dependency set:
requirements.txt: CLI + SDK + BLE runtime
Full contributor / desktop dependency set:
requirements-full.txt: desktop, plotting, dashboard/data extras, plus the base BLE runtime- package extras are also available through
pyproject.toml:.[desktop]: desktop UI and charting.[full]: desktop plus the current optional extras used in the repo.[dev]: same dependency set as.[full]for contributors and CI
macOS / Linux:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[full]"Windows PowerShell:
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e ".[full]"python -m pip install -e .If you prefer requirements files over package extras, the legacy commands still work:
- minimal:
python -m pip install -r requirements.txt && python -m pip install -e . - full:
python -m pip install -r requirements-full.txt && python -m pip install -e .
The editable install provides these entry points from pyproject.toml:
flukefluke-clifluke-desktop
If you skip python -m pip install -e ., use the module entry points directly:
python -m apps.cli.mainpython -m apps.desktop.main
Install expectations:
- minimal install supports the CLI and SDK paths
- desktop usage requires the desktop/full extras or
requirements-full.txt - the full automated test suite also expects the desktop/full dependency set
Installed entry point:
fluke --helpModule fallback:
python -m apps.cli.main --helpGlobal flags:
--version-v/--verbose--jsonfor commands that support structured output
Installed entry point:
fluke-desktopModule fallback:
python -m apps.desktop.mainOn macOS, the desktop app uses PySide6.QtAsyncio for Qt/BLE event-loop integration. Other platforms continue to use the background async runner.
Example:
import asyncio
from fluke_sdk import FlukeClient
async def main() -> None:
client = FlukeClient()
devices = await client.scan(timeout_s=5.0)
if not devices:
print("No devices found.")
return
device = await client.connect(devices[0].device_id)
print(device)
async for reading in client.stream_readings():
print(reading.display_text)
break
await client.close()
asyncio.run(main())fluke scan --timeout 10 --name FlukePlain text stream:
fluke stream --device "<DEVICE_ID>" --profile fluke_376fcRetro terminal dashboard:
fluke stream --device "<DEVICE_ID>" --profile fluke_376fc --dashboardAdditional live terminal views:
fluke watch --device "<DEVICE_ID>" --profile fluke_376fc
fluke alert --device "<DEVICE_ID>" --profile fluke_376fc --high 120fluke log \
--device "<DEVICE_ID>" \
--profile fluke_376fc \
--duration 30 \
--title "Bench run" \
--notes "Initial validation" \
--csv-output exports/session.csv \
--json-output exports/session.jsonfluke sessions list
fluke sessions export --session "<SESSION_ID>" --format json --output exports/session.jsonfluke workflow list
fluke workflow run --workflow battery_pack_check_v1 --device "<DEVICE_ID>" --profile fluke_376fcThe --json flag is global, so place it before the subcommand:
fluke --json devices supported
fluke --json scan --timeout 5
fluke --json sessions list
fluke --json workflow listfluke plugins list
fluke fixtures capture --device "<DEVICE_ID>" --profile fluke_376fc --duration 10 --output fixtures/capture.json
fluke debug bundle --output artifacts/debug-bundle.zipWhen --database is omitted, commands use a platform-appropriate default path:
- macOS:
~/Library/Application Support/fluke-community/fluke.db - Linux:
~/.local/share/fluke-community/fluke.db - Windows:
%LOCALAPPDATA%/fluke-community/fluke.db
The default path is resolved lazily, so commands such as fluke --version do not create directories or touch the database.
The desktop app currently includes:
- Home
- Device Discovery
- Live Reading
- Session
- Workflows
- Settings
The Live Reading tab supports:
- current reading display
- unit and measurement type
- live chart
- chart mode selector
- min / max / avg / sample summary
- session start / stop
- manual session markers
- live chart PNG export
The live chart now keeps a larger timestamped buffer and derives the visible plot from the selected chart mode:
Rolling 30sRolling 60sRolling 5mSince mode startSince session start
When the meter changes measurement context, the live view starts a new derived segment and shows a transient banner. Changing chart mode also starts a new Since mode start boundary instead of discarding buffered readings.
The Session tab supports:
- recent sessions
- replay chart
- measurement-view filtering for mixed-mode sessions
- axis mode selection for
Elapsed Time,UTC Timestamp, andBy Segment - per-segment selection when viewing a derived segment axis
- marker review
- session notes
Export Raw CSVExport Raw JSONExport Analysis CSVExport Segment Summary JSON- chart PNG export
Replay filtering is derived in the UI layer rather than rewriting stored data:
- raw readings stay stored exactly as captured
- compatible unit ranges are grouped into cleaner replay views
- mode is only retained where it is meaningful for replay
- tiny transitional / unknown groups are suppressed from the filter UI
All Measurementsremains available as a mixed-session overview
The Workflows tab supports:
- selecting a built-in workflow
- starting a workflow run against the active session stack
- completing, continuing, retaking, skipping, or canceling steps
- per-step interaction modes:
manual_checkstable_capturecountdown_captureobserve_and_confirm
- capture steps that validate the current live reading type / unit
- staged capture confirmation for pause-after-capture steps
- recent workflow run history stored in SQLite
- selecting recent runs to review historical step results
- in-app workflow reports for the selected run
- Markdown export of workflow run reports
Built-in workflow pack:
- Battery Pack Check
- Solar Panel Test
- Charger Output Check
- Continuity Checklist
Workflow definitions are data-driven JSON files under workflows/.
Example shape:
{
"workflow_id": "battery_pack_check_v1",
"title": "Battery Pack Check",
"steps": [
{
"id": "measure_total_voltage",
"instruction": "Measure total pack voltage.",
"interaction_mode": "stable_capture",
"advance_on_capture": false,
"capture": true,
"capture_settings": {
"stable_for_s": 0.75,
"min_samples": 5,
"relative_tolerance": 0.01
},
"expected_measurement_type": "voltage_dc",
"expected_unit": "V"
}
]
}Legacy workflow JSON that only uses capture: true/false still loads. New workflow definitions should prefer explicit interaction_mode, advance_on_capture, and capture_settings.
The project now includes a lightweight plugin loader in packages/fluke_plugins/.
Plugins can contribute:
- new
DeviceProfileimplementations - additional workflow JSON directories
- fixture directories
Discovery root:
plugins/
See:
- plugins/README.md
- docs/developer/new-device-profile.md
plugins/examples/example_profile_plugin/
Important note: the built-in fluke_376fc path still ships from the core codebase. Plugins are currently additive and intended for experimentation and contribution.
- CLI:
fluke fixtures capture - script:
scripts/fixture_capture.py
- CLI:
fluke debug bundle - script:
scripts/export_debug_bundle.py
These are meant to lower the barrier for remote debugging when hardware is unavailable.
- timestamp
- value
- unit
- measurement type
- status
- display text
- source device
- title
- notes
- tags
- device id
- start / end time
- profile id
- session id
- timestamp
- label
- note
- workflow id
- session id
- start / end time
- run result
Quick regression suite used during active development:
python -m unittest tests.unit.test_cli_main tests.unit.test_desktop_presenter tests.unit.test_desktop_views tests.unit.test_sdk_clientFull suite:
python -m pip install -e ".[dev]"
python -m unittest discover -s tests -p "test_*.py"
python -m compileall apps packages testsMinimal CLI/SDK-only verification:
python -m pip install -e .
python -m unittest tests.unit.test_cli_main tests.unit.test_sdk_client tests.unit.test_fluke_376fc_profile tests.unit.test_fake_adapter_and_stream tests.unit.test_logging_flow tests.unit.test_workflow_catalog tests.unit.test_workflow_runner tests.unit.test_capture_export tests.unit.test_debug_bundle tests.unit.test_fixture_capture_tool tests.unit.test_plugin_loader tests.unit.test_statistics tests.unit.test_store_pathsThe test strategy is intentionally layered:
- parser and statistics unit tests
- fake-adapter integration tests
- desktop presenter tests without hardware
- workflow runner tests
- plugin loader tests
- fixture capture and debug bundle tests
- the full suite requires the desktop/full dependency set because desktop tests import
PySide6
Check the basics first:
- the meter is powered on and advertising over BLE
- Bluetooth is enabled on the host
- the device is in range
- another app is not already holding the BLE connection
- you are using the expected profile for the device you are testing
Useful first commands:
fluke scan --timeout 10
fluke --json devices supportedCommon causes:
- the meter is too far away from the computer
- another app or vendor utility is still connected to the meter
- the host Bluetooth adapter is unstable or power-managed aggressively
- the measurement mode changed and you are expecting the previous live context
- the environment has not had repeated long-duration validation yet
If the desktop app disconnects unexpectedly, it will attempt automatic reconnect. If reconnect fails, logging stops and any active workflow is canceled.
Check:
PySide6is installed- the desktop dependency set was installed from
requirements-full.txt - you launched the app from the same virtual environment where those packages were installed
Recommended install path:
python -m pip install -r requirements-full.txt
python -m pip install -e .Check:
- the current export directory shown in the desktop
Settingstab - the explicit output path you passed on the CLI
- that the target directory exists or can be created
- BLE behavior depends heavily on the Windows Bluetooth stack and adapter drivers
- if scan/connect behavior is inconsistent, update the adapter driver and retry after fully disconnecting other Bluetooth apps
- pairing is not the same as maintaining an active BLE telemetry connection; avoid assuming the OS pairing state means the stream path is free
- allow Bluetooth access for the terminal, IDE, or app host you are launching from
- if the desktop app starts but cannot discover devices, check Privacy & Security permissions and retry
- the desktop app uses
PySide6.QtAsyncioon macOS, so make sure the full desktop dependency set is installed in the active environment
- Linux support is still marked
Unknownin the support matrix because adapter/runtime validation is limited - make sure the host Bluetooth stack is running and the adapter is not blocked by
rfkill - desktop behavior may also depend on local
dbusand Bluetooth service configuration
For more first-run guidance, see docs/getting-started.md and docs/support-matrix.md.
- docs/README.md
- docs/getting-started.md
- docs/desktop-guide.md
- docs/cli-guide.md
- docs/data-and-exports.md
- docs/sdk-guide.md
- docs/workflows-page.md
- docs/developer/README.md
- docs/developer/development-workflow.md
- docs/developer/testing-guide.md
- docs/developer/desktop-ui-architecture.md
- docs/architecture.md
- docs/support-matrix.md
- docs/developer/new-device-profile.md
- docs/developer/fixtures-and-debug.md
- CONTRIBUTING.md
Already implemented:
- shared BLE + protocol + CLI slice
- session logging and exports
- desktop live / session UX
- charts, markers, summaries, and chart export
- guided workflows
- workflow run review and reporting
- plugin boundary, fixture capture, and debug bundle export
Next likely work:
- more real hardware validation
- additional profile support
- packaging and installer work
- contributor docs expansion


