Skip to main content

JRing client

This is an offline-first Python 3 Linux client for explicitly selected JRing BLE devices. It performs safe standard Bluetooth GATT reads and one bounded standard heart-rate notification today, and provides a tested simulator. Vendor writes and hardware history remain disabled until packet captures from the owner's selected ring establish the protocol exactly.

Install and run

For the quickest try (no checkout and no system install), use uvx. The first screen is safe and simulator-first:

uvx --from jring-client jring tui

The menu's s and c choices only run offline simulator commands. For a one-shot check, use uvx --from jring-client jring status --simulate; uvx creates and discards an isolated environment automatically. The base package includes its runtime Bluetooth and Linux-input dependencies; no extras are needed.

From a clone, create an isolated environment. python3 is used for bootstrapping on Linux distributions that do not provide a python command:

python3 -m venv .venv
. .venv/bin/activate
python -m pip install -e .
jring
jring doctor
jring completion bash > ~/.local/share/bash-completion/completions/jring
jring non-health-capabilities
jring protocol-coverage
jring status --simulate
jring heart-rate --simulate
jring capabilities --simulate
jring history --simulate --output history.jsonl
jring input --simulate --map step=click:left

After installation, jring tui opens a keyboard-driven, refreshable terminal menu (s status, c capabilities, d doctor, i inputs, p pairing, r refresh, q quit) and jring-tui is an equivalent console-script shortcut. It never scans, connects, or emits desktop input from the menu itself. The p flow asks for the address-file path and literal confirmations for pairing and optional trust; hardware actions remain explicit commands with separate consent flags.

--simulate uses the named basic profile everywhere: it has standard status data and does not advertise HID. To inspect a synthetic, metadata-only HID inventory, name the hid profile explicitly in either supported option position:

jring capabilities --simulate --simulate-profile hid
jring --simulate --simulate-profile hid status

Human and JSON results name the selected profile. The HID profile never reads or emits HID reports and does not claim operating-system attachment.

For hardware, the base package already includes Bleak. Discovery is an active radio scan: --active-scan explicitly acknowledges that BLE scan requests are transmitted. It redacts addresses and never connects.

jring discover --active-scan

If BlueZ requires an OS bond before connecting, pairing is a separate, explicit operation. Keep the address in the same private mode-0600 file and confirm the one-time action:

jring pair --address-file ~/.config/jring/address --allow-pairing

This invokes only bluetoothctl pair; it never trusts the device, changes vendor binding, writes a vendor characteristic, or retries an uncertain timeout. A successful OS bond is not proof of JRing compatibility. Review the result in your desktop Bluetooth settings or with bluetoothctl info before continuing.

If your desktop requires a trusted device for reconnects, trust is a separate explicit choice in the same command:

jring pair --address-file ~/.config/jring/address --allow-pairing --allow-trust

That runs bluetoothctl trust only after pairing succeeds. Pairing never implies trust, and neither operation changes application/vendor binding.

Use BlueZ locally to identify your ring, then put its exact address on one line in a private file. The client rejects files accessible by another user:

mkdir -p ~/.config/jring
chmod 700 ~/.config/jring
# Add the exact address with your editor, then:
chmod 600 ~/.config/jring/address
jring status --address-file ~/.config/jring/address
jring heart-rate --address-file ~/.config/jring/address --allow-notifications
jring time-sync --address-file ~/.config/jring/address --yes

The legacy --address option remains available, but it exposes the identifier in shell history and process listings. Neither discovery result aliases nor addresses are persisted by the client, and discovery never auto-selects a device.

For an interactive status or metadata-only capability check without putting an address in argv or a file, use:

jring status --select --active-scan
jring capabilities --select --active-scan
jring heart-rate --select --active-scan --allow-notifications

The scan and connection are separate consent steps. The command shows temporary aliases and a possible-JRing label explicitly identified as a client-side name heuristic, then asks a default-no confirmation before it can connect. It never auto-selects a sole result. Discovery JSON includes the same likely_jring_basis=client_name_heuristic boundary. This guided path is human-only and does not support --json; scripts should keep using the mode-0600 address file. The capabilities path reads service/characteristic metadata only; it never reads values, subscribes, or writes. It evaluates fixed main/raw vendor route structure and current-snapshot target ownership without exposing target identities. Structural readiness grants no live eligibility, owner authorization, or hardware support.

