OpenPiste Protocol Level 2 — C++ reference implementation
A C++ library for building fencing electronics that communicate using the OpenPiste Protocol Level 2 (OPP2) — a modern, open MQTT/JSON communication standard for scoring apparatus, displays, remote controls, and competition management software.
Most commercial fencing scoring equipment communicates using EFP1.1 (commonly called Cyrano), a UDP-based protocol that has been in use since 2008. It is error-prone, difficult to extend, and locks clubs and federations into proprietary vendor ecosystems.
OPP2 replaces it with MQTT and JSON — the same communication stack used across the IoT industry, with libraries available for every platform from microcontrollers to cloud services. The result is reliable message delivery, multiple simultaneous subscribers, retained state for late-joining clients, and millisecond-precision timestamps for video synchronisation.
OPP2 covers the full lifecycle of a fencing bout:
| Message | Publisher | Description |
|---|---|---|
lights |
apparatus | On-target and off-target light state |
clock |
apparatus | Stopwatch — running, stopped, time value |
blade_contact |
apparatus | Raw blade contact event with timestamp |
score |
apparatus / software | Scores, cards, priority, fencer status |
connection |
apparatus | Online/offline status, firmware version |
state |
apparatus | Apparatus state: fencing, halt, pause, waiting, ending |
fencers |
software | Fencer, coach, referee, and video official identities |
match |
software | Weapon, competition, phase, round metadata |
uw2f |
apparatus | Passivity timer and P-card state |
medical |
apparatus | Medical timeout with countdown timer |
video_review |
apparatus / software | Review requests, decisions, and call history |
control |
apparatus / software / remote | Commands between devices |
The full protocol specification is at openpiste.org and in the OpenPiste repository.
- Typed structs for all 12 OPP2 message types — no raw JSON parsing in your application code
- Serializer — populate a struct, get a JSON string ready to publish
- Deserializer — receive a JSON payload, get a populated struct
- Topic parser and builder — parse and construct
openpiste/{piste_id}/{publisher}/{message_type}strings - Dispatcher — route incoming MQTT messages to typed callbacks with a single
dispatch()call - SystemState — optional live mirror of all retained piste state, updated automatically on every received message
- Forward compatibility — unknown enum values deserialize to
UNKNOWNrather than causing errors; new protocol versions are accepted gracefully
The library is designed for both directions: apparatus firmware that publishes, and subscriber implementations (displays, monitors, video tools, competition software) that consume.
| Platform | Framework | Status |
|---|---|---|
| ESP32 | Arduino | Primary target |
| ESP32 | ESP-IDF | Supported |
| Linux / macOS / Windows | Native (PlatformIO) | Supported — used for unit testing |
| Any C++11 target | — | Header-only, no platform dependencies |
The library has one external dependency: ArduinoJson (v6 or v7), which itself supports all the above platforms.
Add to your platformio.ini:
lib_deps =
https://github.com/OpenPiste/opp2-library
bblanchon/ArduinoJson @ ^7.0.0- Download the latest release zip from the Releases page
- In Arduino IDE: Sketch → Include Library → Add .ZIP Library
- Install ArduinoJson via the Library Manager
Copy the src/ directory into your project and include opp2.h.
#include <opp2.h>
// Build a lights message
OPP2::Lights msg;
msg.seq = nextSeq();
msg.ts = OPP2::Timestamp::fromEpochMs(epochMillis());
msg.left.on_target = true; // red light on
msg.left.white = false;
msg.right.on_target = false;
msg.right.white = false;
// Serialize to JSON
char payload[128];
OPP2::Serializer::serialize(msg, payload, sizeof(payload));
// payload == {"protocol":"OPP2","version":"1.0","seq":1,"ts":...,"right":{"green":false,"white":false},"left":{"red":true,"white":false}}
// Build the MQTT topic
char topic[64];
OPP2::TopicParser::buildFrom("17", OPP2::Publisher::APPARATUS,
OPP2::MessageType::LIGHTS, topic, sizeof(topic));
// topic == "openpiste/17/apparatus/lights"
// Publish using your MQTT client of choice
mqttClient.publish(topic, payload, /*retained=*/true);#include <opp2.h>
OPP2::Dispatcher dispatcher;
OPP2::SystemState pisteState; // live mirror of all retained topics
// Register callbacks for the message types you care about
dispatcher.onLights = [](const OPP2::Topic& t, const OPP2::Lights& msg) {
Serial.printf("Piste %s: left=%s right=%s\n",
t.piste_id,
msg.left.on_target ? "ON" : "off",
msg.right.on_target ? "ON" : "off");
};
dispatcher.onScore = [](const OPP2::Topic& t, const OPP2::Score& msg) {
Serial.printf("Score: %d - %d\n", msg.left.score, msg.right.score);
};
// Attach SystemState to keep a live mirror automatically
dispatcher.setSystemState(&pisteState);
// In your MQTT callback:
void mqttCallback(const char* topic, uint8_t* payload, unsigned int length) {
dispatcher.dispatch(topic, (const char*)payload, length);
}// Set up LWT before connecting — the broker publishes this automatically
// if your device disconnects unexpectedly
char lwtTopic[64];
OPP2::TopicParser::buildLwtTopic("17", lwtTopic, sizeof(lwtTopic));
// lwtTopic == "openpiste/17/apparatus/connection"
const char* lwtPayload = "{\"online\":false}";
mqttClient.setWill(lwtTopic, lwtPayload, /*retained=*/true, /*qos=*/1);OPP2 is designed to run on a local network — a club network, a competition network, or a self-contained access point. No internet connection is required.
Recommended broker: Mosquitto — lightweight, open source, runs on a Raspberry Pi or laptop.
Default broker hostname: openpiste.local (mDNS) — all OPP2-compatible devices use this as their default broker address, so no IP configuration is needed on a local network.
NTP: For video synchronisation, run a local NTP server on the same host as the broker. All devices synchronise to UTC from it, making timestamps comparable across apparatus, displays, and video tools.
All OPP2 topics follow this pattern:
openpiste/{piste_id}/{publisher}/{message_type}
The piste_id can be a number, name, or colour (17, podium, rouge, vert). The publisher segment identifies who sent the message (apparatus, software, remote). Both are authoritative — they are not duplicated in the payload.
Useful subscription patterns:
openpiste/# all messages from all pistes
openpiste/17/# all messages from piste 17
openpiste/+/apparatus/lights lights from all pistes
openpiste/+/+/control control events from all publishers, all pistes
The library is header-only and structured in layers:
opp2.h ← single include (pulls in everything below)
├── opp2_types.h ← structs, enums, Timestamp, SystemState
├── opp2_topic.h ← topic parser and builder
├── opp2_time.h ← time string formatting (M:SS, M:SS.cc)
├── opp2_serialize.h ← struct → JSON (requires ArduinoJson)
├── opp2_deserialize.h ← JSON → struct (requires ArduinoJson)
└── opp2_dispatcher.h ← topic routing, callbacks, SystemState mirror
Each layer can be included independently. If you only need topic parsing and struct definitions (no JSON), include opp2_types.h and opp2_topic.h directly — no ArduinoJson dependency.
git clone https://github.com/OpenPiste/opp2-library
cd opp2-library
pio test -e nativePlatformIO downloads all dependencies automatically on first run. The test suite covers all 12 message types with serialization round-trips, deserialization error paths, forward compatibility, LWT handling, dispatcher routing, and SystemState mirroring.
To run a single test suite:
pio test -e native -f test_topic
pio test -e native -f test_serializeThis library is one component of the OpenPiste open source fencing electronics platform — a complete alternative to expensive proprietary scoring equipment, covering scoring hardware, weapon and wire testing, remote controls, piste monitoring, and electronics designs.
The platform is developed by Piet Wauters, member of the FIE SEMI Commission (technology commission of the International Fencing Federation) and EFC SEMI Commission, and field-tested at international competitions.
- Platform website: openpiste.org
- Platform organisation: github.com/OpenPiste
- Protocol specification: openpiste.org/docs/platform
Contributions are welcome — bug reports, protocol feedback, implementations in other languages, and hardware integrations.
Please read CONTRIBUTING.md before opening a pull request. Note that contributions require signing a Contributor Licence Agreement to allow the project to be relicensed or used commercially in the future, while you retain copyright over your own contributions.
MIT — see LICENSE.
The protocol specification (OPP2) is separately released under MIT licence. Any implementation — in any language, by any party — may use it without restriction.