timer NATA Kazan - нужно проверить, возможно поправил гашение 24 секунд
This commit is contained in:
@@ -7,15 +7,52 @@ import os
|
|||||||
import time
|
import time
|
||||||
import binascii
|
import binascii
|
||||||
import requests
|
import requests
|
||||||
|
import ast
|
||||||
|
from requests.adapters import HTTPAdapter
|
||||||
|
from urllib3.util.retry import Retry
|
||||||
|
|
||||||
HOST = "192.168.127.254"
|
HOST = "192.168.127.254"
|
||||||
PORT = 1993
|
PORT = 1993
|
||||||
PATH = (
|
# --- vMix ---
|
||||||
r"D:\ГРАФИКА\БАСКЕТБОЛ\ЕДИНАЯ ЛИГА ВТБ 2022-2023\python\JSON\timer_basketball.json"
|
_VMIX_URL = "http://127.0.0.1:8088/API/"
|
||||||
)
|
_VMIX_INPUT = "SCOREBUG" # или твой номер/имя входа, если жёстко задан
|
||||||
|
_VMIX_COOLDOWN = 5.0
|
||||||
|
_vmix_suppress_until = 0.0
|
||||||
|
_vmix_online = None # None/True/False
|
||||||
|
_vmix_state = { # сюда складываем последнее известное состояние
|
||||||
|
"TIMER.Text": None,
|
||||||
|
"24sec.Text": None,
|
||||||
|
"SCORE1.Text": None,
|
||||||
|
"SCORE2.Text": None,
|
||||||
|
# при желании добавь картинки и др. поля:
|
||||||
|
# "24SECBACKGROUND.Source": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- MOXA reconnect/watchdog ---
|
||||||
|
_SOCK_RECV_TIMEOUT = 2.0 # socket recv() timeout
|
||||||
|
_IDLE_RECONNECT = 5.0 # если нет данных столько сек — реконнект
|
||||||
|
_RETRY_BACKOFF = 1.0
|
||||||
|
_RETRY_BACKOFF_MAX = 10.0
|
||||||
|
_last_rx_mono = 0.0
|
||||||
|
_attack_last = None # последнее принятое значение в секундах (float)
|
||||||
|
_attack_last_t = 0.0 # когда его приняли (monotonic)
|
||||||
|
_attack_off_t = -1.0 # когда видели OFF (monotonic)
|
||||||
|
_ATTACK_OFF_GRACE = 0.8 # окно (сек) после OFF: первое новое значение принимаем всегда
|
||||||
|
_ATTACK_UP_GLITCH = 0.3 # если выросло больше чем на 0.3 c без OFF — игнорируем
|
||||||
|
# --- анти-дребезг основного таймера ---
|
||||||
|
_hold_zero_active = False # сейчас удерживаем "0.0"
|
||||||
|
_hold_zero_t0 = 0.0 # момент начала удержания
|
||||||
|
_HOLD_ZERO_SEC = 1.0 # сколько секунд держать "0.0" (можно 0.8–1.5)
|
||||||
|
_last_timer_text = None # последнее отправленное значение
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
session = requests.Session()
|
session = requests.Session()
|
||||||
|
adapter = HTTPAdapter(
|
||||||
|
max_retries=Retry(total=0, connect=0, read=0, redirect=0, status=0)
|
||||||
|
)
|
||||||
|
session.mount("http://", adapter)
|
||||||
|
session.mount("https://", adapter)
|
||||||
session.headers.update({"Connection": "keep-alive"})
|
session.headers.update({"Connection": "keep-alive"})
|
||||||
|
|
||||||
|
|
||||||
@@ -23,141 +60,378 @@ def hexspace(string, length):
|
|||||||
return " ".join(string[i : i + length] for i in range(0, len(string), length))
|
return " ".join(string[i : i + length] for i in range(0, len(string), length))
|
||||||
|
|
||||||
|
|
||||||
def send_data(name, value):
|
def _vmix_resend_full_state():
|
||||||
url = "http://127.0.0.1:8088/API/"
|
"""Переотправляет ВСЕ последние значения, когда vMix вернулся онлайн."""
|
||||||
par = "SetText" if name.split(".")[1] == "Text" else "SetImage"
|
for name, val in _vmix_state.items():
|
||||||
|
if val is None:
|
||||||
|
continue
|
||||||
|
_send_to_vmix(name, val)
|
||||||
|
|
||||||
|
|
||||||
|
def _send_to_vmix(name: str, value):
|
||||||
|
"""Низкоуровневый вызов vMix API (без защиты)."""
|
||||||
|
try:
|
||||||
|
kind = name.split(".")[1]
|
||||||
|
except Exception:
|
||||||
|
kind = "Text"
|
||||||
|
par = "SetText" if kind == "Text" else "SetImage"
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
"Function": par,
|
"Function": par,
|
||||||
"Input": 33,
|
"Input": _VMIX_INPUT,
|
||||||
"SelectedName": name,
|
"SelectedName": name,
|
||||||
"Value": value,
|
"Value": value,
|
||||||
}
|
}
|
||||||
session.get(url, params=params)
|
# короткий таймаут, чтобы не блокировать
|
||||||
|
session.get(_VMIX_URL, params=params, timeout=0.25)
|
||||||
|
|
||||||
|
|
||||||
|
def send_data(name: str, value):
|
||||||
|
"""
|
||||||
|
«Мягкая» отправка в vMix: не роняем процесс при оффлайне,
|
||||||
|
вводим cooldown, и при появлении онлайна переотправляем весь стейт.
|
||||||
|
"""
|
||||||
|
global _vmix_suppress_until, _vmix_online
|
||||||
|
|
||||||
|
# запоминаем последнее значение
|
||||||
|
_vmix_state[name] = value
|
||||||
|
|
||||||
|
now = time.monotonic()
|
||||||
|
if now < _vmix_suppress_until:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
_send_to_vmix(name, value)
|
||||||
|
# если раньше были оффлайн — объявим «online» и добьём состояние
|
||||||
|
if _vmix_online is not True:
|
||||||
|
print("[vMix] online")
|
||||||
|
_vmix_online = True
|
||||||
|
_vmix_resend_full_state()
|
||||||
|
return True
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
# оффлайн
|
||||||
|
if _vmix_online is not False:
|
||||||
|
msg = f"[vMix] offline: {e.__class__.__name__}: {e}"
|
||||||
|
print(msg)
|
||||||
|
_vmix_online = False
|
||||||
|
_vmix_suppress_until = now + _VMIX_COOLDOWN
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def parse_new(line):
|
def parse_new(line):
|
||||||
try:
|
global _attack_last, _attack_last_t, _attack_off_t
|
||||||
with open(PATH, "r", encoding="utf-8") as f:
|
global _hold_zero_active, _hold_zero_t0, _last_timer_text
|
||||||
new_data = json.load(f)
|
|
||||||
except json.decoder.JSONDecodeError:
|
|
||||||
new_data = [
|
|
||||||
{
|
|
||||||
"timeGFX": "0:00",
|
|
||||||
"time_attackGFX": "",
|
|
||||||
"quarter": "0",
|
|
||||||
"points1": "0",
|
|
||||||
"points2": "0",
|
|
||||||
"foul1": "0",
|
|
||||||
"foul2": "0",
|
|
||||||
"foul_pic1": "",
|
|
||||||
"foul_pic2": "",
|
|
||||||
"time_attac_pic": "",
|
|
||||||
"timeout1": "0",
|
|
||||||
"timeout2": "0",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
# Преобразуем входящие байты в читаемый HEX
|
||||||
|
if line == b"\xff":
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
cdata = binascii.hexlify(line)
|
cdata = binascii.hexlify(line)
|
||||||
ddata = cdata.decode("utf-8").upper()
|
ddata = cdata.decode("utf-8").upper()
|
||||||
edata = hexspace(ddata, 2)
|
edata = hexspace(ddata, 2)
|
||||||
temp = edata.split("FF 52")
|
except Exception:
|
||||||
# print(temp)
|
return
|
||||||
for i in temp:
|
|
||||||
if "7D 4A C0 0A" in i: #основной таймер
|
# Разделяем поток на пакеты по сигнатуре начала
|
||||||
minutes = int(i.split()[-5], )
|
parts = [seg.strip() for seg in edata.split("FF FF 51 7E") if seg.strip()]
|
||||||
seconds = int(i.split()[-4], )
|
if not parts:
|
||||||
milliseconds = int(i.split()[-3], )
|
return
|
||||||
timer_str = (
|
|
||||||
f"{minutes}:{seconds:02d}" if minutes != 0 else f"{seconds}.{milliseconds}"
|
for i in parts:
|
||||||
)
|
arr = i.split()
|
||||||
|
print(arr)
|
||||||
|
# ---------------------- 24 СЕКУНД – OFF / RESET ----------------------
|
||||||
|
if "89 4E C8 05" in i:
|
||||||
|
print(arr, "возможно гашение/включение")
|
||||||
|
send_data("24sec.Text", "")
|
||||||
|
_attack_last = None
|
||||||
|
_attack_off_t = time.monotonic()
|
||||||
|
continue
|
||||||
|
if "79 84 C0 08" in i:
|
||||||
|
print(arr, '2/4 секунды значение')
|
||||||
|
idx = None
|
||||||
|
for k in range(len(arr) - 3):
|
||||||
|
if arr[k:k+4] == ["79", "84", "C0", "08"]:
|
||||||
|
idx = k
|
||||||
|
break
|
||||||
|
if idx is None or len(arr) <= idx + 13:
|
||||||
|
continue
|
||||||
|
|
||||||
|
seconds_24 = int(arr[idx + 10])
|
||||||
|
tenths_24 = int(arr[idx + 11])
|
||||||
|
|
||||||
|
# Гашение при > 24 или 0.0
|
||||||
|
if seconds_24 > 24 or (seconds_24 == 0 and tenths_24 == 0):
|
||||||
|
send_data("24sec.Text", "")
|
||||||
|
_attack_last = None
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Формирование отображаемого значения
|
||||||
|
shown = f"{seconds_24}.{tenths_24}" if seconds_24 <= 4 else str(seconds_24)
|
||||||
|
if shown in ("0", "0.0"):
|
||||||
|
shown = ""
|
||||||
|
|
||||||
|
# Анти-рывок: не принимаем «рост» без OFF
|
||||||
|
now = time.monotonic()
|
||||||
|
new_val = seconds_24 + (tenths_24 / 10.0 if seconds_24 <= 5 else 0.0)
|
||||||
|
accept = False
|
||||||
|
|
||||||
|
if _attack_last is None:
|
||||||
|
accept = True
|
||||||
|
elif (now - _attack_off_t) <= _ATTACK_OFF_GRACE:
|
||||||
|
accept = True
|
||||||
|
else:
|
||||||
|
accept = new_val <= _attack_last + _ATTACK_UP_GLITCH
|
||||||
|
|
||||||
|
if accept:
|
||||||
|
send_data("24sec.Text", shown)
|
||||||
|
_attack_last = new_val
|
||||||
|
_attack_last_t = now
|
||||||
|
continue
|
||||||
|
if "79 84 C8 06" in i:
|
||||||
|
idx = None
|
||||||
|
for k in range(len(arr) - 3):
|
||||||
|
if arr[k:k+4] == ["79", "84", "C8", "06"]:
|
||||||
|
idx = k
|
||||||
|
break
|
||||||
|
if idx is None or len(arr) <= idx + 13:
|
||||||
|
continue
|
||||||
|
|
||||||
|
seconds_24 = int(arr[idx + 10])
|
||||||
|
tenths_24 = int(arr[idx + 11])
|
||||||
|
|
||||||
|
# Гашение при > 24 или 0.0
|
||||||
|
if seconds_24 > 24 or (seconds_24 == 0 and tenths_24 == 0):
|
||||||
|
send_data("24sec.Text", "")
|
||||||
|
_attack_last = None
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Формирование отображаемого значения
|
||||||
|
shown = f"{seconds_24}.{tenths_24}" if seconds_24 <= 4 else str(seconds_24)
|
||||||
|
if shown in ("0", "0.0"):
|
||||||
|
shown = ""
|
||||||
|
|
||||||
|
# Анти-рывок: не принимаем «рост» без OFF
|
||||||
|
now = time.monotonic()
|
||||||
|
new_val = seconds_24 + (tenths_24 / 10.0 if seconds_24 <= 5 else 0.0)
|
||||||
|
accept = False
|
||||||
|
|
||||||
|
if _attack_last is None:
|
||||||
|
accept = True
|
||||||
|
elif (now - _attack_off_t) <= _ATTACK_OFF_GRACE:
|
||||||
|
accept = True
|
||||||
|
else:
|
||||||
|
accept = new_val <= _attack_last + _ATTACK_UP_GLITCH
|
||||||
|
|
||||||
|
if accept:
|
||||||
|
send_data("24sec.Text", shown)
|
||||||
|
_attack_last = new_val
|
||||||
|
_attack_last_t = now
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ---------------------- 24 СЕКУНД – VALUE ----------------------
|
||||||
|
if "79 84 C0 0A" in i:
|
||||||
|
# print(arr, '24 секунды значение')
|
||||||
|
idx = None
|
||||||
|
for k in range(len(arr) - 3):
|
||||||
|
if arr[k:k+4] == ["79", "84", "C0", "0A"]:
|
||||||
|
idx = k
|
||||||
|
break
|
||||||
|
if idx is None or len(arr) <= idx + 13:
|
||||||
|
continue
|
||||||
|
|
||||||
|
seconds_24 = int(arr[idx + 12])
|
||||||
|
tenths_24 = int(arr[idx + 13])
|
||||||
|
# Гашение при > 24 или 0.0
|
||||||
|
if seconds_24 > 24 or (seconds_24 == 0 and tenths_24 == 0):
|
||||||
|
send_data("24sec.Text", "")
|
||||||
|
_attack_last = None
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Формирование отображаемого значения
|
||||||
|
shown = f"{seconds_24}.{tenths_24}" if seconds_24 <= 4 else str(seconds_24)
|
||||||
|
if shown in ("0", "0.0"):
|
||||||
|
shown = ""
|
||||||
|
|
||||||
|
# Анти-рывок: не принимаем «рост» без OFF
|
||||||
|
now = time.monotonic()
|
||||||
|
new_val = seconds_24 + (tenths_24 / 10.0 if seconds_24 <= 5 else 0.0)
|
||||||
|
accept = False
|
||||||
|
|
||||||
|
if _attack_last is None:
|
||||||
|
accept = True
|
||||||
|
elif (now - _attack_off_t) <= _ATTACK_OFF_GRACE:
|
||||||
|
accept = True
|
||||||
|
else:
|
||||||
|
accept = new_val <= _attack_last + _ATTACK_UP_GLITCH
|
||||||
|
|
||||||
|
if accept:
|
||||||
|
send_data("24sec.Text", shown)
|
||||||
|
_attack_last = new_val
|
||||||
|
_attack_last_t = now
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ---------------------- ОСНОВНОЕ ВРЕМЯ ----------------------
|
||||||
|
if "7D 4A C0 0A" in i:
|
||||||
|
try:
|
||||||
|
idx = None
|
||||||
|
for k in range(len(arr) - 3):
|
||||||
|
if arr[k:k+4] == ["7D", "4A", "C0", "0A"]:
|
||||||
|
idx = k
|
||||||
|
break
|
||||||
|
if idx is None or len(arr) <= idx + 13:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def bcd_to_dec(b):
|
||||||
|
return ((b >> 4) & 0x0F) * 10 + (b & 0x0F)
|
||||||
|
|
||||||
|
# секунды и минуты
|
||||||
|
seconds_b = int(arr[idx + 12], 16)
|
||||||
|
minutes_b = int(arr[idx + 11], 16)
|
||||||
|
|
||||||
|
# отбросить флаговые старшие биты минут
|
||||||
|
minutes_b = minutes_b & 0x0F | ((minutes_b >> 4) & 0x07) << 4
|
||||||
|
|
||||||
|
minutes = bcd_to_dec(minutes_b)
|
||||||
|
seconds_raw = bcd_to_dec(seconds_b)
|
||||||
|
|
||||||
|
# нормальный таймер
|
||||||
|
if seconds_raw < 60:
|
||||||
|
timer_calc = f"{minutes}:{seconds_raw:02d}"
|
||||||
|
else:
|
||||||
|
timer_calc = f"{minutes}.{seconds_raw - 128}"
|
||||||
|
|
||||||
|
# анти-дребезг конца периода
|
||||||
|
now = time.monotonic()
|
||||||
|
is_zero = (minutes == 0 and seconds_raw == 0)
|
||||||
|
is_two0 = (minutes == 2 and seconds_raw == 0)
|
||||||
|
|
||||||
|
if is_zero:
|
||||||
|
_hold_zero_active = True
|
||||||
|
_hold_zero_t0 = now
|
||||||
|
timer_str = "0.0"
|
||||||
|
elif _hold_zero_active and is_two0:
|
||||||
|
timer_str = "0.0"
|
||||||
|
if now - _hold_zero_t0 > _HOLD_ZERO_SEC:
|
||||||
|
_hold_zero_active = False
|
||||||
|
else:
|
||||||
|
if _hold_zero_active and (now - _hold_zero_t0 > _HOLD_ZERO_SEC):
|
||||||
|
_hold_zero_active = False
|
||||||
|
timer_str = timer_calc
|
||||||
|
|
||||||
send_data("TIMER.Text", timer_str)
|
send_data("TIMER.Text", timer_str)
|
||||||
|
_last_timer_text = timer_str
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------- СЧЁТ ----------------------
|
||||||
|
if "7D 4A C0 07" in i:
|
||||||
|
try:
|
||||||
|
idx = None
|
||||||
|
for k in range(len(arr) - 3):
|
||||||
|
if arr[k:k+4] == ["7D", "4A", "C0", "07"]:
|
||||||
|
idx = k
|
||||||
|
break
|
||||||
|
if idx is None or len(arr) <= idx + 7:
|
||||||
|
continue
|
||||||
|
|
||||||
|
score1 = int(arr[idx + 6], 16)
|
||||||
|
score2 = int(arr[idx + 7], 16)
|
||||||
|
|
||||||
# print(i.split()[-7], i)
|
|
||||||
# if i.split()[-7] == "13" or i.split()[-7] == "10":
|
|
||||||
# new_data[0]["timeGFX"] = timer_str
|
|
||||||
elif "7D 4A C0 07" in i: #Счет
|
|
||||||
if i.split()[-7] == "06": #Счет первой команды
|
|
||||||
score1 = int(i.split()[-4], 16)
|
|
||||||
# new_data[0]["points1"] = score1
|
|
||||||
send_data("SCORE1.Text", score1)
|
send_data("SCORE1.Text", score1)
|
||||||
elif i.split()[-7] == "07": #Счет второй команды
|
|
||||||
score2 = int(i.split()[-4], 16)
|
|
||||||
send_data("SCORE2.Text", score2)
|
send_data("SCORE2.Text", score2)
|
||||||
# new_data[0]["points2"] = score2
|
except Exception:
|
||||||
else:
|
continue
|
||||||
print("[СЧЁТ] == НЕПОНЯТНО")
|
|
||||||
elif "7D 4A C0 06" in i: #Информация
|
|
||||||
if i.split()[-6] == "09": #фолы первой команды
|
|
||||||
foul1 = int(i.split()[-3], 16)
|
|
||||||
# new_data[0]["foul1"] = foul1
|
|
||||||
send_data("fouls1.Source", f"D:\\Графика\\БАСКЕТБОЛ\\ЕДИНАЯ ЛИГА ВТБ 2022-2023\\Scorebug Indicators\\Away_{foul1}.png")
|
|
||||||
elif i.split()[-6] == "0A": #фолы второй команды
|
|
||||||
foul2 = int(i.split()[-3], 16)
|
|
||||||
send_data("fouls2.Source", f"D:\\Графика\\БАСКЕТБОЛ\\ЕДИНАЯ ЛИГА ВТБ 2022-2023\\Scorebug Indicators\\Away_{foul2}.png")
|
|
||||||
# new_data[0]["foul2"] = foul2
|
|
||||||
elif i.split()[-6] == "0E": #тайм-аут первой команды
|
|
||||||
time_out1 = int(i.split()[-3], 16)
|
|
||||||
# new_data[0]["timeout1"] = time_out1
|
|
||||||
elif i.split()[-6] == "0F": #тайм-аут второй команды
|
|
||||||
time_out2 = int(i.split()[-3], 16)
|
|
||||||
# new_data[0]["timeout2"] = time_out2
|
|
||||||
elif i.split()[-6] == "08": #четверть
|
|
||||||
quarter = int(i.split()[-3], 16)
|
|
||||||
# new_data[0]["quarter"] = quarter
|
|
||||||
|
|
||||||
elif "79 84 C0 0A" in i: #24 секунды
|
|
||||||
print(i)
|
|
||||||
seconds = int(i.split()[-4])
|
|
||||||
milliseconds = int(i.split()[-3])
|
|
||||||
if seconds < int(5):
|
|
||||||
time_attack = f"{seconds}.{milliseconds}"
|
|
||||||
timer_pic = "D:\\Графика\\БАСКЕТБОЛ\\ЕДИНАЯ ЛИГА ВТБ 2022-2023\\Scorebug Indicators\\24Sec_Red.png"
|
|
||||||
else:
|
|
||||||
time_attack = seconds
|
|
||||||
timer_pic = "D:\\Графика\\БАСКЕТБОЛ\\ЕДИНАЯ ЛИГА ВТБ 2022-2023\\Scorebug Indicators\\24Sec_Empty.png"
|
|
||||||
if time_attack == "0.0":
|
|
||||||
time_attack = ""
|
|
||||||
timer_pic = "D:\\Графика\\БАСКЕТБОЛ\\ЕДИНАЯ ЛИГА ВТБ 2022-2023\\Scorebug Indicators\\24Sec_Empty.png"
|
|
||||||
send_data("24sec.Text", time_attack)
|
|
||||||
# send_data("24SECBACKGROUND.Source", timer_pic)
|
|
||||||
|
|
||||||
# print(time_attack)
|
def _connect_socket():
|
||||||
elif "89 4E C8 05" in i:
|
s = socket(AF_INET, SOCK_STREAM)
|
||||||
if i.split()[-3] == "05": #таймер 24 секунд выключен
|
s.settimeout(_SOCK_RECV_TIMEOUT)
|
||||||
time_attack = ""
|
try:
|
||||||
timer_pic = "D:\\Графика\\БАСКЕТБОЛ\\ЕДИНАЯ ЛИГА ВТБ 2022-2023\\Scorebug Indicators\\24Sec_Empty.png"
|
s.setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1)
|
||||||
send_data("24sec.Text", time_attack)
|
except OSError:
|
||||||
# send_data("24SECBACKGROUND.Source", timer_pic)
|
pass
|
||||||
# data = {
|
s.connect((HOST, PORT))
|
||||||
# "TIMER.Text": timer_str,
|
s.send(b"hello") # как у тебя
|
||||||
# "ATTACK.Text": time_attack,
|
return s
|
||||||
# "Score_Home.Text": score1,
|
|
||||||
# "Score_Away.Text": score2,
|
|
||||||
# "fouls1.Source": f"D:\\Графика\\БАСКЕТБОЛ\\ЕДИНАЯ ЛИГА ВТБ 2022-2023\\Scorebug Indicators\\Home_{foul1}.png",
|
def run_client():
|
||||||
# "fouls2.Source": f"D:\\Графика\\БАСКЕТБОЛ\\ЕДИНАЯ ЛИГА ВТБ 2022-2023\\Scorebug Indicators\\Away_{foul2}.png",
|
"""Главный цикл: читает данные; при ошибке или простое — переподключается."""
|
||||||
# "24SECBACKGROUND.Source": timer_pic,
|
global _last_rx_mono, _rx_buf
|
||||||
# }
|
|
||||||
|
backoff = _RETRY_BACKOFF
|
||||||
|
s = None
|
||||||
|
_rx_buf = bytearray()
|
||||||
|
_last_rx_mono = time.monotonic()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
if s is None:
|
||||||
|
print(f"[MOXA] connecting to {HOST}:{PORT} ...")
|
||||||
|
s = _connect_socket()
|
||||||
|
print("[MOXA] connected")
|
||||||
|
backoff = _RETRY_BACKOFF
|
||||||
|
_last_rx_mono = time.monotonic()
|
||||||
|
|
||||||
|
# idle-watchdog: давно нет данных — форсим реконнект
|
||||||
|
if time.monotonic() - _last_rx_mono > _IDLE_RECONNECT:
|
||||||
|
raise TimeoutError(f"idle {time.monotonic() - _last_rx_mono:.1f}s")
|
||||||
|
|
||||||
|
try:
|
||||||
|
chunk = s.recv(1024)
|
||||||
|
if not chunk:
|
||||||
|
raise ConnectionResetError("socket closed by peer")
|
||||||
|
logging.debug(chunk)
|
||||||
|
parse_new(chunk)
|
||||||
|
except timeout:
|
||||||
|
# socket.timeout: просто следующая итерация; проверим idle
|
||||||
|
pass
|
||||||
|
|
||||||
|
except (ConnectionError, OSError, TimeoutError) as e:
|
||||||
|
if s is not None:
|
||||||
|
try:
|
||||||
|
s.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
s = None
|
||||||
|
print(f"[MOXA] reconnect in {backoff:.1f}s due to: {e}")
|
||||||
|
time.sleep(backoff)
|
||||||
|
backoff = min(backoff * 2, _RETRY_BACKOFF_MAX)
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
def read_logs():
|
def read_logs():
|
||||||
with open(
|
path = r"C:\Code\timer\LOGS\timer_Nata_Nport_2025-04-25_14-53-51.log"
|
||||||
r"C:\Users\soule\Downloads\Telegram Desktop\timer_Megasport_Nport_2024-03-05_20-00-17.log",
|
# path = r"C:\Code\timer\LOGS\timer_Nata_Nport_2025-04-25_15-15-42.log"
|
||||||
"r",
|
with open(path, "r", encoding="utf-8", errors="ignore") as file:
|
||||||
) as file:
|
|
||||||
for line in file:
|
for line in file:
|
||||||
parts = line.strip().split(" DEBUG ")
|
parts = line.strip().split(" DEBUG ", 1)
|
||||||
if len(parts) == 2:
|
if len(parts) != 2:
|
||||||
timestamp = parts[0][1:]
|
continue
|
||||||
data = eval(parts[1])
|
|
||||||
if b"\xf83" in data or b"\xf88" in data or b"\xf87" in data:
|
payload = parts[1].strip()
|
||||||
parse(data)
|
|
||||||
time.sleep(0.1)
|
# читаем только байтовые строки
|
||||||
|
if payload.startswith("b'") or payload.startswith('b"'):
|
||||||
|
try:
|
||||||
|
data = ast.literal_eval(payload)
|
||||||
|
if isinstance(data, (bytes, bytearray)):
|
||||||
|
parse_new(bytes(data))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
time.sleep(0.005)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
||||||
if not os.path.isdir("LOGS"):
|
if not os.path.isdir("LOGS"):
|
||||||
os.mkdir("LOGS")
|
os.mkdir("LOGS")
|
||||||
|
|
||||||
LogFileName = f"LOGS/timer_Nata_Nport_{current_time}.log"
|
LogFileName = "LOGS/timer_SARATOV.log"
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.DEBUG,
|
level=logging.DEBUG,
|
||||||
format="%(asctime)s %(levelname)s %(message)s",
|
format="%(asctime)s %(levelname)s %(message)s",
|
||||||
@@ -166,24 +440,11 @@ def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tcp_socket = socket(AF_INET, SOCK_STREAM)
|
# run_client() # ← онлайн-режим с авто-реконнектом
|
||||||
tcp_socket.connect((HOST, PORT))
|
read_logs() # ← оставь для оффлайн-прогона логов (ручной переключатель)
|
||||||
data = str.encode("hello")
|
|
||||||
tcp_socket.send(data)
|
|
||||||
data = bytes.decode(data)
|
|
||||||
while True:
|
|
||||||
data = tcp_socket.recv(1024)
|
|
||||||
parse_new(data)
|
|
||||||
logging.debug(data)
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
tcp_socket.close()
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
|
||||||
main()
|
main()
|
||||||
# read_logs()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
sys.exit(1)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user