For a bounded owner investigation of one notify-capable metadata row, keep the address and capture outside the repository and choose the exact service, characteristic, and instance from the explicitly opted-in, metadata-only selector manifest (jring capabilities ... --include-observation-targets --json):

jring capabilities --address-file ~/.config/jring/address \
  --include-observation-targets --json \
  | jq -r '.observation_targets[] | [.service_uuid, .characteristic_uuid, .instance_id] | @tsv'
jring observe --address-file ~/.config/jring/address \
  --private-output ~/.config/jring/observation.json \
  --service-uuid SERVICE --characteristic-uuid CHARACTERISTIC --instance-id INSTANCE \
  --max-records 8 --allow-connect --allow-notifications --allow-observation
jring review-observation --private-input ~/.config/jring/observation.json

observe makes one bounded connection and notification subscription, then writes only a new mode-0600 private record. It never reads a characteristic, writes a vendor value, decodes a frame, enables an input action, uploads, opens a browser, or retries. review-observation is offline-only and displays only capture state and count; it never renders a captured frame, address, target identity, or private path. An observation is not proof of protocol meaning, compatibility, or runtime authorization.

The selector manifest contains only service UUID, characteristic UUID, and a connection-scoped instance ID for notify endpoints with exactly one advertised CCCD. It is omitted by default and contains no characteristic values, addresses, paths, or connection generations.

To prepare a reviewable public handoff after a metadata-only probe, add --issue-draft-url to capabilities. The client creates a GitHub issue-draft URL locally; it does not open a browser or make a network request. Its prefilled text is limited to coarse inventory states and a route count. Review it before opening it—raw packets, addresses, values, health data, private paths, and firmware details are never included.

jring heart-rate collects exactly one standard Bluetooth Heart Rate Measurement and then disables its notification before displaying a result. Hardware use requires --allow-notifications before a transport is constructed because BlueZ may perform standard CCCD control traffic while enabling or disabling the notification. The client sends no vendor characteristic command, saves no measurement, omits raw bytes and the device address, and does not turn an advertisement or one valid sample into a broad model/firmware compatibility claim. Output is fitness information only, not medical advice. Use jring heart-rate --simulate for the synthetic 72 bpm sample; simulation rejects the hardware-only consent flag and performs no Bluetooth operation.

Human-readable output is the default. Add --json to status, heart-rate, capabilities, or discover for automation. Both task-first options (jring status --simulate) and the original global-first form (jring --simulate status) are supported. Simulated human output clearly states that no ring was contacted; structured results and exports include source and schema provenance.

To install the packaged Bash completion for your user without running a setup script:

mkdir -p ~/.local/share/bash-completion/completions
jring completion bash > ~/.local/share/bash-completion/completions/jring

completion is offline and only supports Bash today; Fish and other shells remain out of scope.

Comparative research, alternatives, and data sources

Start with the external JRing / 56ff prior-art ledger. It links the public PulseLoop iOS and PulseLoop Android implementations, an independent JRing capture write-up, and SR08 retail/manual material. These sources are useful comparative evidence for forming reconciliation candidates. An implementation may inform an explicitly unverified offline decoder or owner-consented probe candidate, but is not verification or runtime authorization: a model name, frame resemblance, or third-party claim never marks a JRing capability supported, hardware-verified, or safe to replay.

For adjacent tools or a different companion-app approach, see BlueZ and Gadgetbridge. They are alternatives to evaluate on their own terms; this project does not claim they support a selected JRing or share its protocol. The authoritative sources for JRing behavior stay separate: the clean-room recovered scope, the public prior-art ledger, and owner-authorized private evidence. Each external claim must be independently reconciled before it can affect a specification, and owner-hardware evidence is still required before a live capability can be considered.

JSON successes include schema_version, operation, source, and ok. JSON failures write one redacted envelope to stdout and nothing to stderr. Stable failure exits are 2 for usage, 3 for unavailable prerequisites/device, 4 for timeout, 5 for protocol incompatibility, 6 for permission, 70 for an unexpected internal failure, and 130 for interruption. Scripts should branch on the error code, not its explanatory message.

