This project is not affiliated with or endorsed by Invant Inc. ExPutt and ExPutt RG are trademarks of Invant Inc.
Rust client library (crate threeputt) for the ExPutt RG putting device over
WiFi. It talks to the device the way the device's mobile app does and delivers
each measured putt (launch direction, ball speed, and putter path and face
angle when the putter is tracked) to your program. The device reports ball
speed and direction only; distance is left to the consumer (for example a
simulator).
discoverwaits for the device's UDP beacon, or connect straight to a known IP.Sessionconnects, runs the setup (handshake, mat sync, putting hand, start measuring), sends keepalives, and deliversEvents over a channel.Session::find_matlocates the mat again on a live session, without reconnecting. Every new round, including a hand change (Session::restart_match), locates the mat again too.Connectionis the lower layer: send aRequest, receive anEvent.
Plain blocking std::net sockets and threads. No async runtime, no ZeroMQ
library.
This project is an exercise of the interoperability exception under 17 U.S.C. § 1201(f):
(f) Reverse Engineering.—
(1) Notwithstanding the provisions of subsection (a)(1)(A), a person who has lawfully obtained the right to use a copy of a computer program may circumvent a technological measure that effectively controls access to a particular portion of that program for the sole purpose of identifying and analyzing those elements of the program that are necessary to achieve interoperability of an independently created computer program with other programs, and that have not previously been readily made available to the person engaging in the circumvention, to the extent any such acts of identification and analysis do not constitute infringement under this title.
(2) Notwithstanding the provisions of subsections (a)(2) and (b), a person may develop and employ technological means to circumvent a technological measure, or to circumvent protection afforded by a technological measure, in order to enable the identification and analysis described in paragraph (1), or for the purpose of enabling interoperability of an independently created computer program with other programs, if such means are necessary to achieve such interoperability, to the extent that doing so does not constitute infringement under this title.
The ExPutt RG uses a proprietary protocol over WiFi to communicate putt data (ball speed, launch direction, putter path and face angle, etc.) to its mobile app. Invant does not publish this protocol or provide an SDK for third-party integration. The protocol was worked out from WiFi traffic between the researcher's own lawfully purchased device and the official ExPutt app, and from analysis of that app, solely to enable interoperability with third-party golf simulation software.
No Invant code is reproduced here; the protocol is described and implemented in our own words and names. No access controls were circumvented — the device accepts unencrypted connections on the local network and its session handshake carries no secret, and all traffic was captured from the researcher's own hardware.
This project exists solely to enable interoperability between the ExPutt RG and third-party golf simulation software.
It must not be used to:
- Circumvent licensing or subscription requirements on Invant products
- Unlock paid features without purchase
- Bypass any access controls on Invant software or services
Issues or discussions proposing circumvention of licensing will be closed and the user blocked.
Licensed under either of:
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
at your option.
Beta (0.0.x). Verified against one ExPutt RG (device software
1.21.2176). The API may change between releases. Several values in a putt
result are not yet identified; see the table below.
Put the device in mobile mode (from its TV login screen) and join the same network, then:
use std::time::Duration;
use threeputt::{Event, Session, SessionOptions, discover};
fn main() -> threeputt::Result<()> {
let beacon = discover(Duration::from_secs(30))?;
let session = Session::start(beacon.ip, &SessionOptions::default())?;
for event in session.events() {
if let Event::PuttResult(putt) = event {
println!(
"{:+.1}°, {:.2} m/s",
putt.launch_direction_deg, putt.ball_speed_mps
);
}
}
Ok(())
}Or run the example monitor, which prints every event:
cargo run --example monitor -- [--ip IP] [--left] [--frames DIR]--frames DIR saves the camera images the device streams while it locates
the mat (SessionOptions::camera; without it the session still starts and
stops the stream but drops the frames). The images are upside down; rotate
them 180° to view. Ctrl-C ends the session cleanly.
Each PuttResult event carries 16 values. A value the device did not measure
is -9999 on the wire and None in the typed fields. When the putter is not
tracked, values 3–16 are all unmeasured and only the ball values are present.
| # | Meaning | Unit, sign | Field | Status |
|---|---|---|---|---|
| 1 | Launch direction | degrees, + = right | launch_direction_deg |
confirmed |
| 2 | Ball speed | m/s | ball_speed_mps |
confirmed |
| 3 | unknown | raw[2] |
unknown | |
| 4 | Putter speed | m/s | club_speed_mps_unconfirmed |
unconfirmed |
| 5 | Putter path | degrees, + = right | path_deg |
confirmed |
| 6 | Face angle at impact | degrees, + = right | face_deg |
confirmed |
| 7–16 | unknown | raw[6..] |
unknown |
"Confirmed" means the value matches what the device's own display shows for
the same putt. All 16 values are always available in raw.
| Device comment | Event |
|---|---|
Test |
Heartbeat (about once a second) |
FindMat |
FindingMat |
MatOK |
MatFound |
PuttReady |
PuttReady |
PuttShot |
BallStruck (can fire without a putt following) |
IndiCator / IndiCatorOut |
Indicator / IndicatorOut |
PuttResult |
PuttResult |
| camera image | CameraFrame (JPEG bytes) |
| anything else | Unknown { packet_id, comment } |
Unrecognized packets are never errors.
- Discovery: in mobile mode the device broadcasts a JSON beacon about once a second to UDP port 9999, including its IP address.
- Transport: TCP port 8889, ZeroMQ ZMTP 2.0. The device is a
ROUTER, the client aDEALER. Every message is one frame holding one JSON object. - Session: identify (
N_VERIFY_ID), join (N_REQ_CONNECT), locate the mat (sendImage,resyncToMat, thenstopSendImageonce the mat is found:resyncToMatmakes the device stream camera frames, about 10 per second, until told to stop), set the putting hand, start measuring (PM_API_MatchBegin). The client sends a keepalive (N_ALIVE) every 2 s. On close it sendsPM_API_MatchEndedandN_APP_PAUSE. - Events: device events arrive as packets whose comment is
Name,args...; putt results arePuttResult,followed by the 16 values.
The testing feature adds threeputt::testing::MockDevice, an in-process
mock device on a local TCP port, plus sample putt results, for testing code
built on this crate without hardware.