temptours+tours
This commit is contained in:
402
chronotrack_server_au.py
Normal file
402
chronotrack_server_au.py
Normal file
@ -0,0 +1,402 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import socket
|
||||
import threading
|
||||
import queue
|
||||
import time
|
||||
import sys
|
||||
import datetime
|
||||
import binascii # laissé si tu en as besoin plus tard, ça garde le diff minimal
|
||||
|
||||
from mysql_writer import write_chrinfo, write_log
|
||||
|
||||
# ===== Debug =====
|
||||
DEBUG = False # activé via paramètre --debug
|
||||
|
||||
# ===== Config =====
|
||||
HOST = "0.0.0.0"
|
||||
PORT = 20201
|
||||
PROGRAM_NAME = "MS1Server"
|
||||
PROGRAM_VERSION = "2.0-TCP"
|
||||
STREAM_MODE = "push"
|
||||
TIME_FORMAT = "unix"
|
||||
LOG_FILE = "chronotrack.log"
|
||||
|
||||
# Credentials plaintext
|
||||
AUTH_USER = "progiweb"
|
||||
AUTH_PASS = "Airstream"
|
||||
|
||||
# ===== État global =====
|
||||
running = True
|
||||
contexts = {} # contexte par IP de feed (logique CTP01)
|
||||
requested_evt = set()
|
||||
packet_q = queue.Queue()
|
||||
|
||||
# Gestion des clients TCP
|
||||
clients_lock = threading.Lock()
|
||||
client_id_seq = 0 # compteur pour attribuer un id lisible à chaque connexion
|
||||
last_client = None # dernier client ayant envoyé quelque chose (pour la console)
|
||||
|
||||
server_sock = None # socket d'écoute TCP
|
||||
|
||||
|
||||
def now_iso():
|
||||
return datetime.datetime.now().isoformat(timespec="seconds")
|
||||
|
||||
|
||||
def send_line(message: str, client: dict):
|
||||
"""
|
||||
Envoie UNE LIGNE CTP01 sur la connexion TCP du client.
|
||||
client = {"id": int, "conn": socket, "addr": (ip, port)}
|
||||
"""
|
||||
msg = (message.strip() + "\r\n").encode("utf-8", errors="ignore")
|
||||
try:
|
||||
client["conn"].sendall(msg)
|
||||
if DEBUG:
|
||||
print(f"📤 Envoyé → [C{client['id']}] {client['addr']}: {message}")
|
||||
except Exception as e:
|
||||
if DEBUG:
|
||||
print(f"❌ Erreur send_line vers [C{client['id']}] {client['addr']}: {e}")
|
||||
|
||||
|
||||
def map_type_transaction(tagcode_lc: str) -> str:
|
||||
if tagcode_lc == "guntime":
|
||||
return "GUN"
|
||||
if tagcode_lc == "newlocation":
|
||||
return "NEW LOCATION"
|
||||
return "TAG READ"
|
||||
|
||||
|
||||
def print_mysql_ct(client: dict, ctx, parts, time_unix, time_iso):
|
||||
"""
|
||||
Prépare les champs, affiche en console si debug, et pousse l'écriture
|
||||
dans MySQL (via write_chrinfo).
|
||||
"""
|
||||
addr = client["addr"]
|
||||
feed_ip, feed_port = addr
|
||||
format_id, seq, location, tagcode, _t, gator, reader_id, lap = parts[:8]
|
||||
|
||||
event_id = ctx.get("event_id") or "UNKNOWN"
|
||||
event_name = ctx.get("event_name") or ""
|
||||
event_desc = ctx.get("event_desc") or ""
|
||||
|
||||
tag_lc = tagcode.lower()
|
||||
type_transaction = map_type_transaction(tag_lc)
|
||||
tag_display = "" if tag_lc in ("newlocation", "guntime") else tagcode
|
||||
|
||||
received_at = now_iso()
|
||||
|
||||
if DEBUG:
|
||||
print("──────────────────────────────────────── CHAMPS MYSQL")
|
||||
print(f" client_id = C{client['id']}")
|
||||
print(f" chrinfo_feed_ip = {feed_ip}")
|
||||
print(f" chrinfo_feed_port = {feed_port}")
|
||||
print(f" chrinfo_event_id = {event_id}")
|
||||
print(f" chrinfo_event_name = {event_name}")
|
||||
print(f" chrinfo_event_desc = {event_desc}")
|
||||
print(f" chrinfo_type_transaction = {type_transaction}")
|
||||
print(f" chrinfo_format_id = {format_id}")
|
||||
print(f" chrinfo_sequence = {seq}")
|
||||
print(f" chrinfo_location = {location}")
|
||||
print(f" chrinfo_tag = {tag_display}")
|
||||
print(f" chrinfo_time = {time_unix}")
|
||||
print(f" chrinfo_time_iso = {time_iso}")
|
||||
print(f" chrinfo_gatornumber = {gator}")
|
||||
print(f" chrinfo_readerid = {reader_id}")
|
||||
print(f" chrinfo_lap = {lap}")
|
||||
print(f" chrinfo_received_at_iso = {received_at}")
|
||||
print("────────────────────────────────────────────────────\n")
|
||||
|
||||
# === Construction du dict MySQL ===
|
||||
row = {
|
||||
"chrinfo_feed_ip": feed_ip,
|
||||
"chrinfo_feed_port": feed_port,
|
||||
"chrinfo_event_id": event_id,
|
||||
"chrinfo_event_name": event_name,
|
||||
"chrinfo_event_desc": event_desc,
|
||||
"chrinfo_type_transaction": type_transaction,
|
||||
"chrinfo_format_id": format_id,
|
||||
"chrinfo_sequence": seq,
|
||||
"chrinfo_location": location,
|
||||
"chrinfo_tag": tag_display,
|
||||
"chrinfo_time": time_unix,
|
||||
"chrinfo_time_iso": time_iso,
|
||||
"chrinfo_gatornumber": gator,
|
||||
"chrinfo_readerid": reader_id,
|
||||
"chrinfo_lap": lap,
|
||||
"chrinfo_received_at_iso": received_at,
|
||||
}
|
||||
|
||||
try:
|
||||
if type_transaction == "NEW LOCATION":
|
||||
write_log("NEWLOCATION", f"[C{client['id']}] New location reçue : {row}")
|
||||
write_chrinfo(row)
|
||||
except Exception as e:
|
||||
if DEBUG:
|
||||
print(f"❌ Erreur écriture MySQL: {e}")
|
||||
|
||||
|
||||
def print_mysql_control(client: dict, raw_line, tokens, ctx):
|
||||
if not DEBUG:
|
||||
return
|
||||
|
||||
addr = client["addr"]
|
||||
feed_ip, feed_port = addr
|
||||
event_id = ctx.get("event_id") or "UNKNOWN"
|
||||
event_name = ctx.get("event_name") or ""
|
||||
event_desc = ctx.get("event_desc") or ""
|
||||
|
||||
print("──────────────────────────────────────── CHAMPS MYSQL (CONTROL)")
|
||||
print(f" client_id = C{client['id']}")
|
||||
print(f" chrinfo_feed_ip = {feed_ip}")
|
||||
print(f" chrinfo_feed_port = {feed_port}")
|
||||
print(f" chrinfo_event_id = {event_id}")
|
||||
print(f" chrinfo_event_name = {event_name}")
|
||||
print(f" chrinfo_event_desc = {event_desc}")
|
||||
print(f" raw_line = {raw_line}")
|
||||
print(f" tokens = {tokens}")
|
||||
print(f" received_at_iso = {now_iso()}")
|
||||
print("────────────────────────────────────────────────────\n")
|
||||
|
||||
|
||||
# ===================== PACKET HANDLING (CTP01) =====================
|
||||
|
||||
def handle_greeting(client: dict):
|
||||
send_line(f"{PROGRAM_NAME}~{PROGRAM_VERSION}~6", client)
|
||||
send_line(f"stream-mode={STREAM_MODE}", client)
|
||||
send_line(f"time-format={TIME_FORMAT}", client)
|
||||
send_line("pushmode-ack=true", client)
|
||||
send_line("guntimes=true", client)
|
||||
send_line("newlocations=true", client)
|
||||
send_line("authentication=plaintext", client)
|
||||
|
||||
|
||||
def handle_packet(text: str, client: dict):
|
||||
global contexts
|
||||
|
||||
addr = client["addr"]
|
||||
ip_key = addr[0]
|
||||
ctx = contexts.setdefault(ip_key, {"event_id": None, "event_name": None, "event_desc": None})
|
||||
|
||||
if DEBUG:
|
||||
print(f"\n📩 [{now_iso()}] Reçu de [C{client['id']}] {addr}: {text}")
|
||||
|
||||
# Greeting
|
||||
if text.startswith("StreamManager"):
|
||||
if DEBUG:
|
||||
print(f"🔭 Greeting reçu de [C{client['id']}] {addr} → réponse CTP01")
|
||||
handle_greeting(client)
|
||||
return
|
||||
|
||||
# AUTH
|
||||
if text.startswith("authorize~"):
|
||||
parts = text.split("~")
|
||||
user = parts[1] if len(parts) >= 2 else ""
|
||||
pwd = parts[2] if len(parts) >= 3 else ""
|
||||
|
||||
if DEBUG:
|
||||
print(f"🔑 AUTH reçu de [C{client['id']}] {addr} : user={user}, pass={pwd}")
|
||||
|
||||
if user == AUTH_USER and pwd == AUTH_PASS:
|
||||
if DEBUG:
|
||||
print("✅ AUTH OK")
|
||||
send_line("ack~authorize", client)
|
||||
send_line("start", client)
|
||||
send_line("geteventinfo", client)
|
||||
else:
|
||||
if DEBUG:
|
||||
print("❌ AUTH FAIL")
|
||||
send_line("err~authorize~invalid password", client)
|
||||
return
|
||||
|
||||
# EVENT INFO
|
||||
if text.startswith("ack~geteventinfo~"):
|
||||
parts = text.split("~")
|
||||
if len(parts) >= 5:
|
||||
ctx["event_name"] = parts[2]
|
||||
ctx["event_id"] = parts[3]
|
||||
ctx["event_desc"] = parts[4]
|
||||
|
||||
if DEBUG:
|
||||
print(f"🎟️ EVENT INFO [C{client['id']}] {addr} → {ctx}")
|
||||
return
|
||||
|
||||
# ACK / ERR génériques
|
||||
if text.startswith(("ack~", "err~")):
|
||||
parts = text.split("~")
|
||||
print_mysql_control(client, text, parts, ctx)
|
||||
return
|
||||
|
||||
# CT01_xx
|
||||
if text.startswith("CT"):
|
||||
packet_q.put((client, text))
|
||||
return
|
||||
|
||||
# Tout autre message
|
||||
print_mysql_control(client, text, text.split("~"), ctx)
|
||||
|
||||
|
||||
# ===================== THREADS =====================
|
||||
|
||||
def client_thread(client: dict):
|
||||
global running, last_client
|
||||
|
||||
conn = client["conn"]
|
||||
addr = client["addr"]
|
||||
cid = client["id"]
|
||||
|
||||
if DEBUG:
|
||||
print(f"🟢 Nouvelle connexion [C{cid}] depuis {addr}")
|
||||
|
||||
try:
|
||||
f = conn.makefile("r", encoding="utf-8", newline="")
|
||||
except Exception as e:
|
||||
if DEBUG:
|
||||
print(f"❌ Erreur makefile sur [C{cid}] {addr}: {e}")
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
return
|
||||
|
||||
try:
|
||||
for line in f:
|
||||
if not running:
|
||||
break
|
||||
text = line.strip("\r\n")
|
||||
if not text:
|
||||
continue
|
||||
last_client = client
|
||||
handle_packet(text, client)
|
||||
except Exception as e:
|
||||
if DEBUG:
|
||||
print(f"❌ Erreur lecture sur [C{cid}] {addr}: {e}")
|
||||
finally:
|
||||
if DEBUG:
|
||||
print(f"🛑 Connexion fermée [C{cid}] {addr}")
|
||||
try:
|
||||
f.close()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def accept_thread():
|
||||
global server_sock, running, client_id_seq
|
||||
|
||||
while running:
|
||||
try:
|
||||
conn, addr = server_sock.accept()
|
||||
except OSError:
|
||||
break
|
||||
except Exception as e:
|
||||
if DEBUG:
|
||||
print(f"❌ Erreur accept(): {e}")
|
||||
continue
|
||||
|
||||
with clients_lock:
|
||||
client_id_seq += 1
|
||||
cid = client_id_seq
|
||||
|
||||
client = {"id": cid, "conn": conn, "addr": addr}
|
||||
threading.Thread(target=client_thread, args=(client,), daemon=True).start()
|
||||
|
||||
|
||||
def parser_thread():
|
||||
global running
|
||||
with open(LOG_FILE, "a", encoding="utf-8") as log:
|
||||
while running or not packet_q.empty():
|
||||
try:
|
||||
client, text = packet_q.get(timeout=1)
|
||||
except queue.Empty:
|
||||
continue
|
||||
|
||||
addr = client["addr"]
|
||||
ip_key = addr[0]
|
||||
ctx = contexts.get(ip_key, {})
|
||||
|
||||
parts = text.split("~")
|
||||
if len(parts) < 5:
|
||||
print_mysql_control(client, text, parts, ctx)
|
||||
continue
|
||||
|
||||
try:
|
||||
t = float(parts[4])
|
||||
time_iso = datetime.datetime.fromtimestamp(t).isoformat(timespec="milliseconds")
|
||||
except Exception:
|
||||
print_mysql_control(client, text, parts, ctx)
|
||||
continue
|
||||
|
||||
print_mysql_ct(client, ctx, parts, t, time_iso)
|
||||
|
||||
log.write(f"{now_iso()} | C{client['id']} | {addr} | {text}\n")
|
||||
log.flush()
|
||||
|
||||
if DEBUG:
|
||||
print("🛑 Parser arrêté.")
|
||||
|
||||
|
||||
# ===================== MAIN =====================
|
||||
|
||||
def main():
|
||||
global server_sock, running, last_client, DEBUG
|
||||
|
||||
# Active le mode debug via paramètre
|
||||
if "--debug" in sys.argv:
|
||||
DEBUG = True
|
||||
|
||||
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server_sock.bind((HOST, PORT))
|
||||
server_sock.listen(100)
|
||||
|
||||
if DEBUG:
|
||||
print(f"🟢 Serveur ChronoTrack TCP actif sur {server_sock.getsockname()[0]}:{server_sock.getsockname()[1]}")
|
||||
print("Tape 'q' puis Entrée pour quitter proprement.\n")
|
||||
|
||||
t_accept = threading.Thread(target=accept_thread, daemon=True)
|
||||
t_parse = threading.Thread(target=parser_thread, daemon=True)
|
||||
|
||||
t_accept.start()
|
||||
t_parse.start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
if DEBUG:
|
||||
cmd = input(">>> ").strip()
|
||||
if cmd.lower() in ("q", "quit", "exit"):
|
||||
running = False
|
||||
break
|
||||
if last_client:
|
||||
send_line(cmd, last_client)
|
||||
else:
|
||||
print("⚠️ Aucun client connecté pour recevoir la commande.")
|
||||
else:
|
||||
# En mode non-interactif (systemd), pas de input()
|
||||
time.sleep(1)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
running = False
|
||||
|
||||
finally:
|
||||
try:
|
||||
server_sock.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
t_accept.join(timeout=2.0)
|
||||
t_parse.join(timeout=2.0)
|
||||
|
||||
|
||||
if DEBUG:
|
||||
print("✅ Fermeture complète.")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user