Contributions are welcome, but raw Bluetooth captures, app archives, device addresses, account details, timestamps, health values, and vendor payload dumps do not belong in GitHub issues or commits. Read CONTRIBUTING.md for the local fail-closed evidence workflow and SECURITY.md for private reporting. Schema-2 public candidates currently cover only one sealed vendor-main device-info canary shape; validation does not enable it, authorize hardware, or establish support. The local validator also accepts a separate owner-only, self-declared schema-2 historical observation for that one operation. It withholds identifiers and device context and must remain outside Git at mode 0600 or read-only 0400; validation performs no Bluetooth action, and derivation/publication are refused. This is a privacy and consistency boundary, not Bluetooth parity or a reusable consent token.

The separate owner-hardware transport canary is specified in OWNER_HARDWARE_EVIDENCE.md. It supports fresh guided selection, creates one new private mode-0600 record, and requires independent connection, notification, and write authorization. Its response value is discarded: even a matched terminal proves transport correlation only, not device-information contents, firmware support, vendor authorization, or live runtime eligibility. Offline review and sanitized public-row derivation are separate commands and perform no Bluetooth I/O. An interrupted canary is non-retryable: its write may already have escaped, so inspect the requested private record before considering another manual attempt. Human review previews every field eligible for the public row before a separate derivation step. The versioned public artifact labels its model and firmware scope owner-declared and carries explicit false runtime and repeat authority; neither promote nor candidate_success enables an operation.

Maintainers can generate a hardware-independent compatibility row and deterministically merge reviewed reports without publishing them:

python3 scripts/compatibility_matrix.py generate-synthetic
python3 scripts/compatibility_matrix.py merge report-a.json report-b.json

Synthetic success verifies only named local checks; all hardware dimensions remain untested. See docs/COMPATIBILITY.md for the schema and the owner-evidence gate.

For reviewed CI artifacts, checksum verification, isolated pipx/uv tool install, upgrade, and uninstall instructions, see docs/INSTALL.md. The repository does not currently publish to a package index or create GitHub releases. The tokenless, owner-gated release design and remaining PyPI trust step are documented in docs/PUBLISHING.md.

Inspect the complete accounting of the recovered interface declarations—without claiming complete decompilation, protocol parity, or hardware support—and without Bluetooth, a ring, or optional dependencies:

jring protocol-coverage
jring protocol-coverage --json

The report answers the overall question first: complete APK-to-Python Bluetooth capability parity is not established. It then separates complete accounting within the known AIDL declaration scope from incomplete source semantics, live vendor availability, and hardware verification. JSON command success likewise remains distinct from the top-level bluetooth_capability_parity verdict.

