Описание протоколов взаимодействия с матч-контроллером Кайросс
Данные от матч-контроллера могут передаваться в двух режимах:
- по запросу (request, on-demand, pull mode)
- подписка/прослушивание (subscribe, streaming, push mode)
Доступные протоколы и форматы данных
| Протокол | HTTP REST | Server-Sent Events | UDP Multicast | NiBUS | COLOSSEO |
|---|---|---|---|---|---|
| Среда | Ethernet | Ethernet | Ethernet | RS-485 | RS-485 |
| Режим | запрос | подписка | подписка | прослушивание | прослушивание |
| Формат | JSON | JSON | JSON | бинарные данные | бинарные данные |
Имя хоста и адрес матч-контроллера
В некторых случаях вам может понадобиться адрес матч-контроллера в сети. Можно попробовать обратиться к нему по имени matchpad.local.
Чтобы на Windows корректно работали адреса вида *.local (mDNS, Multicast DNS), нужно:
-
Поддержка mDNS в самой системе
Windows 10 (с недавних версий) и Windows 11 уже имеют встроенную поддержку mDNS. Адреса hostname.local должны работать без дополнительного софта, если включены сетевые службы обнаружения.
-
Если не работает «из коробки»
Обычно проблема в том, что на системе не установлен или отключён Bonjour (служба от Apple, используемая для mDNS).
👉 Возможные варианты:
- Установить Bonjour Print Services от Apple (или он автоматически ставится вместе с iTunes / iCloud).
- Убедиться, что служба Bonjour Service (mDNSResponder.exe) запущена.
-
Настройки сети
- mDNS работает через UDP порт 5353 на адрес мультикаста 224.0.0.251.
- Нужно, чтобы брандмауэр Windows и антивирусы не блокировали этот порт.
- В свойствах сетевого адаптера должно быть включено обнаружение сети.
-
Проверка
После установки и запуска службы можно проверить доступность:
ping matchpad.localили
nslookup matchpad.local -
Посмотреть IP-адрес на матч-контроллере
Чтобы узнать IP-адреса, выданные контроллеру, откройте настройки подключения Wi-Fi, нажав на строку состояния. При этом не обязательно подключаться к Wi-Fi — на этой странице будут перечислены все выданные адреса.
UDP Multicast
Один из простых способов получать актуальную информацию о ходе игры — подписаться на мультикаст-рассылку по протоколу UDP.
- Адрес:
232.10.11.12 - Порт:
55000 - Формат данных: текстовый JSON в кодировке UTF-8
Общая структура пакета
| Поле | Обязательное | Описание |
|---|---|---|
event |
да | Тип события (например, "tick") |
data |
да | Данные события (структура описана ниже) |
Ниже приводятся типы событий и форматы данных для каждого из них.
Событие "tick" - время
- Описание: Обновление текущего времени таймера.
- Частота срабатывания:
- Таймер остановлен: 1 раз в секунду
- Таймер запущен: 10 раз в секунду
| Поле | Обязательное | Значение | Пример |
|---|---|---|---|
kind |
да | тип таймера:primary - основной secondary - дополнительный |
"kind": "primary" |
view |
да | текстовое представление таймера на табло | "view": "09:48.3" |
isRunning |
да | true - таймер запущенfalse - таймер остановлен |
"isRunning": false |
isRestTimer |
нет | true - таймер перерываfalse - игровой таймер |
"isRestTimer": false |
elapsed |
нет | Прошедшее время с момента старта в мс | "elapsed": 2347 |
timestamp |
да | Unix-время отправки данного сообщения | "timestamp": 1759405394646 |
name |
нет | Имя таймера | "name": "quarter" |
homePenalties |
нет | Массив удаленных команды Хозяев:number - номер удаленногоtimeLeft - оставшееся время |
"homePenalties": [{ "number": "12", "timeLeft": "1:21" }] |
awayPenalties |
нет | Массив удаленных команды Гостей:number - номер удаленногоtimeLeft - оставшееся время |
"awayPenalties": [{ "number": "8", "timeLeft": "0:13" }] |
shortClock |
нет | 24-секундыvalue - значениеisRunning - активно |
"shortClock": { "value": "18", isRunning: true } |
nibusId |
нет | NiBUS-идентификатор таймера | "nibusId": 1 |
Событие "scoreboard" - данные на табло
- Описание: Обновление информации на табло (счёт, фолы, тайм-ауты и т.д.).
- Частота срабатывания: при изменении состояния, но не реже 1 раза каждые 30 секунд
| Поле | Обязательное | Значение | Пример |
|---|---|---|---|
sportSlug |
да | Вид спорта | "sportSlug": "basketball" |
home |
да | Команда Хозяев | "home": {"score":0,"fouls":0,"name":"ЦСКА","town":"Москва","teamId":"n7hitgakhv1kxhvy89ilvhdp","timeouts":0} |
home.score |
да | Очки команды Хозяев | "score": 0 |
home.fouls |
да | Фолы команды Хозяев | "fouls": 0 |
home.name |
да | Название команды Хозяев | "name": "ЦСКА" |
home.town |
да | Город команды Хозяев | "town": "Москва" |
home.teamId |
нет | Идентификатор команды Хозяев | "teamId": "n7hitgakhv1kxhvy89ilvhdp" |
home.timeouts |
да | Тайм-ауты команды | "timeouts": 0 |
home.lineup |
нет | Массив номеров игроков на площадке | "lineup": ["11", "14", "22", "31", "44"] |
home.filedOutPlayers |
нет | Массив номеров удаленных до конца игры | "filedOutPlayers": ["33"] |
home.lastServerNumber |
нет | В волейболе номер последнего подающего игрока | "lastServerNumber": "12" |
home.rotationCount |
нет | В волейболе количество ротаций от начальной расстановки | "rotationCount": 5 |
home.substitutionsCount |
нет | Количество замен | "substitutionsCount": 3 |
away |
да | Команда Гостей, аналогично команде Хозяев | "away": {"score":0,"fouls":0,"name":"УНИКС","town":"Казань","teamId":"lxf3lhv3vc6byoj7q5yxqoqb","lineup":["2","4","5","12","15"],"timeouts":0} |
period |
да | Период | "period": "OT" |
tournament |
нет | Название турнира | "tournament": "Единая лига ВТБ" |
competitionId |
нет | Идентификатор встречи | "competitionId":"abs6b7s802kzra2gy6jed4km" |
setScores |
нет | Счет по партиям | "setScores": [{"home": 21, "away": 19 }, {"home": 13, "away": 21 }] |
Событие "updateLogo" - обновить логотип команды на табло
С помощью REST API можно загрузить логтип команды или фото игрока по его идентификатору используя URL http://matchpad.local/media/{mediaId}
| Поле | Обязательное | Значение | Пример |
|---|---|---|---|
team |
да | Команда: home - Хозяев away - Гостей |
"team": "home" |
primaryColor |
нет | Основной цвет команды в hex | "primaryColor": "#e31024" |
secondaryColor |
нет | Дополнительный цвет команды в hex | "secondaryColor": "#cc1b51" |
textColor |
нет | Цает текста в hex | "textColor": "#ffffff" |
logo |
нет | Логотип команды | "logo":{"id":"gv65tvb1tsnnghaowjfzsyis","width":143,"height":80} |
logo.id |
да | идентификатор логотипа | "id": "gv65tvb1tsnnghaowjfzsyis" |
logo.width |
нет | Ширина логотипа в пикселях | "width": 143 |
logo.height |
нет | Высота логотипа в пикселях | "height": 80 |
Событие "goalScoredBy" - гол забил
- Описание: Отправляется при взятии ворот, содержит информацию об игроке, забившем гол, и при необходимости — об ассистентах. Используется для вывода сообщения на табло.
- Виды спорта: Все, где фиксируются голы (футбол, хоккей, гандбол и т. п.).
- Частота срабатывания: по факту события (не периодически).
| Поле | Обязательное | Значение | Пример |
|---|---|---|---|
teamKind |
да | Команда: home - Хозяев away - Гостей |
"teamKind": "away" |
number |
да | Номер игрока | "number": "00" |
name |
нет | Имя и Фамилия игрока | "name": "Александр Иванов" |
mediaId |
нет | Идентификатор фото игрока | "mediaId": "gv65tvb1tsnnghaowjfzsyis" |
team |
нет | Сведения о команде | "team": { "name": "Дружба", "town": "Простоквашино", "logo": "lxf3lhv3vc6byoj7q5yxqoqb" } |
team.name |
да | Название команды | "name": "Дружба" |
team.town |
нет | Родной город команды | "town": "Простоквашино" |
team.logo |
нет | Идентификатор логотипа команды | "logo": "gv65tvb1tsnnghaowjfzsyis" |
Событие "playerStateChanged" - обновленная статистика игрока
- Описание: Передаёт обновлённые данные по отдельному игроку: очки, фолы, количество голов, штрафы и т. д.
- Виды спорта: Все командные виды, где ведётся индивидуальная статистика (например, баскетбол).
- Частота срабатывания: при каждом изменении показателей игрока (например, добавлен гол, фол, очко и т. п.).
| Поле | Обязательное | Значение | Пример |
|---|---|---|---|
team |
да | Команда: home - Хозяев away - Гостей |
"team": "home" |
number |
нет | Номер игрка | "number": "27" |
points |
нет | Общее количество очков | "points": 25 |
fouls |
нет | Общее количество фолов | "fouls": 2 |
status |
нет | Статус игрока | "status": "disqualified" |
Пример UDP-клиента на python
👉 Если не приходят пакеты, попробуйте заменить IFACE IP-адресом вашего компьютера из одной сети с матч-контроллером.
# udp_client_async.py
import asyncio
import socket
import struct
import json
import sys
MCAST_GRP = "232.10.11.12"
MCAST_PORT = 55000
BUFFER_SIZE = 65536
# Укажите здесь IP вашего локального интерфейса.
# Например: "192.168.1.42" или оставьте "0.0.0.0" (INADDR_ANY).
IFACE = "0.0.0.0"
class MulticastProtocol(asyncio.DatagramProtocol):
def connection_made(self, transport):
self.transport = transport
sock = transport.get_extra_info("socket")
print("transport ready, sock:", sock.getsockname())
def datagram_received(self, data, addr):
try:
message = json.loads(data.decode("utf-8"))
event_type = message.get("event", "unknown")
event_data = message.get("data", message)
print(f"📩 Новое событие: {event_type} | data={event_data}")
except Exception as e:
print("⚠️ Ошибка парсинга/обработки пакета:", e, "raw:", data)
def error_received(self, exc):
print("❌ Ошибка UDP:", exc)
def connection_lost(self, exc):
print("❌ Соединение закрыто", exc)
async def main():
loop = asyncio.get_running_loop()
# Создаем сокет и настраиваем опции до передачи в asyncio
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# На macOS/новых Linux полезно разрешить REUSEPORT (если доступно)
if hasattr(socket, "SO_REUSEPORT"):
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
except OSError:
pass
# Привязываемся к порту на всех интерфейсах
sock.bind(("", MCAST_PORT))
# Подписываемся на мультикаст
try:
# Для macOS/Unix предпочтительнее 4s4s: group + local_if
mreq = socket.inet_aton(MCAST_GRP) + socket.inet_aton(IFACE)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
except Exception as e:
print("⚠️ Не удалось присоединиться к мультикаст-группе:", e)
print(" Попробуй поставить IFACE = реальный IP интерфейса (например, ipconfig getifaddr en0).")
# не выходим — иногда join не обязателен для теста
sock.setblocking(False)
transport, protocol = await loop.create_datagram_endpoint(
lambda: MulticastProtocol(),
sock=sock
)
print(f"🔌 Подключение к UDP Multicast {MCAST_GRP}:{MCAST_PORT} (IFACE={IFACE})...")
try:
# держим соединение бесконечно
await asyncio.Event().wait()
except asyncio.CancelledError:
pass
except KeyboardInterrupt:
print("\n❌ Остановка клиента...")
finally:
transport.close()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
sys.exit(0)
Server-Sent Events (SSE)
Ещё один способ получать актуальную информацию — это подписка на события по протоколу SSE по адресу:
http://matchpad.local/api/v1/events
Вместо имени сервера matchpad.local можно использовать IP-адрес матч-контроллера.
Подписку можно проверить прямо в браузере, просто открыв указанную ссылку.
Протокол SSE идеально подходит для браузерных клиентов — например, для вывода информации о матче на HTML-странице. Именно этот механизм используется встроенным веб-сервером для отображения данных на видеотабло и SmartTV.
Пример открытой ссылки в браузере
event: scoreboard
data: {"sportSlug":"basketball","home":{"score":0,"fouls":0,"name":"ЦСКА","town":"Москва","teamId":"n7hitgakhv1kxhvy89ilvhdp","lineup":["2","8","9","11","41"],"timeouts":0},"away":{"score":0,"fouls":0,"name":"УНИКС","town":"Казань","teamId":"lxf3lhv3vc6byoj7q5yxqoqb","lineup":["2","4","5","12","15"],"timeouts":0},"period":"1","tournament":"Единая лига ВТБ","competitionId":"abs6b7s802kzra2gy6jed4km"}
event: updateLogo
data: {"team":"home","logo":{"id":"af5t865jqxyoebsot7jgil9t","width":147,"height":216},"primaryColor":"#d61a20","secondaryColor":"#003274","textColor":"#ffffff"}
event: updateLogo
data: {"team":"away","logo":{"id":"y2dny9a6jmj4zar666a0gtij","width":1820,"height":1820},"primaryColor":"#2faf6b","secondaryColor":"#d36d14","textColor":"#ffffff"}
event: tick
data: {"kind":"primary","view":"09:57.3","isRunning":false,"elapsed":2727,"timestamp":1759494388845,"name":"quarter","nibusId":1}
👉 Формат передаваемых данных полностью совпадает с форматом, описанным в разделе UDP Multicast.
Пример SSE-клиента на python
# sse_client_async.py
import asyncio
import aiohttp
import json
SSE_URL = "http://matchpad.local/api/v1/events"
async def sse_client(url):
while True:
try:
print("🔌 Подключение к SSE...")
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
if resp.status != 200:
raise Exception(f"Ошибка подключения: {resp.status}")
event_type = "message"
data_buffer = []
async for line_bytes in resp.content:
line = line_bytes.decode("utf-8").strip()
if line.startswith("event:"):
event_type = line[6:].strip()
elif line.startswith("data:"):
data_buffer.append(line[5:].strip())
elif line == "":
# пустая строка — конец события
if data_buffer:
raw_data = "\n".join(data_buffer)
try:
data = json.loads(raw_data)
except json.JSONDecodeError:
data = raw_data
print(f"📩 Новое событие: {event_type} | data={data}")
# сброс
event_type = "message"
data_buffer = []
except Exception as e:
print("❌ Ошибка SSE:", e)
await asyncio.sleep(5)
print("♻️ Переподключение...")
async def main():
await sse_client(SSE_URL)
if __name__ == "__main__":
asyncio.run(main())
REST API
Полная справка в формате openapi/swagger по REST API доступна при подключении к матч-контроллеру с помощью браузера по адресу http://matchpad.local/api-docs. Вместо matchapd.local можно указать IP-адрес матч-контроллера.
Протокол NiBUS
NiBUS — проприетарный промышленный протокол фирмы Ната-Инфо. Скачать описание протокола NiBUS.pdf
👉 Полное описание протокола выходит за рамки данной публикации. Здесь рассмотрены только ключевые особенности, важные для интеграции матч-контроллера.
Особенностью протокола NiBUS является использование специальных двухбайтовых маркеров (служебных последовательностей), которые передаются без паузы между байтами и используются для управления доступом к каналу RS-485. Некоторые "умные" контроллеры и преобразователи, например устройства MOXA, могут некорректно обрабатывать такие маркеры из-за особенностей внутренней буферизации и автоматического управления направлением передачи, что приводит к искажению данных (“мусору”). Для стабильной работы рекомендуется использовать трансиверы, разработанные Ната-Инфо — Siolynx2 и C22. Скорость подключения к Siolynx2 — 115200 бод, для остальных устройств — 57600 бод.
Каждый кадр данных начинается с преамбулы 0x7e, затем идут адреса получателя и отправителя, сервисные поля, но первое, что имеет значение для анализа — это размер N
полученного кадра, который расположен в байте со смещением 14 байт от начала кадра. После определения размера можно вычислить окончание кадра (17 + N), рассчитать контрольную сумму и сверить её с полученными данными.
Формат кадра
| Поле | Смещение в байтах | Значение/Фильтр |
|---|---|---|
| Преамбула | 0 | 0x7e |
| ... | Адреса и сервисные поля | |
| Размер | 14 | Размер кадра N |
| Тип кадра | 15 | Оставляем только со значением 0x01 |
| Тип сервиса | 16 | Оставляем только со значением 0x18 |
| ID | 17 | Идентификатор ID (см. ниже) |
| Длина nms | 18 | Длина данных nms M |
| Тип данных | 19 | Тип данных TYPE (см. ниже) |
| Данные | 20..[19+M] | Данные DATA |
| CRC | [15 + N]..[15+(N+1)] | Контрольная сумма (big-endian) |
Стандартные типы данных
| Тип | Значение | Размер | Описание |
|---|---|---|---|
| VT_BOOL | 0x0b | 8 бит | Значение TRUE = 1/FALSE = 0 |
| VT_I1 | 0x10 | 8 бит | Знаковый байт |
| VT_I2 | 0x02 | 16 бит | Знаковое короткое целое |
| VT_I4 | 0x03 | 32 бита | Знаковое целое |
| VT_I8 | 0x14 | 64 бита | Знаковое длинное целое |
| VT_UI1 | 0x11 | 8 бит | Байт |
| VT_UI2 | 0x12 | 16 бит | Короткое целое |
| VT_UI4 | 0x13 | 32 бита | Целое |
| VT_UI8 | 0x15 | 64 бита | Длинное целое |
| VT_R4 | 0x04 | 32 бита | Значение с плавающей точкой |
| VT_R8 | 0x05 | 64 бита | Значение с плавающей точкой удвоенной точности |
| VT_LPSTR | 0x1e | Строка символов с терминирующим нулем | |
| VT_DATE | 0x07 | 80 бит | Дата/время в формате BCD |
| DD-MM-YYYY HH:MM:SS.0mmmbW | |||
| DD – дата | |||
| MM – месяц | |||
| YYYY – год | |||
| HH – час (0..23) | |||
| MM – минуты | |||
| SS – секунды | |||
| mmm – миллисекунды | |||
| W – день недели (1..7, 1 – вс, 2 – пн, … 7 – сб) | |||
| b – зарезервировано | |||
| VT_VECTOR | 0x80 | Модификатор типа. Означает вектор типизированных элементов |
Контрольная сумма
Циклическая контрольная сумма подсчитывается (не включает преамбулу) по стандарту CRC16-ССITT. Делитель равен 1 0001 0000 0010 0001 = 0x1021. Начальное значение 0x0000. Хранится в формате big-endian.
Детализация информационных сообщений
👉 Вся текстовая информация в сообщениях представлена кодировкой Windows-1251, бинарные данные используют порядок байт (endianness) little-endian, формат BCD означает binary-coded decimal (например 3810 хранится и передается как байт 0x38) .
Время
ID: 0x05
TYPE: VT_UI1 | VT_VECTOR (0x91)
DATA:
+0 байт - флаги:
| бит | название | описание |
|---|---|---|
| 0 | TFM_ACTIVE | таймер активизирован |
| 1 | TFM_DOTS | включить точки между часами и минутами (используется в основном для показа реального времени) |
| 2 | TFM_REST_TIMER | таймер отсчитывает время перерыва |
| 3 | TFM_MILLS_IMPORTANT | необходимо показать миллисекунды |
| 4 | TFM_MILLIS_10RESOLUTION | разрешение миллисекунд 1/10 |
| 5 | TFM_HIDDEN | не отображать таймер |
| 6 | TFM_SECONDARY | не отображать данный таймер на знакоместах основного времени |
+1 байт - идентификатор таймера:
для баскетбола:
| Значение | Таймер |
|---|---|
| 0x01 | игровой таймер |
| 0x02 | перерыв |
| 0x03 | время до начала матча |
| 0x04 | тайм-аут |
| 0x05 | овертайм |
| 0x06 | перерыв овертайма |
| 0x07 | короткий перерыв |
| 0x20 | время атаки (24c) |
для волейбола:
| Значение | Таймер |
|---|---|
| 0x01 | перерыв |
| 0x02 | тайм-аут |
| 0x03 | технический тайм-аут |
для гандбола:
| Значение | Таймер |
|---|---|
| 0x01 | игровой таймер |
| 0x02 | перерыв |
| 0x03 | время до начала матча |
| 0x04 | тайм-аут |
| 0x05 | овертайм |
| 0x06 | перерыв овертайма |
| 0x07 | короткий перерыв |
для мини-футбола (футзал):
| Значение | Таймер |
|---|---|
| 0x01 | игровой таймер |
| 0x02 | перерыв |
| 0x03 | время до начала матча |
| 0x04 | тайм-аут |
для хоккея:
| Значение | Таймер |
|---|---|
| 0x01 | игровой таймер |
| 0x02 | перерыв |
| 0x03 | время до начала матча |
| 0x04 | тайм-аут |
| 0x05 | овертайм |
| 0x06 | перерыв овертайма |
+3 байта - минуты (BCD)
+4 байта - секунды (BCD)
+5 байт - доли секунд (BCD)
Счет команды хозяев
ID: 0x06
TYPE: VT_UI2 (0x12)
DATA: +0..1 байт - значение счета
Счет команды гостей
ID: 0x07
TYPE: VT_UI2 (0x12)
DATA: +0..1 байт - значение счета
Период
ID: 0x08
TYPE: VT_UI1 (0x11)
DATA: +0 байт - значение периода
Фолы команды хозяев
ID: 0x09
TYPE: VT_UI1 (0x11)
DATA: +0 байт - значение
Фолы команды гостей
ID: 0x0a
TYPE: VT_UI1 (0x11)
DATA: +0 байт - значение
Перерывы команды хозяев
ID: 0x0e
TYPE: VT_UI1 (0x11)
DATA: +0 байт - значение
Перерывы команды гостей
ID: 0x0f
TYPE: VT_UI1 (0x11)
DATA: +0 байт - значение
Количество игроков в команде
ID: 0x10
TYPE: VT_UI1 | VT_VECTOR (0x91)
DATA:
+0 байт - команда:
| Значение | Команда |
|---|---|
| 0x00 | Хозяева |
| 0x01 | Гости |
| 0x02 | Судьи |
+1..2 байт - количество игроков
Информация о игроке
ID: 0x11
TYPE: VT_UI1 | VT_VECTOR (0x91)
DATA:
+0 байт - команда:
| Значение | Команда |
|---|---|
| 0x00 | Хозяева |
| 0x01 | Гости |
| 0x02 | Судьи |
+1 байт - зарезервировано
+2 байт - индекс игрока (начинается с 0)
+3 байт - номер игрока (BCD)
+4 байт - роль игрока:
| Значение | Роль |
|---|---|
| 0x00 | Основной состав |
| 0x01 | Запасной |
| 0x02 | Капитан |
| 0x03 | Тренер |
+5 байт - фамилия (не более 30 знаков, терминированная 0), страна (не более 20 знаков), допинформация (терминированная 0)
Пример: Петров А.\0РОССИЯ\0\0
Статистика по игроку
ID: 0x12
TYPE: VT_UI1 | VT_VECTOR (0x91)
DATA:
+0 байт - команда:
| Значение | Команда |
|---|---|
| 0x00 | Хозяева |
| 0x01 | Гости |
| 0x02 | Судьи |
+1 байт - номер игрока (BCD)
+2 байт - индекс игрока (начинается с 0)
+3 байт - суммарное число очков (BCD)
+4 байт - cуммарное число фолов (BCD)
+5 байт - статус игрока:
| Бит | Статус |
|---|---|
| 0 | Находится на площадке |
| 1 | В пакете присутствует статистика по броскам |
Опционально:
+6 байт - количество 1-очковых бросков игрока (BCD)
+7 байт - количество 2-очковых бросков игрока (BCD)
+8 байт - количество 3-очковых бросков игрока (BCD)
Название команды хозяев
ID: 0x13
TYPE: VT_LPSTR (0x1e)
DATA: +0..M-1 байт - название команды, терминированное 0
Название команды гостей
ID: 0x14
TYPE: VT_LPSTR (0x1e)
DATA: +0..M-1 байт - название команды, терминированное 0
Город/страна команды хозяев
ID: 0x15
TYPE: VT_LPSTR (0x1e)
DATA: +0..M-1 байт - город/страна, терминированное 0
Город/страна команды гостей
ID: 0x16
TYPE: VT_LPSTR (0x1e)
DATA: +0..M-1 байт - город/страна, терминированное 0
Название турнира
ID: 0x17
TYPE: VT_LPSTR (0x1e)
DATA: +0..M-1 байт - название турнира, терминированное 0
Указатель владения мячом
ID: 0x18
TYPE: VT_UI1 (0x11)
DATA: +0 байт - значение
| Значение | Указатель |
|---|---|
| 0x00 | Погашен |
| 0x01 | Хозяева |
| 0x02 | Гости |
Вывод информационного сообщения
ID: 0x19
TYPE: VT_UI1 | VT_VECTOR (0x91)
DATA:
+0 байт - тип сообщения:
| Значение | Тип |
|---|---|
| 0x0b | мяч забросил |
| 0x0c | фол |
+1..M-1 байт - фаимлия игрока (не более 30 знаков, терминированная 0), номер игрока (не более 2 знаков), название команды (не более 20 знаков), допинформация (терминированная 0)
Пример: Петров А.\012\0ЦСКА\0\0
Переключение провайдера игры (смена вида спорта)
ID: 0x1b
TYPE: VT_UI2 (0x12)
DATA: +0..1 байт - вид спорта
| Значение | Вид спорта |
|---|---|
| 0x0000 | хоккей |
| 0x0001 | баскетбол |
| 0x0002 | волейбол |
| 0x0003 | гандбол |
| 0x0004 | футзал |
Статистика по удалениям (Гандбол/Мини-футбол/Хоккей)
ID: 0x1c
TYPE: VT_UI1 | VT_VECTOR (0x91)
DATA:
+0 байт - количество строк (всего позиций 2*N, для 2х команд)
Строка 1 (хозяева)
+1 байт - флаги:
| Бит | Флаг |
|---|---|
| 0 | данная строка валидна |
| 1 | 1 - время удаления активно, 0 - отложенный штраф |
+2 байт - номер игрока (BCD)
+3 байт - минуты (BCD)
+4 байт - секунды (BCD)
+5 байт - зарезервировано
Всего N строк для хозяев, затем N строк для гостей: 5*(2*N) байт.
Статистика "Волейбол"
ID: 0x1d
TYPE: VT_UI1 | VT_VECTOR (0x91)
DATA:
+0 байт - счет команды хозяев в 1-партии
+1 байт - счет команды хозяев в 2-партии
+2 байт - счет команды хозяев в 3-партии
+3 байт - счет команды хозяев в 4-партии
+4 байт - счет команды хозяев в 5-партии
+5 байт - счет команды гостей в 1-партии
+6 байт - счет команды гостей в 2-партии
+7 байт - счет команды гостей в 3-партии
+8 байт - счет команды гостей в 4-партии
+9 байт - счет команды гостей в 5-партии
Количество замен в команде (Волейбол)
ID: 0x1e
TYPE: VT_UI1 | VT_VECTOR (0x91)
DATA:
+0 байт - команда:
| Значение | Команда |
|---|---|
| 0x00 | Хозяева |
| 0x01 | Гости |
+1 байт - количество замен
Пример NiBUS-парсера на Python
# nibus_parser.py
import serial
import struct
import sys
# Настройки порта
SERIAL_PORT = "COM3" # Измените на ваш порт (например, "/dev/ttyUSB0" на Linux/macOS)
BAUD_RATE = 57600 # Укажите 115200, если используете Siolynx2
# Константы протокола
PREAMBLE = 0x7E
FRAME_TYPE = 0x01
SERVICE_TYPE = 0x18
# Типы данных NiBUS
VT_TYPES = {
0x0B: ("VT_BOOL", 1),
0x10: ("VT_I1", 1),
0x02: ("VT_I2", 2),
0x03: ("VT_I4", 4),
0x14: ("VT_I8", 8),
0x11: ("VT_UI1", 1),
0x12: ("VT_UI2", 2),
0x13: ("VT_UI4", 4),
0x15: ("VT_UI8", 8),
0x04: ("VT_R4", 4),
0x05: ("VT_R8", 8),
0x1E: ("VT_LPSTR", None), # строка переменной длины
0x07: ("VT_DATE", 10),
0x91: ("VT_UI1|VT_VECTOR", None), # вектор байтов
}
def calc_crc16(data):
"""Вычисление CRC16-CCITT (полином 0x1021, начальное значение 0x0000)"""
crc = 0x0000
for byte in data:
crc ^= byte << 8
for _ in range(8):
if crc & 0x8000:
crc = (crc << 1) ^ 0x1021
else:
crc = crc << 1
crc &= 0xFFFF
return crc
def parse_data(data_type, data_bytes):
"""Парсинг данных в зависимости от типа"""
if data_type not in VT_TYPES:
return f"Unknown type 0x{data_type:02x}", data_bytes.hex()
type_name, size = VT_TYPES[data_type]
if data_type == 0x1E: # VT_LPSTR - строка
try:
# Ищем терминирующий ноль
end = data_bytes.find(b'\x00')
if end == -1:
return type_name, data_bytes.decode('windows-1251', errors='replace')
return type_name, data_bytes[:end].decode('windows-1251', errors='replace')
except:
return type_name, data_bytes.hex()
elif data_type == 0x91: # VT_UI1|VT_VECTOR - массив байтов
return type_name, ' '.join(f'{b:02x}' for b in data_bytes)
elif data_type == 0x0B: # VT_BOOL
return type_name, bool(data_bytes[0])
elif data_type in [0x11, 0x10]: # VT_UI1, VT_I1
fmt = 'B' if data_type == 0x11 else 'b'
return type_name, struct.unpack(fmt, data_bytes[:1])[0]
elif data_type in [0x12, 0x02]: # VT_UI2, VT_I2
fmt = '<H' if data_type == 0x12 else '<h'
return type_name, struct.unpack(fmt, data_bytes[:2])[0]
elif data_type in [0x13, 0x03]: # VT_UI4, VT_I4
fmt = '<I' if data_type == 0x13 else '<i'
return type_name, struct.unpack(fmt, data_bytes[:4])[0]
elif data_type in [0x15, 0x14]: # VT_UI8, VT_I8
fmt = '<Q' if data_type == 0x15 else '<q'
return type_name, struct.unpack(fmt, data_bytes[:8])[0]
elif data_type == 0x04: # VT_R4 - float
return type_name, struct.unpack('<f', data_bytes[:4])[0]
elif data_type == 0x05: # VT_R8 - double
return type_name, struct.unpack('<d', data_bytes[:8])[0]
elif data_type == 0x07: # VT_DATE - BCD дата/время
return type_name, ' '.join(f'{b:02x}' for b in data_bytes[:10])
return type_name, data_bytes.hex()
def parse_nibus_frame(frame):
"""Парсинг кадра NiBUS"""
if len(frame) < 20:
print("⚠️ Слишком короткий кадр")
return False
# Проверка преамбулы
if frame[0] != PREAMBLE:
print(f"⚠️ Неверная преамбула: 0x{frame[0]:02x}")
return False
# Размер кадра
frame_size = frame[14]
if len(frame) < 15 + frame_size + 2:
print(f"⚠️ Неполный кадр (ожидается {15 + frame_size + 2}, получено {len(frame)})")
return False
# Тип кадра и сервиса
frame_type = frame[15]
service_type = frame[16]
if frame_type != FRAME_TYPE:
return False # Пропускаем без вывода
if service_type != SERVICE_TYPE:
return False # Пропускаем без вывода
# ID, длина данных, тип данных
msg_id = frame[17]
data_len = frame[18]
data_type = frame[19]
# Данные
data_start = 20
data_end = data_start + data_len
data_bytes = frame[data_start:data_end]
# CRC
crc_start = 15 + frame_size
crc_received = struct.unpack('<H', frame[crc_start:crc_start + 2])[0]
# Проверка CRC (без преамбулы)
crc_calculated = int.from_bytes(calc_crc16(frame[1:crc_start]).to_bytes(2, 'big'), 'little')
if crc_calculated != crc_received:
print(f"⚠️ Ошибка CRC: вычислено 0x{crc_calculated:04x}, получено 0x{crc_received:04x}")
return False
# Парсинг данных
type_name, parsed_data = parse_data(data_type, data_bytes)
# Вывод
print(f"📦 ID: 0x{msg_id:02x} | TYPE: {type_name} (0x{data_type:02x}) | DATA: {parsed_data}")
return True
def main():
print(f"🔌 Подключение к {SERIAL_PORT} на скорости {BAUD_RATE} бод...")
try:
ser = serial.Serial(
port=SERIAL_PORT,
baudrate=BAUD_RATE,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=1
)
print("✅ Порт открыт. Ожидание данных...")
buffer = bytearray()
while True:
# Читаем данные
if ser.in_waiting:
chunk = ser.read(ser.in_waiting)
buffer.extend(chunk)
# Ищем преамбулу
while len(buffer) >= 20:
preamble_pos = buffer.find(PREAMBLE)
if preamble_pos == -1:
# Преамбулы нет, очищаем буфер
buffer.clear()
break
# Удаляем мусор до преамбулы
if preamble_pos > 0:
buffer = buffer[preamble_pos:]
# Проверяем, достаточно ли данных для определения размера
if len(buffer) < 15:
break
frame_size = buffer[14]
total_size = 15 + frame_size + 2 # +2 для CRC
# Ждём полного кадра
if len(buffer) < total_size:
break
# Извлекаем кадр
frame = buffer[:total_size]
buffer = buffer[total_size:]
# Парсим кадр
parse_nibus_frame(frame)
except serial.SerialException as e:
print(f"❌ Ошибка порта: {e}")
sys.exit(1)
except KeyboardInterrupt:
print("\n❌ Остановка...")
sys.exit(0)
finally:
if 'ser' in locals() and ser.is_open:
ser.close()
print("🔌 Порт закрыт")
if __name__ == "__main__":
main()
COLOSSEO Universal protocol
Этот протокол используется, если вы подключаете табло или часы производства COLOSSEO.
📄 Скачать описание протокола:
COLOSSEO Universal Protocol.pdf
⚠️ Обратите внимание: нельзя одновременно использовать оба протокола — NiBUS и COLOSSEO.
Необходимо выбрать только один.