How it works
Each club connects CueTracker to its own MQTT broker and selects the match and competition events it wants to publish. Every club has an isolated, asynchronous client, so a slow or unreachable broker does not block another club. Open the club, choose Settings, expand Broker connection, then enter the broker details and test the connection. Automations subscribe to the club event stream or an individual match state topic.
Use a dedicated broker account for CueTracker. Broker passwords are encrypted at rest and credentials are redacted from connection errors. Keep controller, display, session and API credentials separate from MQTT broker credentials.
Real-world venue ideas
MQTT is best for actions that should happen immediately when somebody uses the Controller screen. CueTracker publishes information outward; MQTT subscribers cannot change scores or control a match.
Use match.called_to_table to announce both sides and the allocated table over speakers, send a phone notification or update a waiting-area screen before the first break.
Use achievement.master_break or achievement.master_first_visit to run a short lighting scene, sound effect or celebration animation for the named player.
Turn a table light green when a match is called, change it during live play and return it to its normal scene after match.finished.
Flash an LED strip or sound a quiet warning at the configured warning and alert thresholds, then use a stronger signal for shot_clock.expired.
Start an OBS scene or NVR recording when play starts, add chapter markers after frames and stop or archive the recording when the match finishes.
Subscribe to retained match-state topics so a custom display, LED scoreboard or foyer panel can recover the latest score immediately after it reconnects.
Other useful ideas include a referee call button workflow after a foul, accessibility announcements, sponsor animations between frames, a “next match” display and a private club activity log. Keep celebrations brief so they do not interfere with nearby tables.
Topics
Retained online or offline status for the club publisher.
Club-wide event stream containing only the selected subscription events.
Retained current state for a specific match. It updates after every scoring or clock action, including a shot-clock reset, but it is not an event subscription.
CueTracker permanently assigns cuetracker/<club-id> to the club. The prefix is read-only, remains stable when the club is renamed and prevents two clubs from publishing to the same topics.
Event names
- frame.started
- frame.finished
- match.called_to_table
- match.started
- match.paused
- match.resumed
- match.finished
- competition.started
- competition.finished
- turn.changed
- ball.potted
- foul.recorded
- achievement.master_break
- achievement.master_first_visit
- shot_clock.reset
- shot_clock.warning
- shot_clock.alert
- shot_clock.expired
Select the event types CueTracker publishes in the club’s MQTT settings. Existing clubs can enable the two achievement events by reopening Broker connection and saving their event selections.
| Event | When it is published |
|---|---|
| match.called_to_table | A match is allocated and ready for its players, before the initial frame has started. The payload includes the table and both teams’ player names. |
| match.paused | The controller’s Pause action pauses the active visit and shot clock. |
| match.resumed | The controller’s Resume action restarts the paused visit and shot clock. |
| turn.changed | The active side or player changes through the turn control. |
| ball.potted | One or more balls are recorded as potted. |
| foul.recorded | Record foul is used for the active player on the Controller screen. |
| achievement.master_break | A player legally pots all eight balls in the opening visit after breaking. |
| achievement.master_first_visit | A player who did not break legally pots all eight balls on their first visit. |
| shot_clock.reset | The shot clock is started or manually reset. |
| shot_clock.warning | The running clock reaches that match’s enabled Warning at value. |
| shot_clock.alert | The running clock reaches that match’s enabled Alert at value. |
| shot_clock.expired | The running clock reaches zero. |
Master achievement events are published only when the recorded frame data proves the achievement; they are not inferred from a final score. Their payload identifies the achievement type, player, side, frame and eight-ball run. Warning and alert messages follow each match’s configured timer thresholds. A disabled warning or alert does not publish its corresponding event. Each threshold publishes once per shot, and resetting the clock begins a new shot.
Event payload
{
"schemaVersion": 1,
"messageType": "event",
"eventId": "unique-event-id",
"event": "frame.finished",
"occurredAt": "2026-09-10T12:30:00.000Z",
"club": {
"id": "club-id",
"name": "Example Club"
},
"match": {
"id": "match-id",
"name": "Friday Final",
"format": "singles",
"table": "Table 1",
"teams": {
"A": { "name": "Craig", "players": ["Craig"] },
"B": { "name": "Taylor", "players": ["Taylor"] }
}
},
"frame": {
"number": 3,
"winner": "Craig",
"score": {
"A": 2,
"B": 1
}
}
}{
"event": "achievement.master_break",
"match": {
"id": "match-id",
"table": "Table 1",
"teams": {
"A": { "name": "Craig", "players": ["Craig"] },
"B": { "name": "Taylor", "players": ["Taylor"] }
}
},
"frame": {
"number": 3,
"winner": "Craig",
"score": { "A": 2, "B": 1 }
},
"achievement": {
"type": "master-break",
"frame": 3,
"side": "A",
"player": "Craig",
"run": 8
}
}Every event has the same required envelope: schemaVersion, messageType, eventId, event, occurredAt and club. Match, competition, frame, turn, ball, foul, pause, achievement and shot-clock blocks are added only when relevant. Each logical event receives one UUID event ID before it enters the durable outbox; a retry keeps that same ID.
Match blocks include the table plus both team names and player lists, making call-up announcements possible without another lookup. Turn events include previous and current side/player details. Pot events include the side, player, number potted and balls remaining. Foul events include the frame, side, player and that player’s match total. Pause and resume events include the active frame, side and player. Achievement events include the verified type, player, side, frame and run. Shot-clock events include the frame, side, player, total limit and threshold remaining.
Schema versioning
Schema version 1 is the current contract. CueTracker validates the version when building every event envelope. Adding an optional field or a new event name remains version 1; removing or renaming a field, changing its type, or changing required envelope semantics requires a new schema version. Consumers should reject unsupported major schema versions and ignore unknown optional fields.
| Version | Compatibility record |
|---|---|
| 1 | Original common event envelope, per-event detail blocks, UUID event IDs and ISO UTC occurrence timestamps. |
Delivery behaviour
| Area | Behaviour |
|---|---|
| Quality of service | Choose QoS 0, 1 or 2 in Club Settings. New connections default to QoS 1. |
| Event retention | Event messages are not retained by the broker. |
| Match state | State messages are retained and identify themselves with messageType: match.state plus the action in cause. Pending state for the same match is coalesced to its latest value. |
| Availability | Online/offline status is retained. |
| Duplicates | Consumers deduplicate event messages using the eventId when QoS can redeliver. |
| Ordering | One serial outbox is flushed per club. A failed event remains first in line, so later events from that club cannot overtake it. |
| Occurrence time | occurredAt is written when the action happens and is retained unchanged while an event waits or retries. |
| Offline backlog | Unpublished messages are stored durably and replayed after reconnect. Each club keeps at most 10,000 pending messages by default; pending match state is coalesced and, only if the cap is reached, the oldest non-retained events are discarded first. |
| Reconnect | CueTracker reconnects independently per club with exponential backoff from one second to a 60-second cap. |
| Plan downgrade | The saved broker, credentials, topic and event choices remain in Club Settings, but publishing and reconnects are disabled while the club’s plan does not include MQTT. |
| Subscriptions | Each club selects the event types it publishes. |
| TLS | Always use TLS for a public broker. CueTracker shows a prominent security warning when a public broker is configured without it; plaintext remains available for existing integrations and private LAN brokers. TLS connections validate the broker certificate; when connecting to a LAN IP, enter the DNS name on the certificate in TLS certificate hostname. |
Subscribe to the exact /events topic for automations based on event names. A wildcard subscription also receives retained match-state updates and availability messages. CueTracker operators monitor active connections, last successful publish time, publish successes and failures, reconnects, pending messages and cap drops; broker credentials are never included in those metrics.
Home Assistant use
These examples listen to the same club event topic but respond only to one selected event name.
Call both sides to their table
alias: CueTracker - call players to table
mode: queued
trigger:
- platform: mqtt
topic: cuetracker/club-id/events
condition:
- condition: template
value_template: "{{ trigger.payload_json.event == 'match.called_to_table' }}"
action:
- service: notify.mobile_app_club_manager
data:
title: "Match ready · {{ trigger.payload_json.match.table }}"
message: >-
{{ trigger.payload_json.match.teams.A.players | join(', ') }} versus
{{ trigger.payload_json.match.teams.B.players | join(', ') }}.
Please report to {{ trigger.payload_json.match.table }}.Celebrate a Master Break or Master First Visit
alias: CueTracker - master achievement party
mode: queued
trigger:
- platform: mqtt
topic: cuetracker/club-id/events
condition:
- condition: template
value_template: >-
{{ trigger.payload_json.event in
['achievement.master_break', 'achievement.master_first_visit'] }}
action:
- service: light.turn_on
target:
entity_id: light.pool_room
data:
flash: short
color_name: purple
- service: media_player.play_media
target:
entity_id: media_player.pool_room
data:
media_content_id: https://example.com/master-achievement.mp3
media_content_type: musicThe event’s achievement.player and achievement.type values can also be used in a spoken announcement or display animation. For reliable real-world use, store the event ID in the automation so repeated QoS 1 delivery does not call the players or play the same celebration twice.