The report accounts for 112 requests and 105 callbacks with zero unclassified ledger entries: 85 offline request codecs, 26 non-runnable static behavior-evidence rows, and one non-runnable control model. Evidence rows are not behavioral parity or callable features. All 86 callback declarations classified as opcode-originated have offline decoder coverage; this is not a count of distinct wire families. Every one of those 85 request and 86 callback codec rows links to an importable Python encoder, parser, typed factory, or stateful pipeline. The four shared 23 sensor wrappers have exact start selectors and a common stop selector. The five raw callback rows use callback-specific fail-closed parsers over the shared raw frame decoder, so no codec-family binding remains unresolved. For the 37 builder families reviewed instruction-by-instruction, a separate sanitized ledger records byte-exact parity on the Python encoders' accepted domains: all are fixed 20-byte, checksum-free builders; 31 use the source main queue and six the raw queue. Only sensor-session start/stop are front-inserted. Source gates, logging, queue draining, alarm partial-enqueue behavior, and dial-state queue clearing are explicitly not reproduced. The artifact supplement now keeps the older broad Android Bluetooth source-reference counts explicitly non-exhaustive and publishes a separate direct-instruction inventory for the complete owned application/SDK scope. That inventory classifies 236 referencing methods across 63 classes with zero unclassified, including GATT lifecycle, reads and writes, descriptor/notification setup, MTU, connection priority, RSSI, discovery, legacy/modern scanning, bonding, classic profiles/RFCOMM, and adapter power. This is reference classification—not semantic, dependency/transitive, runtime, or hardware completion. The report also gives every deterministic request codec one request/callback correlation row. Proven single responses, shared streams, stateful families, raw event candidates, callback-silent failures, and explicit unknowns remain distinct. No row remains in the generic topology bucket, but 58 of 85 retain at least one explicit caveat. That zero means only that every row has a more specific static classification; it does not establish response semantics, live availability, or hardware support. The contact-fingerprint request and notification share an exact 46 four-byte shape but remain an unproven event relationship, not an acknowledgement. The phone-volume path is modeled as an inbound device request followed by an outbound host-state projection, never as an acknowledgement. The SMS-send path likewise has only an inbound 4d/06 event and an outbound 4d/07 acknowledgement candidate: value propagation, ordering, failure, and terminal behavior remain unproven. Weather refresh, motion delivery, ChatGPT action, and fragmented chat content now have explicit non-terminal event/shared-topology candidate rows too; none claims request ownership, causality, acknowledgement, or hardware support. Terminal rules comprise 36 single matched responses, 29 with no proven terminal, 17 per-frame only, two local-quiet-unknown, and one metadata-or-marker-else-local-quiet-unknown. Four private E-card/SMS sync rows now preserve only their shared inbound-update and outbound-batch topology. Content, opaque fingerprints, branch selection, ordering, completion, and local data access remain redacted or unproven. Contact-content has only a conditional reverse-direction sync candidate: a fingerprint notification may cause a local mismatch branch to reload private contacts and send a fingerprint plus content batches, while local contact changes can send the same outbound sequence without a notification. No acknowledgement, response, terminal, private-store reproduction, or runtime is established. The three formerly generic rows now record a narrower bounded fact: the reviewed dispatcher exposes no eligible callback for the exact phone-call-state, AI-language, or app-state discriminator. Responses on another discriminator, delivery, device side effects, failure, and terminal behavior remain unproven; quiet is never success. App-ID now has only a cross-opcode notification candidate, while the shared Phone-MAC opcode is recorded as an unrelated host-volume collision with no eligible callback. Two private network-configuration rows retain only a disjoint Wi-Fi state-event candidate, and the media-FTP completion-named call retains only a source-local terminal-shaped projection shared by success and exhausted failure. None establishes identifier equality, credential use, connection, transfer, acknowledgement, runtime, or hardware support. Local quiet is never promoted to success, and matching requires an operation token plus connection generation. The fake-only singleton transaction simulator composes four static query families, the screen-light subcommand, and six typed settings families with statically matched terminals. Its operation-bound device-system path performs one exact synthetic 54/11 query write and closes only on an exact post-entry 54/12 fake response. The private neutral callback code is excluded from structured output. Fake success is not current device state, Bluetooth readiness/connection, battery/power, firmware health, owner binding, live support, or hardware verification. The two shared sensor-session setting shapes are rejected because their per-frame projection is ambiguous across four interface wrappers and a shared opcode. The other three static queries use the separate shared-day history collector because their first matching frame is not a proven terminal. A synthetic mutation acknowledgement is parsed through the same closed correlation rules; this still creates no client method, live adapter, write authority, retry policy, or hardware claim. Closed request bytes are reconstructed before execution, and notification ownership starts only when the fake write coroutine actually enters; an expired pre-entry call or a pre-entry disconnect/cancellation performs no write and owns no response. Every accepted settings, personal, behavior, command, and phone request also retains an identity-bound copy of its originally validated execution shape. Post-construction field or frame changes are rejected before an operation exists; shallow and deep copies retain the same sealed shape. Response ownership checks opcode and any required branch discriminator before length: truncated unrelated traffic stays unrelated, EQ SET-kind traffic cannot close an EQ GET query, and heart-session start and stop keep distinct 14/94 and 15/95 terminals. All seven personal-setting encoders can likewise compose success-only fake matchers; their private input stays hidden and absence of a proven failure opcode remains explicit. Eight single-frame behavior mutations are also composable with paired acknowledgements. Alarm batches are deliberately rejected by this factory because their multi-frame, source-sequential semantics require a separate state machine. A dedicated fake-only alarm batch simulator preserves the exact base/content frame order without exposing a live client method. Exact 0d/8d callbacks are recorded only as uncorrelated per-frame observations: the recovered projection exposes no proven alarm ID, chunk ID, batch ID, or terminal marker, and the remaining body is uninterpreted. Privacy-safe callback multiplicity is preserved, but returned fake calls, callback counts, local quiet, and observation limits never establish batch success. An observed failure stops only future synthetic writes and taints reuse; no alarm data is retained in the result. A separate fake-only notification batch simulator composes the existing bounded notification planner with the scripted MAIN route. It issues the planner's header, title, and content frames as ordered sequential fake calls. An exact 12 callback is associated only with a marker whose frame has already been invoked in that attempt; this is a per-frame callback observation, not proof of display, delivery, whole-batch acknowledgement, or a terminal. A future marker is unowned diagnostic traffic: it is not buffered or later correlated and does not extend quiet, abort, or taint the attempt. The 92 projection has no proven marker or body semantics, so it cannot identify a failed frame. The simulator conservatively stops only not-yet-invoked fake writes and taints reuse without rolling back earlier calls or claiming batch failure. Returned calls, marker coverage, quiet, and limits never commit the planner's proposed UID/deduplication state. Caller throttling, source global-overlap behavior, atomic enqueue/delivery, and source queue acceptance are not reproduced. Results retain no notification data or frame shape, although the explicit scripted-test transport keeps private frame calls for focused test inspection. This adds no client method, live vendor write authority, owner authorization, hardware verification, or input path. All fake transaction and stream coordinators now share one pure main/raw GATT resolver. It checks that connection-scoped characteristic metadata is structurally consistent and refuses UUID ambiguity, missing response-write/notify properties, and CCCD metadata gaps. Each exact fake coordinator then separately verifies that the resolved targets belong to its current transport snapshot before synthetic I/O. Bleak retains an opaque target-to-characteristic map only for current-identity checks and exposes no live target I/O; its direct write boundary accepts only the guarded standard Current Time characteristic when exactly one writable instance exists under the Current Time service and the payload is canonical. Vendor writes and hardware eligibility remain zero. Seven additional no-argument main queries and the typed screen-light request use subcommand-aware fake matchers. Streaming Wi-Fi scan is rejected rather than being misrepresented as a singleton response. Three typed command encoder variants—device time plus heart-rate start and stop—compose because their interface rows have statically matched terminals. Nine typed command value/event projections and two phone projections no longer become transaction success; six other command families with no exact response relationship remain rejected. Only the user-info phone integration has a singleton matched terminal. Other private sync/content families remain rejected until their streaming or causal terminal rules are proven. Across all 85 deterministic requests, a closed fake-singleton eligibility ledger now separates 36 singleton matched terminals, 11 typed non-terminal projections, six ambiguous or batched per-frame routes, 29 with no proven terminal, and three locally/marker-bounded streams. Only the first group can enter the success-returning fake engine; this grants no live, owner-authorized, or hardware eligibility. Dedicated alarm and notification batch simulators do not change that crosswalk. setNotify remains one of the six ambiguous/batched per-frame rows: its invoked-marker relationship is not a whole-batch terminal, and every live and hardware count remains zero. The scripted fake now has a distinct raw 33f5/33f6 route and bounded event collector. It can write a closed raw command and parse typed raw notifications, but always reports unknown completeness: an event is not an acknowledgement, reaching an event limit is not success, and local quiet is not a terminal. Queue size, setup, overall collection, and cleanup are bounded; concurrent use is rejected and stale callbacks are made inert before reuse. Shared multi-sport, oxygen-day, and advanced-sensor-day responses now have a separate fake history collector. It preserves per-frame callback multiplicities, conditional multi-sport failure, unrelated-event isolation, and unknown completion at both local quiet and caller limits; it never emits a synthetic wire-end event. After accepted oxygen or advanced-sensor data followed by local quiet, it does reproduce the source's single local end projection with a hidden last-sample timestamp, while leaving completeness unknown. This collector applies the same bounded-stage, bounded-queue, single-flight, cancellation-cleanup, and stale-callback rules.

The internal streaming simulators are deliberately separate from the live client:

Fake-only collector What it can reproduce Honest stopping state
Generic day history Typed sample/end callback multiplicity Wire/metadata completion only when explicit; otherwise unknown or aborted
Wi-Fi network-name response stream Advertised count and locally assembled SSID callbacks Fake-call return leaves protocol completion unknown; uncertain calls taint reuse
ECG history Metadata, event, and one packed-sample callback per frame No proven terminal; quiet/limit remain unknown

They accept only the scripted fake transport, retain no raw notification frames after cleanup, and never contact a Linux Bluetooth device. Private Wi-Fi/health values are available only through explicitly named local-test accessors and never appear in result representations or ordinary dataclass serialization. An independent app-use view shows that the APK directly invokes 51 of 112 request targets at 152 static call sites; 43 uninvoked SDK entries still have wire codecs, 14 are local/composite, and four are no-op stubs. It also reconciles 181 callback invoke sites: 125 in the main response handler, six in the raw handler, and 50 elsewhere. Those sites reach 103 of 105 declarations; two have no direct invoke. These static counts do not prove runtime reachability. The sanitized Binder crosswalk adds exact transaction IDs and semantic-versus-Parcel kinds for all 217 rows. Every ID is contiguous and agrees across interface, Proxy, Stub, and implementation; all calls are synchronous and all ordered marshalling checks match. Each row links its existing app-use and codec status while leaving wire relationships explicitly unclassified. Binder parity still does not establish BLE semantics or live support. The report distinguishes those from absent, APK-generated, and non-Bluetooth behavior, and always reports zero live or hardware-verified vendor operations. It contains no payload bytes and grants no write authority.

Run jring doctor before touching hardware. It passively checks Python, Linux, Bleak, BlueZ, evdev, and /dev/uinput, explains exactly what is missing, and reports simulator, BLE-hardware, and desktop-input readiness independently. It does not scan, connect, write, or use the network. Automation can use jring doctor --json --require-hardware when missing BLE prerequisites should produce a nonzero exit status. Use --require-input to require evdev and writable uinput instead.

Use a sensor event as desktop input

Live ring input is not available yet. Inspect the local evidence and candidate boundary without Bluetooth first; this includes standard HID metadata, media/volume/shutter actions, the cumulative step counter, unknown motion channels, classic profile/RFCOMM evidence, classic metadata callbacks, the host volume-state request, and raw non-health framing. It also exposes 15 closed general-use rows for already-decoded AI/speech, Wi-Fi, system-state, EQ/media/dial, touch, and screen-light surfaces:

jring non-health-capabilities
jring non-health-capabilities --json

That inventory now labels the device-action, cumulative-step, unknown-motion, Classic info/name, host-volume, main-chat-action, Wi-Fi callback-code, and touch-mode rows whose passive MAIN notifications can be exercised with the exact scripted fake. The same internal collector also exercises the exact redacted App-ID event at 45/02 without adding a human capability row. The fake subscribes to its instance-bound synthetic response target and performs zero writes. It accepts 78/00 and 78/01 only as private, neutral nine-channel callback projections and 78/09 only as a neutral, private touch-mode setting projection; every other 78 selector remains unrelated. The motion values are redacted and do not prove selector meaning, axes, units, cadence, activation, a live sensor event, gesture, step, button, or input action. The corresponding setter has no observed app invoke, so inbound 00/01 is not an acknowledgement or enabled/disabled state. The touch value is not an enabled flag, device state, gesture, tap, button, sensor sample, or input event. The bundled setTouchMode entry has zero observed app invokes, so the projection proves no setter causation, acknowledgement, terminal, live behavior, or hardware support. Decoded values remain redacted and hardware- and input-ineligible. App-ID is likewise only an uncorrelated callback event: it does not prove setter causation, identifier equality, acknowledgement, or a terminal. Exact opcode 4E is separately retained only as a private, neutral chat action-code candidate. This zero-write fake run owns no request, while the protocol relationship to nearby request families remains unknown. It does not execute ChatGPT; parse or retain prompt, response, text, audio, image, or other content; acknowledge a request; establish a terminal; or create input. Exact 54/04 is separately projected as one private, neutral Wi-Fi callback state-code candidate. Address material is discarded, and the fake performs no write, credential processing, host/ring networking, or radio change. The code does not report whether Wi-Fi is enabled, connected, joined, current, or internet-reachable; it is not an acknowledgement, terminal, live behavior, hardware verification, or input. It is a library test surface, not a live-ring or Classic-attachment command.

The Wi-Fi network-name inventory row separately identifies the existing library-only fake response assembler. It invokes one exact request only on the scripted fake, never starts a host or ring Wi-Fi scan, hides private names and fragment metadata from normal serialization, and treats a returned fake write call, advertised-count equality, local quiet, and caller limits as unknown protocol completion. It adds no CLI run command, network authority, live Bluetooth path, or input eligibility.

A separate library-only fake coordinator exercises the proven host-volume reverse pipeline: one exact fake 49 request can cause one closed projection of explicitly caller-supplied offline values on the same fake connection. It never reads or changes desktop audio, never accepts a generic payload, never retries an uncertain write, and never claims that the returned fake transport call is an application acknowledgement or protocol terminal. The passive collector remains strictly zero-write; neither path is exposed as a CLI or live Bluetooth feature.

Separately, the input simulator generates a closed pair of synthetic cumulative-step frames, decodes them through the recovered 51 parser, baselines the first value, and accepts one exact increment as a neutral step preview. It does not consume passive MAIN events or accept counter/frame input from the command line. The experimental counter candidate is not directly dispatchable; only this closed synthetic fixture is promoted into the existing allowlisted simulator event. That event can preview a keyboard key or mouse click, but media, volume, and shutter actions cannot yet be previewed or mapped. Preview is the default and never emits operating-system input:

jring input-actions
jring input-actions --json
jring capabilities --simulate
jring capabilities --simulate --simulate-profile hid
jring input --simulate --map step=key:space
jring input --simulate --map step=click:primary

input-actions is entirely local and lists both simulator profiles plus the complete action vocabulary without Bluetooth, evdev, or /dev/uinput. Its plain-text order is suitable for screen readers. It labels the mouse actions as primary (left), secondary (right), and middle, and states that step is currently a simulator event—not a required physical gesture.

To deliberately inject one simulated event through Linux uinput, keep the base runtime install and add the confirmation flag:

python -m pip install -e .
jring input --simulate --map step=key:space --allow-input

Named keys are space, enter, escape, the four arrows, page-up, and page-down; mouse clicks are primary (left), secondary (right), and middle. Each alias selects the same action, and the temporary Linux input device advertises only the selected key or button. Arbitrary key codes and shell commands are rejected. Status can report that a standard Bluetooth HID service was advertised, but service presence alone does not prove that HID reports work or an operating-system input device exists.

Hardware JRing motion events are not enabled yet: the vendor event frames are not verified. This boundary prevents a guessed packet or misclassified health payload from generating desktop input.

JRing is not a live HID driver. Linux uinput is used only for an explicitly approved simulated event today and is a future translation sink for hardware events only after those events are owner-verified.

jring capabilities --simulate demonstrates the versioned non-health inventory. The offline jring non-health-capabilities view lists all 13 statically mapped device actions: six input candidates and seven blocked side-effecting actions. With the same local-only command, five supplemental evidence rows keep classic profile attachment, an RFCOMM socket lifecycle reference, two classic metadata callbacks, and the host volume-state request visible without claiming that any is live or HID-compatible. The reviewed helper only constructs and closes the classic socket; actual OTA transfer uses GATT, and no RFCOMM connect, read, or write was observed. With the same view, 15 general-use rows link these static surfaces back to their recovered request/callback ledger names. Network names, credentials, media references, and AI/voice state are privacy-classified but never stored. Every row remains non-runnable, hardware-ineligible, and hardware-unverified; a separate scripted_fake_decoder_available field identifies decoder coverage exercised only by the passive scripted fake. With an explicitly selected device, jring capabilities --address-file ... enumerates only standard service/characteristic/descriptor metadata. It never reads a HID Report Map or report value and never subscribes. A read property is only advertised metadata; no value was read. Repeated HID Report characteristics remain separate numbered metadata instances with their own Report Reference descriptor state. HID usability and operating-system attachment remain unverified/not checked. The same snapshot produces exactly two sanitized vendor-route rows, main then raw. Each keeps service/metadata availability, structural preflight, and transport target ownership separate; no characteristic value, target ID, generation, subscription, or write is exposed or attempted.

time-sync is the sole hardware write and targets the standard Bluetooth Current Time characteristic. Some rings may not expose it; failure is safe. History export accepts .csv or .jsonl and refuses to replace an existing file unless --force is given. Hardware history deliberately reports "not verified" rather than guessing a vendor command.

Least-privilege BlueZ setup

Run bluetoothd through your distribution's normal service management. Prefer a local desktop/logind session, where BlueZ's D-Bus policy grants Bluetooth access. Do not run the client as root and do not make the system D-Bus socket world-writable. If a headless service is required, create a dedicated unprivileged user and a narrowly scoped polkit rule granting only BlueZ scan/connect/GATT operations; exact policy syntax varies by distribution. Do not grant network, serial-port, storage, or sudo access to this client.

Pair through the normal BlueZ UI or bluetoothctl only if the selected ring requires it. This client does not automate trust or pairing. Disable Bluetooth when not in use.

Privacy and threat model

BLE advertisements expose proximity and may expose a stable address. Health readings and exported history are sensitive. Discovery output must use per-process aliases; diagnostics omit raw payloads and addresses. Exports remain local, are written atomically with restrictive permissions, and should be stored on encrypted media.

The client assumes the local user, Python environment, BlueZ, and kernel are trusted. It defends against malformed/truncated BLE values, accidental selection, unbounded waits/retries, guessed vendor writes, telemetry, and accidental identifier logging. It does not defend against a compromised host, malicious Bluetooth stack, radio tracking, or a device presenting false measurements. Measurements are not medical advice.

No cloud API, authentication bypass, account impersonation, firmware flashing, DFU, contact/notification upload, or telemetry is implemented.

Validation

Run all repository tests with:

python -m pip install -e '.[dev]'
python -m pytest

The optional hardware test is skipped unless both JRING_HARDWARE_TEST=1 and an exact JRING_DEVICE_ADDRESS are supplied. It is currently only an opt-in guard because no ring was available during development. See DESIGN.md for evidence, confidence levels, architecture, acceptance criteria, and exact gaps. Human-facing behavior and its test map live in UX_SPEC.md. The APK-first source contract is the complete, evidence-graded clean-room functionality specification, with linked request, callback, transport, session, OTA, UI, and data appendices. It describes the reviewed Android build rather than simulator behavior or a claim of hardware parity. The cross-persona adversarial UX review records the v0.5 trust repairs and the gates that remain before live sensor-to-input bridging. All deferred work, including non-health HID/sensor functionality, is owned by the JTBD/SDD/TDD roadmap and its linked GitHub issues.

License

JRing Client is distributed under the MIT License.

Metadata

Release files for jring-client 0.6.18

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jring-client 0.6.18
File Size Uploaded
jring_client-0.6.18.tar.gz 757.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for jring-client 0.6.18
File Interpreter ABI Platform
jring_client-0.6.18-py3-none-any.whl Python 3 none any Details

Total release size: 1.1 MB

Release files / jring_client-0.6.18.tar.gz

Download URL jring_client-0.6.18.tar.gz
Size 757.2 kB
Tags Source
SHA-256 checksum
How to use checksums
e18a14631a81027a00b6abb7e7b1997a279d5aad5105b93945e2222f24e28274
BLAKE2b-256 checksum
How to use checksums
bb7fb9b33893039f395f3f588b8b2cbf5b1d440734fbb69b2435d401b49b70c5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.

Transparency log

Release files / jring_client-0.6.18-py3-none-any.whl

Download URL jring_client-0.6.18-py3-none-any.whl
Size 344.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c1e52044100ac7b1335f026a08d689bbf08b962280b0924b556c822ee8a1be26
BLAKE2b-256 checksum
How to use checksums
fa2d187ddece9598da19b01111f82235d8b14b52f6fbf77c4fb92e49e84b6516
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.6.18 This release

2 release files

0.6.17

2 release files

0.6.6

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.0

2 release 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