From 9a158f5c5f7913c20f90fb1e9ce3f4d92d01835e Mon Sep 17 00:00:00 2001 From: Poulpe Date: Mon, 27 Apr 2026 22:08:44 +0000 Subject: [PATCH] Initial: ContinuousTransponder wrapper for Kogger USBL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit High-level Python wrapper around the upstream cosma-tech/kogger_acousticAntenna driver. Configures a Kogger acoustic antenna as a permanent slave transponder in a single start() call: address filter, echo filter, optional TDMA sync slot, permanent response window, and Python callbacks for each ping received. No modification to the upstream driver — only composes existing public methods in the right order. Snapshot of upstream driver included read-only under driver/ for reference. Includes: - transponder_continu.py (302 lines): the wrapper class + CLI - examples/auv_slave.py (79 lines): usage example with logging - README.md: design rationale, usage, multi-AUV TDMA, watchdog, hardware wiring - driver/: snapshot of cosma-tech/kogger_acousticAntenna at commit 1b539f9 ('Add index slot for multi pinger', 2025-03-11) Built for Cosma context (USV master + N AUVs slaves) following the design conversation in Discord #ping-pong-ping (2026-04-27). See poulpe/ping-pong-ping on Gitea for the interactive demo of the protocol. --- .gitignore | 2 + README.md | 143 ++ driver/README.md | 204 +++ driver/communication.py | 206 +++ driver/interface.py | 841 +++++++++ driver/kogger_protocol_driver.py | 1509 +++++++++++++++++ driver/log_add_diff.py | 51 + driver/log_generator_launcher.py | 64 + driver/log_merge_nav_usbl.py | 111 ++ driver/log_to_csv.py | 203 +++ driver/log_to_human.py | 276 +++ driver/print_log.py | 139 ++ driver/simulation_kogger.py | 212 +++ driver/test/listen_to_antenna.py | 153 ++ .../test/log/2026-03-19_12-51-12_AUV_usbl.csv | 0 .../test/log/2026-03-19_12-51-12_log_usv.log | 5 + .../test/log/2026-03-19_12-52-10_AUV_usbl.csv | 0 .../test/log/2026-03-19_12-52-10_log_usv.log | 5 + .../test/log/2026-03-19_12-52-42_AUV_usbl.csv | 69 + .../test/log/2026-03-19_12-52-42_log_usv.log | 17 + .../test/log/2026-03-20_13-29-40_AUV_usbl.csv | 171 ++ .../test/log/2026-03-20_13-29-40_log_usv.log | 11 + .../test/log/2026-03-20_13-35-32_AUV_usbl.csv | 0 .../test/log/2026-03-20_13-35-32_log_auv.log | 5 + .../test/log/2026-03-20_13-35-54_AUV_usbl.csv | 81 + .../test/log/2026-03-20_13-35-54_log_auv.log | 77 + .../test/log/2026-03-20_14-32-59_AUV_usbl.csv | 7 + .../test/log/2026-03-20_14-32-59_log_auv.log | 28 + .../test/log/2026-03-20_14-34-01_AUV_usbl.csv | 7 + .../test/log/2026-03-20_14-34-01_log_auv.log | 23 + .../test/log/2026-03-20_14-35-07_AUV_usbl.csv | 7 + .../test/log/2026-03-20_14-35-07_log_auv.log | 27 + .../test/log/2026-03-20_14-36-35_AUV_usbl.csv | 7 + .../test/log/2026-03-20_14-36-35_log_auv.log | 26 + .../test/log/2026-03-20_14-37-46_AUV_usbl.csv | 7 + .../test/log/2026-03-20_14-37-46_log_auv.log | 20 + .../test/log/2026-03-20_14-37-51_AUV_usbl.csv | 5 + .../test/log/2026-03-20_14-37-51_log_usv.log | 16 + .../test/log/2026-03-20_14-44-29_AUV_usbl.csv | 7 + .../test/log/2026-03-20_14-44-29_log_auv.log | 21 + .../test/log/2026-03-20_14-56-47_AUV_usbl.csv | 57 + .../test/log/2026-03-20_14-56-47_log_usv.log | 11 + .../test/log/2026-03-22_08-42-12_AUV_usbl.csv | 210 +++ .../test/log/2026-03-22_08-42-12_log_usv.log | 657 +++++++ .../test/log/2026-03-22_08-49-18_AUV_usbl.csv | 185 ++ .../test/log/2026-03-22_08-49-18_log_usv.log | 771 +++++++++ driver/test/test_auv.py | 178 ++ driver/test/test_kogger_driver.py | 252 +++ driver/test/test_messages.bin | 16 + driver/test/test_multi_antenna.py | 230 +++ driver/test/test_usv.py | 183 ++ examples/auv_slave.py | 79 + transponder_continu.py | 302 ++++ 53 files changed, 7894 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 driver/README.md create mode 100755 driver/communication.py create mode 100755 driver/interface.py create mode 100644 driver/kogger_protocol_driver.py create mode 100755 driver/log_add_diff.py create mode 100755 driver/log_generator_launcher.py create mode 100755 driver/log_merge_nav_usbl.py create mode 100755 driver/log_to_csv.py create mode 100755 driver/log_to_human.py create mode 100755 driver/print_log.py create mode 100644 driver/simulation_kogger.py create mode 100755 driver/test/listen_to_antenna.py create mode 100644 driver/test/log/2026-03-19_12-51-12_AUV_usbl.csv create mode 100644 driver/test/log/2026-03-19_12-51-12_log_usv.log create mode 100644 driver/test/log/2026-03-19_12-52-10_AUV_usbl.csv create mode 100644 driver/test/log/2026-03-19_12-52-10_log_usv.log create mode 100644 driver/test/log/2026-03-19_12-52-42_AUV_usbl.csv create mode 100644 driver/test/log/2026-03-19_12-52-42_log_usv.log create mode 100644 driver/test/log/2026-03-20_13-29-40_AUV_usbl.csv create mode 100644 driver/test/log/2026-03-20_13-29-40_log_usv.log create mode 100644 driver/test/log/2026-03-20_13-35-32_AUV_usbl.csv create mode 100644 driver/test/log/2026-03-20_13-35-32_log_auv.log create mode 100644 driver/test/log/2026-03-20_13-35-54_AUV_usbl.csv create mode 100644 driver/test/log/2026-03-20_13-35-54_log_auv.log create mode 100644 driver/test/log/2026-03-20_14-32-59_AUV_usbl.csv create mode 100644 driver/test/log/2026-03-20_14-32-59_log_auv.log create mode 100644 driver/test/log/2026-03-20_14-34-01_AUV_usbl.csv create mode 100644 driver/test/log/2026-03-20_14-34-01_log_auv.log create mode 100644 driver/test/log/2026-03-20_14-35-07_AUV_usbl.csv create mode 100644 driver/test/log/2026-03-20_14-35-07_log_auv.log create mode 100644 driver/test/log/2026-03-20_14-36-35_AUV_usbl.csv create mode 100644 driver/test/log/2026-03-20_14-36-35_log_auv.log create mode 100644 driver/test/log/2026-03-20_14-37-46_AUV_usbl.csv create mode 100644 driver/test/log/2026-03-20_14-37-46_log_auv.log create mode 100644 driver/test/log/2026-03-20_14-37-51_AUV_usbl.csv create mode 100644 driver/test/log/2026-03-20_14-37-51_log_usv.log create mode 100644 driver/test/log/2026-03-20_14-44-29_AUV_usbl.csv create mode 100644 driver/test/log/2026-03-20_14-44-29_log_auv.log create mode 100644 driver/test/log/2026-03-20_14-56-47_AUV_usbl.csv create mode 100644 driver/test/log/2026-03-20_14-56-47_log_usv.log create mode 100644 driver/test/log/2026-03-22_08-42-12_AUV_usbl.csv create mode 100644 driver/test/log/2026-03-22_08-42-12_log_usv.log create mode 100644 driver/test/log/2026-03-22_08-49-18_AUV_usbl.csv create mode 100644 driver/test/log/2026-03-22_08-49-18_log_usv.log create mode 100755 driver/test/test_auv.py create mode 100755 driver/test/test_kogger_driver.py create mode 100644 driver/test/test_messages.bin create mode 100755 driver/test/test_multi_antenna.py create mode 100755 driver/test/test_usv.py create mode 100644 examples/auv_slave.py create mode 100644 transponder_continu.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a60b85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.pyc diff --git a/README.md b/README.md new file mode 100644 index 0000000..d3519cd --- /dev/null +++ b/README.md @@ -0,0 +1,143 @@ +# Kogger USBL — mode transpondeur continu + +Wrapper Python de haut niveau autour du driver Kogger `kogger_acousticAntenna` (cosma-tech) qui configure une antenne acoustique en **slave transpondeur permanent** : elle écoute en continu les pings adressés à son ID, répond automatiquement, ignore les autres, et expose un callback Python pour chaque ping reçu. + +**Pas de re-armement par ping. Une seule init. Le device répond tant que le process tourne.** + +Construit pour le contexte Cosma (USV master + N AUVs slaves), suite à la conversation Discord [#ping-pong-ping](https://gitea.nowyouknow.fr/poulpe/ping-pong-ping) du 2026-04-27 sur les schémas SS-TWR + écoute passive OWR. + +--- + +## Pourquoi ce wrapper + +Le driver Kogger upstream (`kogger_protocol_driver.KoggerSBPDevice`) expose toutes les commandes bas niveau nécessaires pour le mode transpondeur : + +| Commande | Rôle | +|---|---| +| `set_usbl_transponder(enable)` | Ouvre/ferme la fenêtre de réponse acoustique. `True` = always (timeout `0xFFFFFFFF` µs). | +| `set_usbl_request_address_filter([...])` | Liste 8 adresses (0-7 ou `0xFF`=désactivé) pour lesquelles le device doit répondre. | +| `set_usbl_monitor_config(enable, echo_filter_response_us, echo_filter_request_us)` | Active la surveillance + le filtre d'écho (évite l'auto-déclenchement par réverbération). | +| `set_sync_mode(slot_total, slot_index, slot_duration, enable_delay)` | Enrôle le device dans un slot TDMA (utile en multi-slave). | +| `register_callback(ID_USBL_SOLUTION, fn)` | Branche un callback Python sur les frames USBL solution reçues. | + +Mais il faut les composer dans **le bon ordre**, gérer les callbacks, et idéalement avoir un watchdog qui ré-applique la config si le device a redémarré ou que le master a été silencieux trop longtemps. + +C'est exactement ce que `ContinuousTransponder` fait — un appel `start()` configure tout et le device entre en mode permanent. + +## Architecture + +``` +. +├── README.md # ce fichier +├── transponder_continu.py # ★ wrapper haut niveau (zéro modif du driver) +├── examples/ +│ └── auv_slave.py # exemple AUV slave avec logging +└── driver/ # snapshot read-only du driver upstream + ├── kogger_protocol_driver.py (1509 lignes, 78 KB) + ├── communication.py + ├── interface.py + ├── simulation_kogger.py + ├── README.md + └── ... +``` + +**Aucune modification du driver Kogger d'origine.** Tout passe par les méthodes publiques. Quand cosma-tech publie une nouvelle version du driver, il suffit de remplacer le contenu de `driver/` — ce wrapper continue de fonctionner. + +## Usage minimal + +```python +from transponder_continu import ContinuousTransponder + +t = ContinuousTransponder(port="/dev/ttyUSB0", my_address=2, + vehicle_name="AUV-2") +t.on_ping_received(lambda solution: print("ping →", solution)) +t.start() +try: + t.run_forever() +finally: + t.stop() +``` + +## CLI directement + +```bash +python3 transponder_continu.py --port /dev/ttyUSB0 --address 2 +``` + +Options utiles : + +``` +--vehicle AUV-2 # étiquette pour les logs CSV du driver +--watchdog-s 15 # ré-applique la config si silence > 15 s +--slot-total 4 --slot-index 1 --slot-duration 2.0 # enrôle en TDMA +--echo-filter-us 400000 # 400 ms (défaut) +``` + +## Contenu de la trame transpondeur + +Chaque fois que le master USV envoie un ping ciblé sur l'adresse de cet AUV : + +1. Le hardware Kogger filtre par adresse (configuré par `set_usbl_request_address_filter`). +2. Si l'adresse correspond, le hardware **répond automatiquement** (le pong est généré par l'antenne, pas par Python — c'est rapide et déterministe). +3. Le driver émet une frame `ID_USBL_SOLUTION` (0x65) sur la sortie UART contenant la distance hardware-computed, le SNR, l'ID source. +4. Le wrapper appelle votre callback `on_ping_received(message)`. +5. Le state interne (`t.state.last_distance_m`, `last_snr`, `last_ping_id`, `pings_received`) est mis à jour. + +Tout cela en boucle, sans intervention. + +## Multi-AUV en TDMA + +Si N AUVs partagent le canal et que le master scanne en round-robin, configurer chaque slave dans son propre slot évite les collisions de pong : + +```python +ContinuousTransponder( + port="/dev/ttyUSB0", + my_address=2, + sync=SyncSlot(slot_total=4, slot_index=1, slot_duration=2.0), +) +``` + +→ AUV-2 ne répond que pendant son slot (slot_index=1, durée 2 s, cycle complet 8 s). + +Convention proposée : `slot_index = my_address - 1`. + +## Watchdog + +Si on n'a rien reçu depuis `watchdog_timeout_s` secondes (et qu'on a au moins reçu un ping au démarrage), le wrapper ré-applique `set_usbl_transponder(enable=True)`. Couvre le cas où l'antenne Kogger reboot silencieusement et perd sa config. + +```python +ContinuousTransponder(..., watchdog_timeout_s=15.0) +``` + +Off par défaut. Activer en prod si le master est censé pinger en continu. + +## Hardware + +Câblage Kogger côté antenne (recopié du README upstream pour référence) : + +| Couleur | Signal | +|---|---| +| Brown | +V supply | +| Blue | GND | +| Green | UART RX (USBL ← host) | +| Yellow | UART TX (USBL → host) | +| Pink | GND (relié au bleu interne) | +| Gray | TRIGGER_IN | +| White | TRIGGER_OUT | + +Connexion typique : `Pi4B → USB-UART converter → antenne Kogger`. Baudrate par défaut 921600. + +## Limites / TODO + +- [ ] Pas testé sur hardware réel (uniquement statique). Cf. test sur AUV-2 cosma au prochain déploiement. +- [ ] Pas de gestion de reconnexion USB-UART. Si le port disparaît, le process meurt — relancer côté systemd. +- [ ] Pas de support multi-port (un wrapper = une antenne). Pour multi-antennes sur un même Pi, instancier N `ContinuousTransponder` indépendants. +- [ ] L'écoute passive OWR (calcul de distance par non-ciblés à partir de `T_recv − T_send`) n'est **pas** dans ce wrapper — elle nécessite une référence temporelle synchronisée USV↔AUVs et l'extraction de `T_send` depuis la trame du ping (pas exposé proprement par le driver upstream pour l'instant). À ajouter quand le besoin se présente. +- [ ] Ajouter un `start()` idempotent (actuellement appeler 2× réenroule les callbacks). + +## Références + +- Driver upstream : `git@github.com:cosma-tech/kogger_acousticAntenna.git` +- Page démo USBL DS-TWR + SS-TWR + OWR : +- Repo demo : `poulpe/ping-pong-ping` sur Gitea +- Conversation Discord d'origine : `#ping-pong-ping` (2026-04-27) diff --git a/driver/README.md b/driver/README.md new file mode 100644 index 0000000..156e097 --- /dev/null +++ b/driver/README.md @@ -0,0 +1,204 @@ +# Driver for Acoustic Antenna from Kogger + +## Installations request + +``` +sudo apt update +sudo apt install python3-serial python3-loguru +``` + +# Kogger wires + +Brown : supply (+) +Blue : ground (-) +Green : UART_RX (USBL side) +Yellow : UART_TX (USBL side) +Pink : ground (-), connected to the blue wire inside USBL +Gray : TRIGGER_IN (USBL side) +White : TRIGGER_OUT (USBL side) + +## Files description + +``` +. +├── communication.py +├── interface.py +├── kogger_protocol_driver.py +└── test + ├── test_kogger_driver.py + └── test_messages.bin +``` + +### communication.py + +Test all functions from kogger\_protocol\_driver.py + +### interface.py + +Open a tkinter interface with all kogger\_protocol\_driver functions. + +### kogger\_protocol\_driver.py + +Driver for kogger device + +### test folder + +Contains tests function for kogger\_protocol\_driver. + +#### Raspberry connection to Kogger + ++-----------------+ +---------------+ +------------+ +| | | | | | +| RASPBERRY PI 4 o---USB---o Convertisseur o---UART--o Antenne | +| | | USB-vers-UART | (TX,RX, | Kogger | +| | | | GND) | | ++-----------------+ +---------------+ +------------+ + +#### USV / Boat side + +Launch : + +``` +cd test +./test_usv.py +``` + +#### AUV / Sub side + +Launch : + +``` +cd test +./test_auv.py +``` + + +# Analyze koggerApp + + bb55 00 8b 11 00 xx 9cc3 +Header Route Mode ID Length Payload Check + +On boot : +A TX payload […]: +- bb55008b11009cc3 +- bb5500931100a4db +- bb550083100100943e +- bb550083130096af +- bb550083120095ad +- bb550083150098b3 +- bb550083140097b1 +- bb5500832000a3c9 +- bb55008b2000abe1 +- bb5500932000b3f9 +- bb55008318054a5d6bc9017c98 +- bb55008b18054a5d6bc90184d8 +- bb550 + +A RX payload: +- bb5500c91103049cc3401e +- bb5500d1110304a4db6876 +- bb5500c1100304943eaa54 +- bb5500c113030496af20d8 +- bb5500c112030495ad1ccf +- bb5500c1150304 98b328ea +- bb5500c114030497b124e1 +- bb5500412022000f00000000000078fe6b94000203000000000000000000000000000000000000000c32 +- bb5500c120 0301a3c95144 +- bb5500c9200305abe17da8 +- bb550051200900000f0002000003018f00 +- bb5500d1200305b3f9a500 +- bb5500c11803047c98f4a6 +- bb5500c91803 0484d84426 +- bb5500d11803048a870111 + +## Conclusion : + +Host : read 0x10, 0x11, 0x12, x13, x14, x15, x18, x20 +0x10 : ID_DATASET +- bb550083100100943e +- bb5500c1100304943eaa54 +0x11 : ID_DIST_SETU +0x12 : ID_CHART_SETUP +0x14 : ID_TRANSC +0x15 : ID_SND_SPD +0x18 : ID_UART +0x20 : ID_VERSION + +# Kogger number + +## USV to AUV + +0 : DISARM Set AUV to DISARM +1 : DEPTH_HOLD Set AUV to fixed DEPTH +2 : Pause? +3 : MISSION_DEPTH Go to mission on DEPTH_HOLD +4 : MISSION Go to mission on ALT_HOLD +5 : SURFACE Go to SURFACE +6 : USBL_MODE_RESP Set USBL AUV as response +7 : USBL_MODE_TRAN Set USBL AUV as transponder +8 : + +## AUV to USV + +0 : DISARM AUV is DISARMED +1 : DEPTH_HOLD AUV is at fixed DEPTH +2 : Pause? +3 : MISSION_DEPTH AUV is in mission on DEPTH_HOLD +4 : MISSION AUV is in mission on ALT_HOLD +5 : SURFACE AUV is going to to SURFACE +6 : RECOVER_STUCK AUV is in recover_stuck or recover_stuck_random +7 : HARDWARE_FAIL AUV is in emergency and is going to surface +8 : EMERGENCY_STUCK AUV is stuck + +## Kogger transfer in the air + +------------------------------------- + Pinger sending | Responder receiving + 0 | 5 + 1 | 2 + 2 | 1 + 3 | 4 + 4 | 3 + 5 | 0 + 6 | 6 + 7 | 7 OR 3 OR 5 + 8 | 8 OR 255 +------------------------------------- + +# Python info + +## struct.unpack + +- ? : bool (1B) +- b : int8 (1B) +- B : uint8 (1B) +- h : int16 +- H : uint16 +- i : int32 +- I : uint32 +- f : float (4B) +- d : double(8B) +- q : int64 +- Q : uint64 + +# Test multi-antenna + +From test/test_multi_antenna.py we can try a multi-antenna with time syncho. + +## In spiral mode + +### From USV/Slave + +``` +./test_multi_antenna.py /dev/ttyUSB0 0 2 +``` + +### From AUV/Master + +Where every lines is an AUV : +``` +./test_multi_antenna.py /dev/ttyUSB1 1 2 +./test_multi_antenna.py /dev/ttyUSB2 2 2 +``` + + diff --git a/driver/communication.py b/driver/communication.py new file mode 100755 index 0000000..0fa63bb --- /dev/null +++ b/driver/communication.py @@ -0,0 +1,206 @@ +#! /usr/bin/env python +# Test all commands from kogger driver + +import sys +import time +import struct # For unpacking in callbacks, if needed +from loguru import logger + +# Ensure the kogger_protocol_driver.py is in the Python path +try: + from kogger_protocol_driver import ( + KoggerSBPDevice, + ID_TIMESTAMP, + # Add other specific IDs if you want specific callbacks for them + ) +except ImportError: + logger.critical("Failed to import KoggerSBPDevice. Make sure kogger_protocol_driver.py is in the same directory or Python path.") + sys.exit(1) + +# --- Loguru Setup for this script --- +logger.remove() # Remove default handler +LOG_LEVEL = "INFO" # Change to "DEBUG" for more detailed driver logs +logger.add(sys.stderr, level=LOG_LEVEL, format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}") + +# --- Configuration for Real Device --- +SERIAL_PORT = "/dev/ttyUSB0" +BAUDRATE = 115200 +DEVICE_ADDRESS = 0 +REQUEST_INTERVAL_SECONDS = 10 # How often to run the full cycle of get requests +COMMAND_PAUSE_SECONDS = 0.01 # Small pause between individual commands + +# --- Callback for Unsolicited Messages --- +def handle_unsolicited_message(frame): + """Default callback for unsolicited messages.""" + #logger.info(f"[UNSOLICITED MSG] ID: {frame['id']:#02x}, Mode: {frame['mode']:#02x}, Len: {frame['length']}, Payload: {frame['payload'].hex().upper()}") + if frame['id'] == ID_TIMESTAMP and frame.get('checksum_ok') and \ + (frame['mode'] & 0x03) == 1 and frame['length'] == 4: # TYPE_CONTENT = 1 + try: + timestamp = struct.unpack(' Unsolicited Timestamp: {timestamp} ms") + except struct.error as e: + logger.warning(f" -> Failed to unpack unsolicited timestamp: {e}") + # Add more specific parsing here if you expect other unsolicited messages frequently + +def UpdateCounter(data, string, str_valid, str_timeout): + if data: + logger.success(" Success to get "+str(string)+":"+str(data)) + str_valid.append(string) + else: + logger.warning(" Failed to get "+str(string)) + str_timeout.append(string) + + +def main(): + logger.info(f"Attempting to connect to Kogger device on {SERIAL_PORT} at {BAUDRATE} baud.") + + driver = KoggerSBPDevice( + port=SERIAL_PORT, + baudrate=BAUDRATE, + device_address=DEVICE_ADDRESS, + default_timeout=0.1 # Wait up to 2s for a solicited response + ) + + if not driver.connect(): + logger.error(f"Failed to connect to the device. Please check connections and permissions for {SERIAL_PORT}.") + return + + driver.register_default_callback(handle_unsolicited_message) + logger.info("Registered default callback for unsolicited messages.") + + try: + logger.info(f"Successfully connected. Starting data polling cycle every {REQUEST_INTERVAL_SECONDS} seconds. Press Ctrl+C to stop.") + loop_count = 0 + while True: + str_valid = [] + str_timeout = [] + loop_count += 1 + logger.info(f"\n========= Request Cycle {loop_count} =========") + + # --- Measurement Data --- + logger.info("--- Requesting Timestamp (get_timestamp) ---") + data = driver.get_timestamp() + UpdateCounter(data, "get_timestamp", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + logger.info("--- Requesting Distance v0 (get_distance) ---") + data = driver.get_distance(version=0) + UpdateCounter(data, "get_distance(version=0)", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + logger.info("--- Requesting Distance v1 (get_distance) ---") + data = driver.get_distance(version=1) + UpdateCounter(data, "get_distance(version=1)", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + logger.info("--- Requesting Chart Data (get_chart_data) ---") + data = driver.get_chart_data() + UpdateCounter(data, "get_chart_data()", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + logger.info("--- Requesting Attitude v0 - Euler (get_attitude) ---") + data = driver.get_attitude(version=0) + UpdateCounter(data, "get_attitude(version=0)", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + logger.info("--- Requesting Attitude v1 - Quaternion (get_attitude) ---") + data = driver.get_attitude(version=1) + UpdateCounter(data, "get_attitude(version=1)", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + logger.info("--- Requesting Temperature (get_temperature) ---") + data = driver.get_temperature() + UpdateCounter(data, "get_temperature()", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + # --- Settings Data (GET methods) --- + UART_ID_TO_QUERY = 1 # Example UART ID from PDF + CHANNEL_ID_TO_QUERY = 0 # Example: 0 for "all active" or default dataset + + logger.info(f"--- Requesting Dataset Config (Channel {CHANNEL_ID_TO_QUERY}) (get_dataset_config) ---") + data = driver.get_dataset_config(channel_id_to_request=CHANNEL_ID_TO_QUERY) + UpdateCounter(data, "get_dataset_config(channel_id_to_request=CHANNEL_ID_TO_QUERY)", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + logger.info("--- Requesting Distance Setup (get_distance_setup) ---") + data = driver.get_distance_setup() + UpdateCounter(data, "get_distance_setup()", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + logger.info("--- Requesting Chart Setup (get_chart_setup) ---") + data = driver.get_chart_setup() + UpdateCounter(data, "get_chart_setup()", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + logger.info("--- Requesting Transceiver Settings (get_transceiver_settings) ---") + data = driver.get_transceiver_settings() + UpdateCounter(data, "get_transceiver_settings()", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + data = driver.get_sound_speed() + UpdateCounter(data, "get_sound_speed()", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + logger.info(f"--- Requesting UART Config v0 (Baudrate, UART {UART_ID_TO_QUERY}) (get_uart_config) ---") + data = driver.get_uart_config(uart_id=UART_ID_TO_QUERY, version=0) + UpdateCounter(data, "get_uart_config(uart_id=1, version=0)", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + logger.info(f"--- Requesting UART Config v1 (Dev Address, UART {UART_ID_TO_QUERY}) (get_uart_config) ---") + data = driver.get_uart_config(uart_id=UART_ID_TO_QUERY, version=1) + UpdateCounter(data, "get_uart_config(uart_id=1, version=1)", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + # --- System Data (GET methods) --- + logger.info("--- Requesting Version Info (get_version_info) ---") + data = driver.get_version_info() + UpdateCounter(data, "get_version_info()", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + logger.info("--- Requesting Mark Status (get_mark_status) ---") + data = driver.get_mark_status() + UpdateCounter(data, "get_mark_status()", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + logger.info("--- Requesting Diagnostics (get_diagnostics) ---") + data = driver.get_diagnostics() + UpdateCounter(data, "get_diagnostics()", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + # --- Navigation Data (GET methods) --- + logger.info("--- Requesting Navigation Data (get_navigation_data) ---") + data = driver.get_navigation_data() + UpdateCounter(data, "get_navigation_data()", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + logger.info("--- Requesting DVL Velocity Data (get_dvl_velocity_data) ---") + data = driver.get_dvl_velocity_data() + UpdateCounter(data, "get_dvl_velocity_data()", str_valid, str_timeout) + time.sleep(COMMAND_PAUSE_SECONDS) + + logger.info(f"========= End of Request Cycle {loop_count}. Waiting {REQUEST_INTERVAL_SECONDS} seconds... =========") + logger.info("str_valid "+str(len(str_valid)) +"="+str(str_valid)) + logger.info("str_timeout "+str(len(str_timeout))+"="+str(str_timeout)) + time.sleep(REQUEST_INTERVAL_SECONDS) + + except KeyboardInterrupt: + logger.info("Keyboard interrupt received. Stopping application...") + except Exception as e: + logger.critical(f"An unexpected error occurred during main loop: {e}", exc_info=True) + finally: + logger.info("Attempting to disconnect from the device...") + if 'driver' in locals() and driver and hasattr(driver, 'serial_conn') and driver.serial_conn : + if hasattr(driver.serial_conn, 'is_open') and driver.serial_conn.is_open: + driver.disconnect() + logger.info("Disconnected successfully.") + else: # Port was not open, but driver object exists + if hasattr(driver, '_reader_thread') and driver._reader_thread and driver._reader_thread.is_alive(): + logger.info("Port was not open, ensuring reader thread is stopped if it was started.") + driver._stop_event.set() + driver._reader_thread.join(timeout=1.0) + else: + logger.info("Driver was not connected or instance not fully available for disconnect.") + +if __name__ == "__main__": + main() + logger.info("Real device interaction script finished.") diff --git a/driver/interface.py b/driver/interface.py new file mode 100755 index 0000000..4a8a3a0 --- /dev/null +++ b/driver/interface.py @@ -0,0 +1,841 @@ +#! /usr/bin/env python + +import tkinter as tk +from tkinter import ttk, scrolledtext, messagebox, filedialog +import threading +import queue +import sys +import os +import time # For delays in "Get All" + +# Ensure kogger_protocol_driver.py is accessible +try: + from loguru import logger + from kogger_protocol_driver import KoggerSBPDevice, KEY_CONFIRM + from kogger_protocol_driver import ( + ID_TIMESTAMP, ID_DIST, ID_ATTITUDE, ID_TEMP, ID_UART, ID_FLASH, ID_BOOT, + ID_DATASET, ID_DIST_SETUP, ID_CHART_SETUP, ID_TRANSC, ID_SND_SPD, + ID_VERSION, ID_MARK, ID_DIAG, ID_NAV, ID_DVL_VEL, ID_IMU_SETUP, ID_UPDATE + ) +except ImportError as e: + try: + import logging + _crit_logger = logging.getLogger(__name__) + _crit_handler = logging.StreamHandler(sys.stderr) + _crit_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') + _crit_handler.setFormatter(_crit_formatter) + _crit_logger.addHandler(_crit_handler) + _crit_logger.setLevel(logging.CRITICAL) + _crit_logger.critical(f"Failed to import necessary modules: {e}. Make sure 'kogger_protocol_driver.py' is in the same directory or your PYTHONPATH, and 'loguru' is installed.") + except ImportError: + print(f"CRITICAL ERROR: Failed to import necessary modules: {e}. Make sure 'kogger_protocol_driver.py' is in the same directory or your PYTHONPATH, and 'loguru' is installed.") + sys.exit(1) + +class TkinterLogHandler: + """A handler for Loguru that redirects log messages to a Tkinter Text widget.""" + def __init__(self, text_widget): + """ + Initializes the log handler. + :param text_widget: The Tkinter Text widget to which logs will be written. + """ + self.text_widget = text_widget + self.queue = queue.Queue() + self.text_widget.after(100, self._process_log_queue) + + def write(self, message): + """ + Called by Loguru to write a log message. Adds message to an internal queue. + :param message: The log message string. + """ + self.queue.put(message) + + def _process_log_queue(self): + """Processes messages from the log queue and inserts them into the Text widget.""" + try: + while not self.queue.empty(): + message = self.queue.get_nowait() + if self.text_widget.winfo_exists(): + self.text_widget.configure(state=tk.NORMAL) + self.text_widget.insert(tk.END, message) + self.text_widget.see(tk.END) + self.text_widget.configure(state=tk.DISABLED) + if self.text_widget.winfo_exists(): + self.text_widget.after(100, self._process_log_queue) + except Exception as e: + print(f"Error in TkinterLogHandler: {e}") + + +class KoggerGuiApp: + """Main application class for the Kogger SBP Control Panel GUI.""" + def __init__(self, root_window): + """ + Initializes the main application window and its components. + :param root_window: The main Tkinter window (tk.Tk instance). + """ + self.root = root_window + self.root.title("Kogger SBP Control Panel") + self.root.geometry("950x750") + + self.driver = None + self.command_queue = queue.Queue() # For results from driver threads to GUI + self.unsolicited_log_queue = queue.Queue() # For unsolicited messages to GUI log + + self.log_text_widget = scrolledtext.ScrolledText(self.root, state=tk.DISABLED, height=10, wrap=tk.WORD, font=("Consolas", 9)) + self.log_text_widget.pack(side=tk.BOTTOM, fill=tk.X, padx=5, pady=5) + + self.configure_logging() + + self.connection_frame = ttk.LabelFrame(self.root, text="Connection") + self.connection_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5) + + self.notebook = ttk.Notebook(self.root) + self.notebook.pack(expand=True, fill="both", padx=5, pady=5) + + self.tab_measurements = ttk.Frame(self.notebook) + self.tab_device_settings = ttk.Frame(self.notebook) + self.tab_data_settings = ttk.Frame(self.notebook) + self.tab_system = ttk.Frame(self.notebook) + self.tab_navigation = ttk.Frame(self.notebook) + + self.notebook.add(self.tab_measurements, text="Measurements & Actions") + self.notebook.add(self.tab_device_settings, text="Device Settings") + self.notebook.add(self.tab_data_settings, text="Data Config") + self.notebook.add(self.tab_system, text="System & Flash") + self.notebook.add(self.tab_navigation, text="Navigation") + + self._create_connection_widgets() + self._create_measurement_widgets() + self._create_device_settings_widgets() + self._create_data_settings_widgets() + self._create_system_widgets() + self._create_navigation_widgets() + + self.root.protocol("WM_DELETE_WINDOW", self._on_closing) + self.root.after(100, self._process_command_queue) + self.root.after(100, self._process_unsolicited_log_queue) + + def configure_logging(self): + """Configures Loguru to output to console and the GUI's log widget.""" + logger.remove() + logger.add(sys.stderr, level="DEBUG", + format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}") + if self.log_text_widget.winfo_exists(): + log_handler = TkinterLogHandler(self.log_text_widget) + logger.add(log_handler, format="{time:HH:mm:ss} | {level: <7} | {message}", level="INFO") + logger.info("GUI Initialized. Logging configured.") + + def _create_connection_widgets(self): + """Creates widgets for the connection panel (port, baud, connect button).""" + ttk.Label(self.connection_frame, text="Port:").grid(row=0, column=0, padx=2, pady=2, sticky="w") + self.port_var = tk.StringVar(value="/dev/ttyUSB0") + ttk.Entry(self.connection_frame, textvariable=self.port_var, width=15).grid(row=0, column=1, padx=2, pady=2) + + ttk.Label(self.connection_frame, text="Baud:").grid(row=0, column=2, padx=2, pady=2, sticky="w") + self.baud_var = tk.StringVar(value="115200") + ttk.Entry(self.connection_frame, textvariable=self.baud_var, width=10).grid(row=0, column=3, padx=2, pady=2) + + ttk.Label(self.connection_frame, text="Addr:").grid(row=0, column=4, padx=2, pady=2, sticky="w") + self.addr_var = tk.StringVar(value="0") + ttk.Entry(self.connection_frame, textvariable=self.addr_var, width=3).grid(row=0, column=5, padx=2, pady=2) + + self.connect_button = ttk.Button(self.connection_frame, text="Connect", command=self._toggle_connection) + self.connect_button.grid(row=0, column=6, padx=5, pady=2) + self.connection_status_var = tk.StringVar(value="Status: Disconnected") + ttk.Label(self.connection_frame, textvariable=self.connection_status_var).grid(row=0, column=7, padx=5, pady=2, sticky="w") + + def _create_labeled_entry(self, parent, label_text, default_value="", width=10): + """ + Helper to create a labeled entry field. + :param parent: The parent widget. + :param label_text: Text for the label. + :param default_value: Default value for the entry. + :param width: Width of the entry field. + :return: Tuple (frame_containing_label_and_entry, string_var_for_entry, entry_widget). + """ + frame = ttk.Frame(parent) + ttk.Label(frame, text=label_text).pack(side=tk.LEFT, padx=(0,2)) + var = tk.StringVar(value=default_value) + entry = ttk.Entry(frame, textvariable=var, width=width) + entry.pack(side=tk.LEFT) + return frame, var, entry + + def _create_result_display(self, parent, label_text, width=50): + """ + Helper to create a display area (label + read-only entry) for command results. + :param parent: The parent widget. + :param label_text: Text for the label describing the result. + :param width: Width of the result display entry. + :return: Tuple (frame_containing_label_and_display, string_var_for_result_display). + """ + frame = ttk.Frame(parent) + ttk.Label(frame, text=label_text, anchor="w").pack(side=tk.LEFT, padx=(0, 2)) + var = tk.StringVar(value="N/A") + entry = ttk.Entry(frame, textvariable=var, state="readonly", width=width) + entry.pack(side=tk.LEFT, fill=tk.X, expand=True) + return frame, var + + def _create_measurement_widgets(self): + """Creates widgets for the 'Measurements & Actions' tab, including the 'Get All' button.""" + parent = self.tab_measurements + row_idx = 0 + + self.get_all_button = ttk.Button(parent, text="🔄 Get All Available Data", command=self._get_all_data_sequentially) + self.get_all_button.grid(row=row_idx, column=0, columnspan=2, sticky="ew", padx=5, pady=10) + row_idx += 1 + + ttk.Separator(parent, orient='horizontal').grid(row=row_idx, column=0, columnspan=2, sticky='ew', pady=5) + row_idx +=1 + + # Timestamp + f, self.ts_result_var = self._create_result_display(parent, "Timestamp (ms):") + f.grid(row=row_idx, column=1, sticky="ew", padx=5, pady=2) + ttk.Button(parent, text="Get Timestamp", command=lambda: self._run_driver_command(self.driver.get_timestamp, "Timestamp", self.ts_result_var)).grid(row=row_idx, column=0, sticky="w", padx=5, pady=2) + row_idx += 1 + + # Distance v0 + f, self.dist_v0_result_var = self._create_result_display(parent, "Distance v0 (mm):") + f.grid(row=row_idx, column=1, sticky="ew", padx=5, pady=2) + ttk.Button(parent, text="Get Distance (v0)", command=lambda: self._run_driver_command(self.driver.get_distance, "Distance v0", self.dist_v0_result_var, version=0)).grid(row=row_idx, column=0, sticky="w", padx=5, pady=2) + row_idx += 1 + + # Distance v1 + f, self.dist_v1_result_var = self._create_result_display(parent, "Distance v1 (full):") + f.grid(row=row_idx, column=1, sticky="ew", padx=5, pady=2) + ttk.Button(parent, text="Get Distance (v1)", command=lambda: self._run_driver_command(self.driver.get_distance, "Distance v1", self.dist_v1_result_var, version=1)).grid(row=row_idx, column=0, sticky="w", padx=5, pady=2) + row_idx += 1 + + # Attitude v0 + f, self.att_v0_result_var = self._create_result_display(parent, "Attitude v0 (Euler):") + f.grid(row=row_idx, column=1, sticky="ew", padx=5, pady=2) + ttk.Button(parent, text="Get Attitude (v0)", command=lambda: self._run_driver_command(self.driver.get_attitude, "Attitude v0", self.att_v0_result_var, version=0)).grid(row=row_idx, column=0, sticky="w", padx=5, pady=2) + row_idx += 1 + + # Attitude v1 + f, self.att_v1_result_var = self._create_result_display(parent, "Attitude v1 (Quat):") + f.grid(row=row_idx, column=1, sticky="ew", padx=5, pady=2) + ttk.Button(parent, text="Get Attitude (v1)", command=lambda: self._run_driver_command(self.driver.get_attitude, "Attitude v1", self.att_v1_result_var, version=1)).grid(row=row_idx, column=0, sticky="w", padx=5, pady=2) + row_idx += 1 + + # Temperature + f, self.temp_result_var = self._create_result_display(parent, "Temperature (°C):") + f.grid(row=row_idx, column=1, sticky="ew", padx=5, pady=2) + ttk.Button(parent, text="Get Temperature", command=lambda: self._run_driver_command(self.driver.get_temperature, "Temperature", self.temp_result_var)).grid(row=row_idx, column=0, sticky="w", padx=5, pady=2) + row_idx += 1 + + # Chart Data + f, self.chart_result_var = self._create_result_display(parent, "Chart Info:", width=60) + f.grid(row=row_idx, column=1, sticky="ew", padx=5, pady=2) + ttk.Button(parent, text="Get Chart Data", command=lambda: self._run_driver_command(self.driver.get_chart_data, "Chart Data", self.chart_result_var)).grid(row=row_idx, column=0, sticky="w", padx=5, pady=2) + row_idx += 1 + + def _create_device_settings_widgets(self): + """Creates widgets for the 'Device Settings' tab (UART, Transceiver, Sound Speed).""" + parent = self.tab_device_settings + current_row = 0 + + # UART Config + uart_frame = ttk.LabelFrame(parent, text="UART Configuration (ID 0x18)") + uart_frame.grid(row=current_row, column=0, columnspan=3, sticky="ew", padx=5, pady=5, ipady=5) + current_row +=1 + + f_uart_id, self.uart_id_var, _ = self._create_labeled_entry(uart_frame, "UART ID:", "1", 3) + f_uart_id.grid(row=0, column=0, padx=2, pady=2, sticky="w") + f_uart_baud, self.uart_baud_var, _ = self._create_labeled_entry(uart_frame, "Set Baudrate:", "115200") + f_uart_baud.grid(row=1, column=0, padx=2, pady=2, sticky="w") + f_uart_addr, self.uart_new_addr_var, _ = self._create_labeled_entry(uart_frame, "Set New Dev Addr:", "0", 3) + f_uart_addr.grid(row=2, column=0, padx=2, pady=2, sticky="w") + + ttk.Button(uart_frame, text="Set Baudrate (v0)", command=self._set_uart_baud).grid(row=1, column=1, padx=5, pady=2, sticky="ew") + ttk.Button(uart_frame, text="Set Dev Address (v1)", command=self._set_uart_dev_addr).grid(row=2, column=1, padx=5, pady=2, sticky="ew") + + f_uart_get_v, self.uart_get_ver_var, _ = self._create_labeled_entry(uart_frame, "Get Info for Version (0/1):", "0", 2) + f_uart_get_v.grid(row=3, column=0, padx=2, pady=2, sticky="w") + ttk.Button(uart_frame, text="Get UART Config", command=self._get_uart_config).grid(row=3, column=1, padx=5, pady=2, sticky="ew") + + f_uart_res, self.uart_get_result_var = self._create_result_display(uart_frame, "UART Get Result:") + f_uart_res.grid(row=4, column=0, columnspan=2, sticky="ew", padx=5, pady=2) + + # Transceiver Settings + transc_frame = ttk.LabelFrame(parent, text="Transceiver Settings (ID 0x14)") + transc_frame.grid(row=current_row, column=0, columnspan=3, sticky="ew", padx=5, pady=5, ipady=5) + current_row += 1 + + f_freq, self.transc_freq_var, _ = self._create_labeled_entry(transc_frame, "Freq (kHz):", "675") + f_freq.grid(row=0, column=0) + f_pulse, self.transc_pulse_var, _ = self._create_labeled_entry(transc_frame, "Pulse Count:", "10") + f_pulse.grid(row=0, column=1) + self.transc_boost_var = tk.BooleanVar(value=True) + ttk.Checkbutton(transc_frame, text="Boost Enabled", variable=self.transc_boost_var).grid(row=0, column=2) + ttk.Button(transc_frame, text="Set Transceiver Cfg", command=self._set_transceiver_settings).grid(row=1, column=0, columnspan=3, sticky="ew") + + f_transc_get, self.transc_get_result_var = self._create_result_display(transc_frame, "Current Transc:") + f_transc_get.grid(row=2, column=0, columnspan=2, sticky="ew") + ttk.Button(transc_frame, text="Get Transceiver Cfg", command=lambda: self._run_driver_command(self.driver.get_transceiver_settings, "Transceiver Settings", self.transc_get_result_var)).grid(row=2, column=2, sticky="ew") + + # Sound Speed Settings + snd_spd_frame = ttk.LabelFrame(parent, text="Sound Speed (ID 0x15)") + snd_spd_frame.grid(row=current_row, column=0, columnspan=3, sticky="ew", padx=5, pady=5, ipady=5) + current_row += 1 + f_sspd, self.sspd_var, _ = self._create_labeled_entry(snd_spd_frame, "Speed (mm/s):", "1500000") + f_sspd.grid(row=0, column=0) + ttk.Button(snd_spd_frame, text="Set Sound Speed", command=self._set_sound_speed).grid(row=0, column=1) + + f_sspd_get, self.sspd_get_result_var = self._create_result_display(snd_spd_frame, "Current Speed:") + f_sspd_get.grid(row=1, column=0, sticky="ew") + ttk.Button(snd_spd_frame, text="Get Sound Speed", command=lambda: self._run_driver_command(self.driver.get_sound_speed, "Sound Speed", self.sspd_get_result_var)).grid(row=1, column=1) + + def _create_data_settings_widgets(self): + """Creates widgets for the 'Data Config' tab (Dataset, Distance Setup, Chart Setup).""" + parent = self.tab_data_settings + current_row = 0 + + dataset_frame = ttk.LabelFrame(parent, text="Dataset Configuration (ID 0x10)") + dataset_frame.grid(row=current_row, column=0, sticky="ew", padx=5, pady=5, ipady=5) + current_row += 1 + f_ds_ch, self.ds_ch_id_var, _ = self._create_labeled_entry(dataset_frame, "Ch ID (0-2):", "0", 3) + f_ds_ch.grid(row=0, column=0) + f_ds_period, self.ds_period_var, _ = self._create_labeled_entry(dataset_frame, "Period (ms):", "0") + f_ds_period.grid(row=0, column=1) + f_ds_mask, self.ds_mask_var, _ = self._create_labeled_entry(dataset_frame, "Mask (hex):", "0x0") + f_ds_mask.grid(row=0, column=2) + ttk.Button(dataset_frame, text="Set Dataset Cfg", command=self._set_dataset_config).grid(row=1, column=0, columnspan=3, sticky="ew") + + f_ds_get_ch, self.ds_get_ch_id_var, _ = self._create_labeled_entry(dataset_frame, "Get Ch ID:", "0", 3) + f_ds_get_ch.grid(row=2, column=0) + f_ds_get, self.ds_get_result_var = self._create_result_display(dataset_frame, "Current Dataset Cfg:") + f_ds_get.grid(row=3, column=0, columnspan=2, sticky="ew") + ttk.Button(dataset_frame, text="Get Dataset Cfg", command=self._get_dataset_config).grid(row=2, column=1, sticky="ew") + + distsetup_frame = ttk.LabelFrame(parent, text="Distance Setup (ID 0x11)") + distsetup_frame.grid(row=current_row, column=0, sticky="ew", padx=5, pady=5, ipady=5) + current_row += 1 + f_distoff, self.distsetup_offset_var, _ = self._create_labeled_entry(distsetup_frame, "Start Offset (mm):", "0") + f_distoff.grid(row=0, column=0) + f_distmax, self.distsetup_max_var, _ = self._create_labeled_entry(distsetup_frame, "Max Dist (mm):", "50000") + f_distmax.grid(row=0, column=1) + ttk.Button(distsetup_frame, text="Set Distance Setup", command=self._set_distance_setup).grid(row=0, column=2) + + f_distsetup_get, self.distsetup_get_result_var = self._create_result_display(distsetup_frame, "Current Dist Setup:") + f_distsetup_get.grid(row=1, column=0, columnspan=2, sticky="ew") + ttk.Button(distsetup_frame, text="Get Distance Setup", command=lambda: self._run_driver_command(self.driver.get_distance_setup, "Distance Setup", self.distsetup_get_result_var)).grid(row=1, column=2) + + chartsetup_frame = ttk.LabelFrame(parent, text="Chart Setup (ID 0x12)") + chartsetup_frame.grid(row=current_row, column=0, sticky="ew", padx=5, pady=5, ipady=5) + current_row += 1 + f_cs_count, self.cs_count_var, _ = self._create_labeled_entry(chartsetup_frame, "Sample Count:", "1000") + f_cs_count.grid(row=0,column=0) + f_cs_resol, self.cs_resol_var, _ = self._create_labeled_entry(chartsetup_frame, "Sample Resol (mm):", "10") + f_cs_resol.grid(row=0,column=1) + f_cs_offset, self.cs_offset_var, _ = self._create_labeled_entry(chartsetup_frame, "Sample Offset:", "0") + f_cs_offset.grid(row=0,column=2) + ttk.Button(chartsetup_frame, text="Set Chart Setup", command=self._set_chart_setup).grid(row=1, column=0, columnspan=3, sticky="ew") + + f_cs_get, self.cs_get_result_var = self._create_result_display(chartsetup_frame, "Current Chart Setup:") + f_cs_get.grid(row=2, column=0, columnspan=2, sticky="ew") + ttk.Button(chartsetup_frame, text="Get Chart Setup", command=lambda: self._run_driver_command(self.driver.get_chart_setup, "Chart Setup", self.cs_get_result_var)).grid(row=2, column=2) + + def _create_system_widgets(self): + """Creates widgets for the 'System & Flash' tab.""" + parent = self.tab_system + current_row = 0 + + f_ver, self.ver_info_result_var = self._create_result_display(parent, "Version Info:", width=70) + f_ver.grid(row=current_row, column=1, sticky="ew", padx=5, pady=2) + ttk.Button(parent, text="Get Version Info", command=lambda: self._run_driver_command(self.driver.get_version_info, "Version Info", self.ver_info_result_var)).grid(row=current_row, column=0, sticky="w", padx=5, pady=2) + current_row += 1 + + mark_frame = ttk.LabelFrame(parent, text="Device Mark (ID 0x21)") + mark_frame.grid(row=current_row, column=0, columnspan=2, sticky="ew", padx=5, pady=5) + current_row += 1 + self.mark_enable_var = tk.BooleanVar() + ttk.Checkbutton(mark_frame, text="Enable Mark (via ID_MARK SETTING)", variable=self.mark_enable_var).pack(side=tk.LEFT) + ttk.Button(mark_frame, text="Set Mark", command=self._set_mark).pack(side=tk.LEFT, padx=5) + + f_mark_get, self.mark_get_result_var = self._create_result_display(mark_frame, "Mark Status:") + f_mark_get.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) + ttk.Button(mark_frame, text="Get Mark Status", command=lambda: self._run_driver_command(self.driver.get_mark_status, "Mark Status", self.mark_get_result_var)).pack(side=tk.LEFT, padx=5) + + f_diag, self.diag_result_var = self._create_result_display(parent, "Diagnostics:", width=70) + f_diag.grid(row=current_row, column=1, sticky="ew", padx=5, pady=2) + ttk.Button(parent, text="Get Diagnostics", command=lambda: self._run_driver_command(self.driver.get_diagnostics, "Diagnostics", self.diag_result_var)).grid(row=current_row, column=0, sticky="w", padx=5, pady=2) + current_row += 1 + + flash_frame = ttk.LabelFrame(parent, text="Flash Operations (ID 0x23)") + flash_frame.grid(row=current_row, column=0, columnspan=2, sticky="ew", padx=5, pady=5) + current_row += 1 + ttk.Button(flash_frame, text="Save Settings to Flash", command=lambda: self._run_driver_command(self.driver.save_settings_to_flash, "Save Flash")).grid(row=0, column=0, padx=5, pady=2) + ttk.Button(flash_frame, text="Restore Settings from Flash", command=lambda: self._run_driver_command(self.driver.restore_settings_from_flash, "Restore Flash")).grid(row=0, column=1, padx=5, pady=2) + ttk.Button(flash_frame, text="Erase Flash Settings", command=lambda: self._run_driver_command(self.driver.erase_flash_settings, "Erase Flash")).grid(row=0, column=2, padx=5, pady=2) + + boot_frame = ttk.LabelFrame(parent, text="Boot Operations (ID 0x24)") + boot_frame.grid(row=current_row, column=0, columnspan=2, sticky="ew", padx=5, pady=5) + current_row += 1 + ttk.Button(boot_frame, text="Reboot Device", command=self._reboot_device).grid(row=0, column=0, padx=5, pady=2) + ttk.Button(boot_frame, text="Run FW from Bootloader", command=lambda: self._run_driver_command(self.driver.run_firmware_from_bootloader, "Run FW")).grid(row=0, column=1, padx=5, pady=2) + + imu_frame = ttk.LabelFrame(parent, text="IMU Calibration (ID 0x1B - In Dev)") + imu_frame.grid(row=current_row, column=0, columnspan=2, sticky="ew", padx=5, pady=5) + current_row +=1 + self.imu_gyro_var = tk.BooleanVar() + ttk.Checkbutton(imu_frame, text="Calibrate Gyro", variable=self.imu_gyro_var).pack(side=tk.LEFT, padx=5) + self.imu_accel_var = tk.BooleanVar() + ttk.Checkbutton(imu_frame, text="Calibrate Accel", variable=self.imu_accel_var).pack(side=tk.LEFT, padx=5) + ttk.Button(imu_frame, text="Start IMU Calibration", command=self._set_imu_calibration).pack(side=tk.LEFT, padx=5) + + fw_frame = ttk.LabelFrame(parent, text="Firmware Update (ID 0x25 - Simplified)") + fw_frame.grid(row=current_row, column=0, columnspan=2, sticky="ew", padx=5, pady=5) + current_row += 1 + f_fw_pkt, self.fw_pkt_num_var, _ = self._create_labeled_entry(fw_frame, "Packet #:", "1", 5) + f_fw_pkt.pack(side=tk.LEFT) + f_fw_data, self.fw_data_var, _ = self._create_labeled_entry(fw_frame, "Data (hex):", "", 40) + f_fw_data.pack(side=tk.LEFT) + ttk.Button(fw_frame, text="Upload FW Chunk", command=self._upload_fw_chunk).pack(side=tk.LEFT, padx=5) + + def _create_navigation_widgets(self): + """Creates widgets for the 'Navigation' tab.""" + parent = self.tab_navigation + current_row = 0 + + f_nav, self.nav_result_var = self._create_result_display(parent, "Navigation Data:", width=60) + f_nav.grid(row=current_row, column=1, sticky="ew", padx=5, pady=2) + ttk.Button(parent, text="Get Nav Data", command=lambda: self._run_driver_command(self.driver.get_navigation_data, "Nav Data", self.nav_result_var)).grid(row=current_row, column=0, sticky="w", padx=5, pady=2) + current_row += 1 + + f_dvl, self.dvl_result_var = self._create_result_display(parent, "DVL Velocity:", width=70) + f_dvl.grid(row=current_row, column=1, sticky="ew", padx=5, pady=2) + ttk.Button(parent, text="Get DVL Velocity", command=lambda: self._run_driver_command(self.driver.get_dvl_velocity_data, "DVL Velocity", self.dvl_result_var)).grid(row=current_row, column=0, sticky="w", padx=5, pady=2) + current_row += 1 + + # --- Driver Interaction & Threading --- + def _toggle_connection(self): + """Toggles the device connection state (connect or disconnect).""" + if self.driver and self.driver.serial_conn and hasattr(self.driver.serial_conn, 'is_open') and self.driver.serial_conn.is_open: + self._disconnect_driver() + else: + self._connect_driver() + + def _connect_driver(self): + """Initiates connection to the device in a separate thread.""" + port = self.port_var.get() + if not port: + messagebox.showerror("Error", "Serial port cannot be empty.") + return + try: + baud = int(self.baud_var.get()) + addr = int(self.addr_var.get()) + except ValueError: + messagebox.showerror("Error", "Baud rate and Address must be integers.") + return + + self.driver = KoggerSBPDevice(port, baud, addr, default_timeout=1.5) + self.driver.register_default_callback(self._handle_unsolicited_from_driver) + self.connection_status_var.set("Status: Connecting...") + self.connect_button.config(text="Connecting...", state=tk.DISABLED) + threading.Thread(target=self._connect_thread_target, daemon=True).start() + + def _connect_thread_target(self): + """Target function for the connection thread. Handles actual connection attempt.""" + if self.driver.connect(): + self.command_queue.put(("status_update", ("Status: Connected", "green", "Disconnect"))) + else: + self.command_queue.put(("status_update", ("Status: Failed to Connect", "red", "Connect"))) + self.driver = None + + def _disconnect_driver(self): + """Initiates disconnection from the device in a separate thread.""" + if self.driver: + logger.info("GUI: Disconnecting driver...") + if hasattr(self.driver, 'unregister_default_callback'): + self.driver.unregister_default_callback() + + self.connect_button.config(text="Disconnecting...", state=tk.DISABLED) + threading.Thread(target=self._disconnect_thread_target, daemon=True).start() + else: + logger.info("GUI: Driver not initialized, cannot disconnect.") + + def _disconnect_thread_target(self): + """Target function for the disconnection thread.""" + if self.driver: + self.driver.disconnect() + self.driver = None + self.command_queue.put(("status_update", ("Status: Disconnected", "black", "Connect"))) + + + def _run_driver_command(self, driver_method_ref, command_name, result_var_target=None, *args, **kwargs): + """ + Executes a given driver method in a separate thread. + Posts its result to the GUI's command queue for UI updates. + + :param driver_method_ref: The method of the `KoggerSBPDevice` instance to call. + :param command_name: A descriptive name for the command (for logging/display). + :param result_var_target: The Tkinter StringVar to update with the result. + :param args: Positional arguments for the driver method. + :param kwargs: Keyword arguments for the driver method. + """ + if not (self.driver and self.driver.serial_conn and hasattr(self.driver.serial_conn, 'is_open') and self.driver.serial_conn.is_open): + logger.warning(f"Cannot run '{command_name}': Not connected.") + messagebox.showwarning("Not Connected", "Please connect to the device first.") + if result_var_target: result_var_target.set("Error: Not Connected") + return + + logger.info(f"GUI: Queuing execution for: {command_name} with args: {args}, kwargs: {kwargs}") + if result_var_target: result_var_target.set("Executing...") + + thread = threading.Thread(target=self._command_thread_target, + args=(driver_method_ref, command_name, result_var_target) + args, + kwargs=kwargs, daemon=True) + thread.start() + + def _command_thread_target(self, driver_method_ref, command_name, result_var_target, *args, **kwargs): + """ + Target function for the thread that executes a single driver command. + Posts the result or error to the GUI's command queue. + """ + try: + result = driver_method_ref(*args, **kwargs) + self.command_queue.put((command_name, result, result_var_target)) + except Exception as e: + logger.error(f"Exception in driver command thread for '{command_name}': {e}", exc_info=True) + self.command_queue.put((command_name, f"Exception: {type(e).__name__}", result_var_target)) + + def _process_command_queue(self): + """Processes results and status updates from the command queue to update the GUI.""" + try: + while True: + message_type, data, target_var = self.command_queue.get_nowait() + + if message_type == "status_update": + status_text, _, button_text = data + self.connection_status_var.set(status_text) + self.connect_button.config(text=button_text, state=tk.NORMAL) + logger.info(f"GUI: Connection status updated to '{status_text}'") + elif message_type == "get_all_finished": + if hasattr(self, 'get_all_button'): # Check if button exists + self.get_all_button.config(state=tk.NORMAL) + logger.info("GUI: 'Get All Available Data' sequence finished.") + elif message_type == "set_fetching_status": # New message type for "Get All" + # data is command_name_desc, target_var is the actual StringVar + command_name_desc = data + if target_var and isinstance(target_var, tk.StringVar): + target_var.set("Fetching...") + logger.debug(f"GUI: Set fetching status for {command_name_desc}") + else: + logger.warning(f"GUI: Invalid target_var for set_fetching_status of {command_name_desc}") + + else: # Assumed to be a command result + command_name = message_type + result = data + result_var_target = target_var + + logger.info(f"GUI: Received result for '{command_name}': {str(result)[:100]}") + if result_var_target and isinstance(result_var_target, tk.StringVar): + display_str = "N/A" + if isinstance(result, dict): + items = [] + for k, v_item in result.items(): + if isinstance(v_item, bytes): items.append(f"{k}: {v_item.hex().upper()[:20]}{'...' if len(v_item)>10 else ''}") + elif isinstance(v_item, float): items.append(f"{k}: {v_item:.3f}") + else: items.append(f"{k}: {v_item}") + display_str = ", ".join(items) + if len(display_str) > 70: display_str = display_str[:67] + "..." + elif isinstance(result, bool): display_str = "Success" if result else "Failed/False" + elif result is None: display_str = "No data / Failed / Timeout" + elif isinstance(result, (str, bytes)): display_str = result if isinstance(result, str) else result.hex().upper() + else: display_str = str(result) + result_var_target.set(display_str) + + if result is True: logger.success(f"Command '{command_name}' reported success (True).") + elif result is False: logger.warning(f"Command '{command_name}' reported failure (False).") + elif result is None: logger.warning(f"Command '{command_name}' returned None (no data or error).") + except queue.Empty: + pass + except Exception as e: + logger.error(f"Error processing command queue: {e}", exc_info=True) + finally: + if self.root.winfo_exists(): + self.root.after(100, self._process_command_queue) + + def _handle_unsolicited_from_driver(self, frame): + """ Puts unsolicited messages (received by driver's callback) into a GUI-thread-safe queue. """ + self.unsolicited_log_queue.put(frame) + + def _process_unsolicited_log_queue(self): + """Processes unsolicited messages from its queue and logs them via Loguru (to Tkinter widget).""" + try: + while True: + frame = self.unsolicited_log_queue.get_nowait() + logger.info(f"[UNSOLICITED In GUI] ID: {frame['id']:#02x}, Mode: {frame['mode']:#02x}, Payload: {frame['payload'].hex().upper()}") + except queue.Empty: + pass + finally: + if self.root.winfo_exists(): + self.root.after(100, self._process_unsolicited_log_queue) + + def _on_closing(self): + """Handles the event when the main window is closed by the user.""" + logger.info("Application closing initiated by user...") + if self.driver and self.driver.serial_conn and hasattr(self.driver.serial_conn, 'is_open') and self.driver.serial_conn.is_open: + logger.info("Attempting to disconnect driver on close...") + self._disconnect_driver() # This is now threaded, main window might close before it finishes + + self.root.destroy() + logger.info("Application window destroyed.") # This log might go to console only + + # --- Specific Command Handlers for SET operations & "Get All" --- + def _set_uart_baud(self): + """Handles 'Set UART Baudrate' button click. Parses inputs and calls driver.""" + if not self.driver: messagebox.showerror("Error", "Not connected!"); return + try: + uart_id = int(self.uart_id_var.get()) + baud = int(self.uart_baud_var.get()) + self._run_driver_command(self.driver.set_uart_config, "Set UART Baudrate", + uart_id=uart_id, baudrate=baud, new_dev_address=None) + except ValueError: messagebox.showerror("Input Error", "UART ID and Baudrate must be integers.") + + def _set_uart_dev_addr(self): + """Handles 'Set UART Device Address' button click. Parses inputs and calls driver.""" + if not self.driver: messagebox.showerror("Error", "Not connected!"); return + try: + uart_id = int(self.uart_id_var.get()) + addr = int(self.uart_new_addr_var.get()) + if not (0 <= addr <= 15): + messagebox.showerror("Input Error", "Device Address must be between 0 and 15.") + return + self._run_driver_command(self.driver.set_uart_config, "Set UART Device Address", + uart_id=uart_id, new_dev_address=addr) + except ValueError: messagebox.showerror("Input Error", "UART ID and New Address must be integers.") + + def _get_uart_config(self): + """Handles 'Get UART Config' button click. Parses inputs and calls driver.""" + if not self.driver: messagebox.showerror("Error", "Not connected!"); return + try: + uart_id = int(self.uart_id_var.get()) + version_str = self.uart_get_ver_var.get() + version = int(version_str) if version_str else 0 # Default to version 0 if empty + if version not in [0,1]: + messagebox.showerror("Input Error", "Version must be 0 or 1.") + return + self._run_driver_command(self.driver.get_uart_config, f"Get UART Config v{version}", self.uart_get_result_var, + uart_id=uart_id, version=version) + except ValueError: messagebox.showerror("Input Error", "UART ID and Version must be integers.") + + def _set_transceiver_settings(self): + """Handles 'Set Transceiver Config' button click.""" + if not self.driver: messagebox.showerror("Error", "Not connected!"); return + try: + freq = int(self.transc_freq_var.get()) + pulse = int(self.transc_pulse_var.get()) + boost = self.transc_boost_var.get() + self._run_driver_command(self.driver.set_transceiver_settings, "Set Transceiver Settings", + frequency_khz=freq, pulse_count=pulse, boost_enabled=boost) + except ValueError: messagebox.showerror("Input Error", "Frequency and Pulse Count must be integers.") + + def _set_sound_speed(self): + """Handles 'Set Sound Speed' button click.""" + if not self.driver: messagebox.showerror("Error", "Not connected!"); return + try: + speed = int(self.sspd_var.get()) + self._run_driver_command(self.driver.set_sound_speed, "Set Sound Speed", sound_speed_mm_s=speed) + except ValueError: messagebox.showerror("Input Error", "Sound speed must be an integer.") + + def _set_dataset_config(self): + """Handles 'Set Dataset Config' button click.""" + if not self.driver: messagebox.showerror("Error", "Not connected!"); return + try: + ch_id = int(self.ds_ch_id_var.get()) + period = int(self.ds_period_var.get()) + mask_str = self.ds_mask_var.get() + mask = int(mask_str, 16) if mask_str.lower().startswith("0x") else int(mask_str) + self._run_driver_command(self.driver.set_dataset_config, "Set Dataset Config", + channel_id=ch_id, channel_period_ms=period, channel_mask=mask) + except ValueError: messagebox.showerror("Input Error", "Channel ID, Period, and Mask must be valid integers (Mask can be hex '0x...').") + + def _get_dataset_config(self): + """Handles 'Get Dataset Config' button click.""" + if not self.driver: messagebox.showerror("Error", "Not connected!"); return + try: + ch_id_str = self.ds_get_ch_id_var.get() + ch_id = int(ch_id_str) if ch_id_str else 0 # Default to 0 if empty + self._run_driver_command(self.driver.get_dataset_config, "Get Dataset Config", self.ds_get_result_var, + channel_id_to_request=ch_id) + except ValueError: messagebox.showerror("Input Error", "Channel ID must be an integer.") + + def _set_distance_setup(self): + """Handles 'Set Distance Setup' button click.""" + if not self.driver: messagebox.showerror("Error", "Not connected!"); return + try: + offset = int(self.distsetup_offset_var.get()) + max_dist = int(self.distsetup_max_var.get()) + self._run_driver_command(self.driver.set_distance_setup, "Set Distance Setup", + start_offset_mm=offset, max_distance_mm=max_dist) + except ValueError: messagebox.showerror("Input Error", "Offset and Max Distance must be integers.") + + def _set_chart_setup(self): + """Handles 'Set Chart Setup' button click.""" + if not self.driver: messagebox.showerror("Error", "Not connected!"); return + try: + count = int(self.cs_count_var.get()) + resol = int(self.cs_resol_var.get()) + offset = int(self.cs_offset_var.get()) + self._run_driver_command(self.driver.set_chart_setup, "Set Chart Setup", + sample_count=count, sample_resolution_mm=resol, sample_offset_num=offset) + except ValueError: messagebox.showerror("Input Error", "Sample Count, Resolution, and Offset must be integers.") + + def _set_mark(self): + """Handles 'Set Mark' button click.""" + if not self.driver: messagebox.showerror("Error", "Not connected!"); return + enable = self.mark_enable_var.get() + if enable: + self._run_driver_command(self.driver.set_mark, "Set Mark (Request Enable)", result_var_target=self.mark_get_result_var, enable_mark=True) + else: + logger.warning("GUI: 'Set Mark' (disable) requested via ID_MARK checkbutton. Protocol docs suggest ID_MARK SETTING command is for setting the mark. Disabling the mark is usually done by host sending a normal command with the MODE.Mark bit cleared.") + messagebox.showinfo("Info", "The ID_MARK SETTING command primarily sets the mark. To clear it, the host typically sends any frame with the mark bit cleared in its MODE field.") + + def _reboot_device(self): + """Handles 'Reboot Device' button click with confirmation.""" + if not (self.driver and self.driver.serial_conn and hasattr(self.driver.serial_conn, 'is_open') and self.driver.serial_conn.is_open): + messagebox.showwarning("Not Connected", "Please connect to the device first.") + return + if messagebox.askyesno("Confirm Reboot", "Are you sure you want to reboot the device? This will disconnect."): + logger.info("GUI: Initiating device reboot...") + self.connect_button.config(text="Rebooting...", state=tk.DISABLED) + threading.Thread(target=self._reboot_thread_target, daemon=True).start() + + def _reboot_thread_target(self): + """Target function for the reboot operation thread.""" + success = False + if self.driver: + success = self.driver.reboot_device() + + if success: # driver.reboot_device() calls disconnect internally + self.command_queue.put(("status_update", ("Status: Rebooted. Disconnected.", "black", "Connect"))) + else: + self.command_queue.put(("status_update", ("Status: Reboot Failed", "red", "Connect"))) + + def _set_imu_calibration(self): + """Handles 'Start IMU Calibration' button click.""" + if not self.driver: messagebox.showerror("Error", "Not connected!"); return + gyro = self.imu_gyro_var.get() + accel = self.imu_accel_var.get() + if not gyro and not accel: + messagebox.showinfo("IMU Calibration", "No calibration type selected (Gyro or Accelerometer).") + return + self._run_driver_command(self.driver.set_imu_calibration, "IMU Calibration", + calibrate_gyro=gyro, calibrate_accelerometer=accel) + + def _upload_fw_chunk(self): + """Handles 'Upload FW Chunk' button click.""" + if not self.driver: messagebox.showerror("Error", "Not connected!"); return + try: + pkt_num_str = self.fw_pkt_num_var.get() + if not pkt_num_str: messagebox.showerror("Input Error", "Packet number is required."); return + pkt_num = int(pkt_num_str) + + data_hex = self.fw_data_var.get() + if not data_hex: + messagebox.showerror("Input Error", "Firmware data (hex) cannot be empty.") + return + data_bytes = bytes.fromhex(data_hex) + self._run_driver_command(self.driver.upload_firmware_update_chunk, "Upload FW Chunk", + packet_number=pkt_num, update_data_chunk=data_bytes) + except ValueError: + messagebox.showerror("Input Error", "Packet number must be int, data must be valid hex string (e.g., AABBCCDD).") + except Exception as e: + messagebox.showerror("Error", f"Failed to prepare FW chunk: {e}") + + + def _get_all_data_sequentially(self): + """ + Handles the 'Get All Available Data' button click. + Initiates a sequence of all implemented 'get_*' commands in a separate thread. + """ + if not (self.driver and self.driver.serial_conn and hasattr(self.driver.serial_conn, 'is_open') and self.driver.serial_conn.is_open): + messagebox.showwarning("Not Connected", "Please connect to the device first.") + return + + logger.info("GUI: Starting 'Get All Available Data' sequence...") + if hasattr(self, 'get_all_button'): # Ensure button exists + self.get_all_button.config(state=tk.DISABLED) + + threading.Thread(target=self._get_all_data_thread_target, daemon=True).start() + + def _get_all_data_thread_target(self): + """ + Target function for the 'Get All Data' thread. + Sequentially calls driver methods and posts results to the command queue. + """ + # Ensure all these result_vars are defined in your _create_..._widgets methods + # Default UART ID and Dataset Channel ID for "Get All" + uart_id_for_get_all = int(self.uart_id_var.get() or "1") + dataset_ch_id_for_get_all = int(self.ds_get_ch_id_var.get() or "0") + + commands_to_run_config = [ + # (Driver Method, "Command Name for Log", Target StringVar, (args_tuple), {kwargs_dict}) + (self.driver.get_timestamp, "Timestamp (All)", self.ts_result_var, (), {}), + (self.driver.get_distance, "Distance v0 (All)", self.dist_v0_result_var, (0,), {}), + (self.driver.get_distance, "Distance v1 (All)", self.dist_v1_result_var, (1,), {}), + (self.driver.get_chart_data, "Chart Data (All)", self.chart_result_var, (), {}), + (self.driver.get_attitude, "Attitude v0 (All)", self.att_v0_result_var, (0,), {}), + (self.driver.get_attitude, "Attitude v1 (All)", self.att_v1_result_var, (1,), {}), + (self.driver.get_temperature, "Temperature (All)", self.temp_result_var, (), {}), + (self.driver.get_dataset_config, "Dataset Cfg (All)", self.ds_get_result_var, (dataset_ch_id_for_get_all,), {}), + (self.driver.get_distance_setup, "Distance Setup (All)", self.distsetup_get_result_var, (), {}), + (self.driver.get_chart_setup, "Chart Setup (All)", self.cs_get_result_var, (), {}), + (self.driver.get_transceiver_settings, "Transceiver Cfg (All)", self.transc_get_result_var, (), {}), + (self.driver.get_sound_speed, "Sound Speed (All)", self.sspd_get_result_var, (), {}), + (self.driver.get_uart_config, "UART Cfg v0 (All)", self.uart_get_result_var, (uart_id_for_get_all, 0), {}), + (self.driver.get_uart_config, "UART Cfg v1 (All)", self.uart_get_result_var, (uart_id_for_get_all, 1), {}), + (self.driver.get_version_info, "Version Info (All)", self.ver_info_result_var, (), {}), + (self.driver.get_mark_status, "Mark Status (All)", self.mark_get_result_var, (), {}), + (self.driver.get_diagnostics, "Diagnostics (All)", self.diag_result_var, (), {}), + (self.driver.get_navigation_data, "Nav Data (All)", self.nav_result_var, (), {}), + (self.driver.get_dvl_velocity_data, "DVL Velocity (All)", self.dvl_result_var, (), {}), + ] + + for method_ref, cmd_name, res_var, args, kwargs in commands_to_run_config: + if hasattr(self.root, '_already_closed') and self.root._already_closed: # Check if window is closing + logger.info("'Get All' sequence aborted as window is closing.") + break + if not (self.driver and self.driver.serial_conn and hasattr(self.driver.serial_conn, 'is_open') and self.driver.serial_conn.is_open): + logger.warning("'Get All' sequence aborted: Not connected.") + break + + logger.info(f"GUI 'Get All': Requesting '{cmd_name}'...") + # Send a message to GUI to update status to "Fetching..." for this specific res_var + self.command_queue.put(("set_fetching_status", cmd_name, res_var)) + + actual_result = None + try: + # This is the blocking call to the driver method. + # The driver method itself uses _execute_command which handles its own threading for send/receive. + # This _get_all_data_thread_target thread will wait here until method_ref completes. + actual_result = method_ref(*args, **kwargs) + except Exception as e: + logger.error(f"Exception during 'Get All' for '{cmd_name}': {e}", exc_info=True) + actual_result = f"Exception: {type(e).__name__}" + + # Put the actual result (or error string) onto the queue for GUI update + self.command_queue.put((cmd_name, actual_result, res_var)) + + time.sleep(0.35) # Pause slightly longer between actual device commands + + # Signal completion to re-enable the button + self.command_queue.put(("get_all_finished", None, None)) + + +if __name__ == "__main__": + main_window = tk.Tk() + app = KoggerGuiApp(main_window) + try: + main_window.mainloop() + except Exception as e: + # This is a last resort if mainloop itself or _on_closing has an issue + print(f"CRITICAL GUI ERROR during mainloop or close: {e}") + if 'logger' in globals() or 'logger' in locals(): # Check if logger might exist + try: + logger.critical(f"Tkinter mainloop crashed or error during close: {e}", exc_info=True) + except: # logger itself might fail if stderr is redirected weirdly on crash + print("Logger failed during critical GUI error.") + # It's often good practice to re-raise or sys.exit(1) after such an error + # raise + diff --git a/driver/kogger_protocol_driver.py b/driver/kogger_protocol_driver.py new file mode 100644 index 0000000..8de8a3f --- /dev/null +++ b/driver/kogger_protocol_driver.py @@ -0,0 +1,1509 @@ +#! /usr/bin/env python + +import serial +import struct +import time +import threading +import queue +from loguru import logger +import sys # For logger setup in main +import json # for easy print +import math # calculate distance with angles +import datetime # For timestamping CSV logs +import os +try: + from .simulation_kogger import simu_serial as simu +except Exception as e: + print("Can't import simu:"+str(e)+", try other way") +try: + from simulation_kogger import simu_serial as simu +except Exception as e: + print("Can't import simu:"+str(e)) + +SAVE_CSV = True + +class CsvLogger: + def __init__(self, filename): + self.filename = filename + self.filepath = "log/" + filename + self.file = None + self.lock = threading.Lock() + self._open_file() + + def _open_file(self): + try: + # Get the directory from the filepath + log_dir = os.path.dirname(self.filepath) + + # Create the directory if it doesn't exist + if log_dir: # Ensure log_dir is not an empty string + os.makedirs(log_dir, exist_ok=True) + + self.file = open(self.filepath, 'a', buffering=1) # Line buffered + logger.info(f"CSV logging enabled to {self.filepath}") + except Exception as e: + logger.error(f"Failed to open CSV log file {self.filepath}: {e}") + self.file = None + + def log(self, direction, message_bytes): + if self.file: + with self.lock: + timestamp = datetime.datetime.now().isoformat(sep=' ', timespec='microseconds') + # Escape double quotes in the message bytes representation + escaped_message = str(message_bytes).replace('"', '""') + self.file.write(f'{timestamp},{direction},"' + escaped_message + '"\n') + + def __del__(self): + if self.file and not self.file.closed: + self.file.close() + +def setup_logging(level="INFO", file=""): + """ + Configures the module's logger. + This function can be called from an external script to set the desired log level. + :param level: The logging level to set (e.g.,"TRACE"<"DEBUG"<"INFO"<"SUCCESS"<"WARNING"<"ERROR"<"CRITICAL"). + :param file: The file path to save logs. If empty, logs are not saved to a file. + If file is not defined, it doesn't create a new file, and use the old one + If the level is the same as the current one, it does nothing + """ + current_file="" + current_level=-1 + for handler_id, handler in logger._core.handlers.items(): + # The '_name' attribute usually holds the string path for file sinks + if isinstance(handler._name, str) and "/" in handler._name: + current_file = handler._name + current_level= handler._levelno + if file != current_file or logger.level(level).no!=current_level: + logger.remove() + logger.add(sys.stdout, level=level, format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}") + if file != "": + logger.add(file, level=level, format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}") + logger.info(f"Kogger Protocol Driver: Loguru logging configured to level {level} and file {file}.") + else: + logger.info(f"Kogger Protocol Driver: Loguru logging configured to level {level}.") + +# Protocol Constants +SYNC1 = 0xBB +SYNC2 = 0x55 +KEY_CONFIRM = 0xC96B5D4A + +# Command IDs from PDF KS_SBP_100 Rev 3.0.7, Page 5 +# Measurement data +ID_TIMESTAMP = 0x01 # Timestamp +ID_DIST = 0x02 # Distance data +ID_CHART = 0x03 # Chart data in reflection patterns +ID_ATTITUDE = 0x04 # Attitude +ID_TEMP = 0x05 # Temperature data + +# Settings data +ID_DATASET = 0x10 # Dataset management for automatic output +ID_DIST_SETUP = 0x11 # Detection Settings to Get Distance +ID_CHART_SETUP = 0x12 # Chart Settings +ID_DSP = 0x13 # DSP settings (No detailed spec in PDF beyond listing) +ID_TRANSC = 0x14 # Transceiver settings +ID_SND_SPD = 0x15 # Sound speed settings +ID_PIN = 0x16 # Pin functions settings (No detailed spec in PDF beyond listing) +ID_BUS = 0x17 # Bus settings (No detailed spec in PDF beyond listing) +ID_UART = 0x18 # UART settings +ID_I2C = 0x19 # I2C settings (No detailed spec in PDF beyond listing) +ID_CAN = 0x1A # CAN settings (No detailed spec in PDF beyond listing) +ID_IMU_SETUP = 0x1B # IMU settings (In developing) + +# System +ID_VERSION = 0x20 # Software and hardware version information (In developing) +ID_MARK = 0x21 # Setting the mark of continuous work (non-reboot) device +ID_DIAG = 0x22 # Diagnostic data (In developing) +ID_FLASH = 0x23 # Work with built-in non-volatile memory +ID_BOOT = 0x24 # Boot device +ID_UPDATE = 0x25 # Firmware update + +# Navigation & Signal +ID_NAV = 0x64 # Navigation data (Latitude, Longitude, Accuracy) +ID_SIGNAL_ENCODER = 0xf6 #old 0x66 # Signal Encoder Data +ID_SIGNAL_DECODER = 0xf7 #old 0x67 # Signal Decoder Data +ID_USBL_SOLUTION = 0x65 # USBL Solution Data +ID_MODEM_SOLUTION = 0x66 # Modem Solution Data +ID_USBL_CONTROL = 0x68 # USBL Acoustic Control (Pinging and Auto-Response) +ID_MODEM_CONTROL = 0x69 # USBL modem control +ID_DVL_VEL = 0x79 # DVL Velocity data (0x79 decimal 121) + + + +# MODE field - Type (Bits 0:1) +TYPE_RESERVED = 0 +TYPE_CONTENT = 1 +TYPE_SETTING = 2 +TYPE_GETTING = 3 + +# MODE field - Response (Bit 7) +RESPONSE_REQUEST_FLAG = 1 + +# RESP Codes (Response codes from device) +RESP_NONE = 0 +RESP_OK = 1 +RESP_ERR_CHECKSUM = 2 +RESP_ERR_PAYLOAD = 3 +RESP_ERR_ID = 4 +RESP_ERR_VERSION = 5 +RESP_ERR_TYPE = 6 +RESP_ERR_KEY = 7 +RESP_ERR_RUNTIME = 8 + +class KoggerSBPDevice: + """ + Python driver for the Kogger Serial Binary Protocol (SBP). + + This class implements methods to interact with a Kogger device + using the SBP, including sending commands, receiving responses, + and handling unsolicited messages via a separate reader thread. + + Implemented based on "Serial Binary Protocol (SBP) specification", + Document Number: KS_SBP_100, Revision: 3.0.7. + + Note on commands without detailed specs in PDF (ID_DSP, ID_PIN, ID_BUS, ID_I2C, ID_CAN): + Constants for these IDs are defined, but corresponding methods are not implemented + due to lack of payload structure information in the provided PDF. + """ + def __init__(self, port, baudrate=921600, device_address=0x0, default_timeout=1.0, serial_read_timeout=0.1, timestamp=None, vehicleName=None, save_csv=SAVE_CSV, log_level="NULL", log_file=""): + """ + Initializes the Kogger SBP device interface. + + :param port: Serial port (e.g., 'COM3' on Windows, '/dev/ttyUSB0' on Linux), or None if using a mock. + :param baudrate: Baud rate for serial communication (default 921600). + :param device_address: Device address (0-15, default 0x0). + :param default_timeout: Default timeout in seconds for waiting for solicited command responses. + :param serial_read_timeout: Timeout in seconds for individual serial read operations in the reader thread. + :param timestamp: timestamp used for filename log + :param vehicleName: name put in filename log + :param save_csv: True:Save log file + :param log_level: The logging level to set (e.g.,"TRACE"<"DEBUG"<"INFO"<"SUCCESS"<"WARNING"<"ERROR"<"CRITICAL" or "NULL" to disable). + """ + self.port = port + self.baudrate = baudrate + self.device_address = device_address & 0x0F + self.serial_conn = None + self.default_timeout = default_timeout + self.serial_read_timeout = serial_read_timeout + + self._reader_thread = None + self._stop_event = threading.Event() + self._response_handlers = {} + self._response_lock = threading.Lock() + + self._callbacks = {} + self._precallbacks = {} + self._default_callback = None + self._nmea_callback = None + self._unsolicited_queue = queue.Queue() + if timestamp == None: + timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + if vehicleName == None: + vehicleName = "AUV" + self._vehicleType = vehicleName[:3] + self.usbl_data = dict() + self._receive_buffer = bytearray() + self._usbl_filter_echo = -1 # Set time.time() when ping sent + self._usbl_filter_echo_enable = True # Set True = enable echo filter, False = disabled + self._usbl_filter_echo_orig_received = False # Set False = first message not received, True=Message received + # Data for synchronization slot + self._sync_slot_total = 0 + self._sync_slot_index = 0 + self._sync_slot_duration = 0 + self._sync_enable_delay = False + # Threading control variables + self._send_lock = threading.Lock() + self._is_sending = False + self._send_last_response = True # First set to True, need the second to get the real value + + # Stores the latest data to be sent + self._latest_address = 255 + self._latest_timeout = 0 + + if log_level != "NULL": + setup_logging(level=log_level, file=log_file) + logger.info(f"KoggerSBPDevice configured for port {self.port}, baudrate {self.baudrate}, address {self.device_address}") + self._csv_logger = None + if save_csv: + CSV_LOG_FILE = timestamp + "_" + vehicleName + "_usbl.csv" + self._csv_logger = CsvLogger(CSV_LOG_FILE) + + def _calculate_fletcher16(self, data_bytes): + """ + Calculates Fletcher-16 checksum for a byte array. + + :param data_bytes: Bytes over which checksum is calculated (ROUTE, MODE, ID, LENGTH, PAYLOAD). + :return: Tuple (CHECK1, CHECK2). + """ + check1 = 0 + check2 = 0 + for byte_val in data_bytes: + check1 = (check1 + byte_val) % 256 + check2 = (check2 + check1) % 256 + return check1, check2 + + def _wait_until_modulo_slot_precise( + self, + slot_total: int, + slot_index: int, + slot_duration: float, + enable_sleep: bool = True, + spin_buffer: float = 0.002 + ): + """ + Waits precisely until the specific slot index is reached. + Args: + slot_total (int): The total number of slots in one full cycle. + slot_index (int): The specific slot to wait for (0 to slot_total-1). + slot_duration (float): The max delay between 2 slots (duration of 1 slot). + enable_sleep (bool): If False, uses 100% CPU busy-wait (extreme precision). + spin_buffer (float): Time in seconds to switch from sleep to busy-wait. + """ + if slot_duration == 0: + return -1 + # 1. Calculate the full cycle parameters based on your inputs + # The "modulo" is now the total time of all slots combined + full_cycle_duration = slot_duration * slot_total + # The "offset" is where your specific slot begins + target_offset = slot_duration * slot_index + now = time.time() + + # 2. Determine where we are in the current cycle + current_pos = now % full_cycle_duration + + # 3. Calculate time remaining to reach the target offset + # The logic handles wrap-around automatically + wait_time = (target_offset - current_pos) % full_cycle_duration + target_time = now + wait_time + if enable_sleep == False: + return target_time + + # 4. Hybrid Wait Strategy + # Only sleep if the wait is significant enough (longer than buffer) + if wait_time > spin_buffer: + time.sleep(wait_time - spin_buffer) + + # 5. Precision Spin (Busy Wait) + # This loop burns CPU for the final milliseconds to ensure accuracy + while True: + if time.time() >= target_time: + break + return target_time + + def connect(self): + """ + Establishes the serial connection and starts the dedicated reader thread. + + :return: True if connection is successful, False otherwise. + """ + if self.serial_conn and hasattr(self.serial_conn, 'is_open') and self.serial_conn.is_open: # Check for mock port compatibility + logger.info("Already connected.") + return True + try: + if self.port is None and not (hasattr(self.serial_conn, 'is_open') and self.serial_conn.is_open): # For mock port assignment + logger.info("Serial port is None, connecting to simulation mock.") + self.serial_conn = simu.Serial(self.port, self.baudrate, timeout=self.serial_read_timeout) + if self.port is not None: # Only create serial.Serial if port is specified (not a mock) + self.serial_conn = serial.Serial(self.port, self.baudrate, timeout=self.serial_read_timeout) + if not (hasattr(self.serial_conn, 'is_open') and self.serial_conn.is_open): # If still not open (e.g. mock not opened) + logger.error("Connection object is not open after attempting to initialize.") + return False + self._stop_event.clear() + self._reader_thread = threading.Thread(target=self._reader_thread_loop, daemon=True) + self._reader_thread.start() + display_port = self.port if self.port is not None else getattr(self.serial_conn, 'port', 'mock port') + logger.success(f"Successfully connected to {display_port} at {self.baudrate} and started reader thread.") + return True + except serial.SerialException as e: + logger.error(f"Error connecting to {self.port}: {e}") + self.serial_conn = None + return False + except AttributeError as e: + logger.error(f"AttributeError during connect: {e}. This might happen if 'port' is None and 'serial_conn' was not pre-assigned for testing.") + return False + except Exception as e: # Catch other potential errors + logger.error(f"An unexpected error occurred during connect: {e}", exc_info=True) + self.serial_conn = None + return False + + def disconnect(self): + """ + Stops the reader thread and closes the serial connection gracefully. + """ + # Determine if we are trying to disconnect a non-existent or already closed connection + was_connected = False + if self.serial_conn: + if hasattr(self.serial_conn, 'is_open') and self.serial_conn.is_open: + was_connected = True + elif not hasattr(self.serial_conn, 'is_open'): # Mock object might not have is_open initially + if self._reader_thread and self._reader_thread.is_alive(): # If thread is running, assume it was "connected" + was_connected = True + if not was_connected and not (self._reader_thread and self._reader_thread.is_alive()): + logger.warning("Not connected or already disconnected.") + return + logger.debug("Stopping reader thread...") + self._stop_event.set() + if self._reader_thread and self._reader_thread.is_alive(): + self._reader_thread.join(timeout=self.default_timeout + 1) + if self._reader_thread.is_alive(): + logger.warning("Reader thread did not stop in time.") + if self.serial_conn and hasattr(self.serial_conn, 'is_open') and self.serial_conn.is_open: + try: + self.serial_conn.close() + logger.info(f"Serial port {getattr(self.serial_conn, 'port', '')} closed.") + except Exception as e: + logger.error(f"Error closing serial connection: {e}") + elif self.serial_conn: # Port existed but wasn't open + logger.warning(f"Serial port {getattr(self.serial_conn, 'port', '')} was not open but cleaning up.") + self.serial_conn = None + with self._response_lock: + for handler_id, handler_info in list(self._response_handlers.items()): + if not handler_info['event'].is_set(): + handler_info['response_data'] = {'error': 'disconnecting', 'id': handler_id} + handler_info['event'].set() + self._response_handlers.clear() + logger.info("Disconnected and cleaned up.") + + def baudrate_update(self, new_baudrate): + self.disconnect() + self.baudrate = new_baudrate + return self.connect() + + def _parse_frame_from_buffer(self): + """ + Attempts to parse one complete SBP frame from the internal _receive_buffer. + Handles finding SYNC bytes, validating length, and checksum. + :return: A dictionary representing the parsed frame if successful, None otherwise. + """ + while True: + sync_pos = self._receive_buffer.find(bytes([SYNC1, SYNC2])) + sync_sddbt = self._receive_buffer.find(bytes(list(b"$SDDBT"))) + # Add delay to release the reader thread + time.sleep(0.0003) + logger.debug(f"pos={sync_pos}, sdd={sync_sddbt}, size={len(self._receive_buffer)}") + if sync_pos == -1 and sync_sddbt == -1: + if len(self._receive_buffer) > 1: + self._receive_buffer = self._receive_buffer[-1:] if self._receive_buffer[-1] == SYNC1 else bytearray() + return None + elif sync_sddbt != -1 and sync_pos == -1: # priority on sync_pos + if sync_sddbt > 0: + self._receive_buffer = self._receive_buffer[sync_sddbt:] + if self._receive_buffer[-2]==0x0d and self._receive_buffer[-1]==0x0a: + logger.debug(f"SDDBT found!!!!{self._receive_buffer}") + ret = {'nmea':self._receive_buffer} + self._receive_buffer = bytearray() + return ret + return None + + if sync_pos > 0: + logger.debug(f"Discarding {sync_pos} bytes before sync: {self._receive_buffer[:sync_pos].hex().upper()}") + self._receive_buffer = self._receive_buffer[sync_pos:] + + if len(self._receive_buffer) < 6: + logger.debug(f"received_buffer={self._receive_buffer}<6") + return None + + s1, s2, route_recv, mode_recv, id_recv, length_recv = struct.unpack_from('> 7) & 0x01 + received_type = parsed_frame['mode'] & 0x03 + if is_device_resp_payload_flag and received_type == TYPE_CONTENT and parsed_frame['length'] == 3: # RESP message structure + resp_code, cmd_chk1, cmd_chk2 = struct.unpack(' 128: + raise ValueError("Payload length cannot exceed 128 bytes as per protocol. ") + + route = self._build_route_field(dev_addr=target_dev_addr) + mode = self._build_mode_field(type_val, version_val, request_resp_flag) + length = len(payload) + + checksum_data = bytes([route, mode, cmd_id, length]) + payload + check1, check2 = self._calculate_fletcher16(checksum_data) + + frame = bytes([SYNC1, SYNC2, route, mode, cmd_id, length]) + payload + bytes([check1, check2]) + return frame + + def _send_frame(self, frame): + """Sends a pre-built frame over the serial connection.""" + if not (self.serial_conn and hasattr(self.serial_conn, 'is_open') and self.serial_conn.is_open): + logger.error("Serial connection is not open for sending.") + return False + try: + self.serial_conn.write(frame) + logger.debug(f"Sent: {frame.hex().upper()}") + if self._csv_logger: self._csv_logger.log("SENT", frame) + return True + except serial.SerialException as e: # Catch pyserial specific errors + logger.error(f"SerialException during send_frame: {e}") + self._stop_event.set() + return False + except Exception as e: # Catch other errors like if serial_conn is a mock without write + logger.error(f"General Exception during send_frame: {e}") + self._stop_event.set() + return False + + + def _execute_command(self, cmd_id, payload=b'', type_val=TYPE_GETTING, version_val=0, request_resp_flag=True, target_dev_addr=None, expect_content=True): + """Core internal function to send a command and wait for its specific response.""" + if not (self.serial_conn and hasattr(self.serial_conn, 'is_open') and self.serial_conn.is_open): + logger.error("Not connected. Cannot execute command.") + return None + + frame_to_send = self._build_frame(cmd_id, payload, type_val, version_val, request_resp_flag, target_dev_addr) + + event = threading.Event() + handler_key = cmd_id + + with self._response_lock: + if handler_key in self._response_handlers: + logger.warning(f"Overwriting existing response handler for command ID {cmd_id:#02x}.") + self._response_handlers[handler_key] = {'event': event, 'response_data': None} + + if not self._send_frame(frame_to_send): + with self._response_lock: + if handler_key in self._response_handlers: + del self._response_handlers[handler_key] + return None + + response_data = None + try: + if not event.wait(timeout=self.default_timeout): + logger.warning(f"Timeout waiting for response for command ID {cmd_id:#02x}") + return None + + with self._response_lock: + if handler_key in self._response_handlers: + response_data = self._response_handlers[handler_key]['response_data'] + else: + logger.warning(f"Response handler for {cmd_id:#02x} missing after event was set.") + return None + + if response_data is None: + logger.error(f"Critical: Event set for command {cmd_id:#02x} but no response data found.") + return None + + if 'error' in response_data: + logger.error(f"Command {cmd_id:#02x} failed: {response_data['error']}{'; Details: ' + response_data['details'] if 'details' in response_data else ''}") + return None + + if 'resp_code' in response_data and response_data['id'] == cmd_id and response_data['resp_code'] != RESP_OK: + logger.warning(f"Command {cmd_id:#02x} received error RESP: {self.get_resp_code_meaning(response_data['resp_code'])}") + return response_data + + if expect_content: + if response_data['id'] == cmd_id and (response_data['mode'] & 0x03) == TYPE_CONTENT and 'resp_code' not in response_data: + return response_data + else: + logger.warning(f"Command {cmd_id:#02x} expected content but received: ID={response_data.get('id', 'N/A'):#02x}, Mode={response_data.get('mode', 'N/A'):#02x}, RespCode={response_data.get('resp_code', 'N/A')}") + return response_data + else: + if 'resp_code' in response_data and response_data['id'] == cmd_id and response_data['resp_code'] == RESP_OK: + return response_data + else: + logger.warning(f"Command {cmd_id:#02x} expected RESP_OK, received: ID {response_data.get('id', 'N/A'):#02x} with resp_code {response_data.get('resp_code', 'N/A')}") + return response_data + finally: + with self._response_lock: + if handler_key in self._response_handlers: + del self._response_handlers[handler_key] + return response_data + + def get_resp_code_meaning(self, code): + """Returns a human-readable string for a device RESP code. """ + return { + RESP_NONE: "RESP_NONE", RESP_OK: "RESP_OK", RESP_ERR_CHECKSUM: "RESP_ERR_CHECKSUM", + RESP_ERR_PAYLOAD: "RESP_ERR_PAYLOAD", RESP_ERR_ID: "RESP_ERR_ID", + RESP_ERR_VERSION: "RESP_ERR_VERSION", RESP_ERR_TYPE: "RESP_ERR_TYPE", + RESP_ERR_KEY: "RESP_ERR_KEY", RESP_ERR_RUNTIME: "RESP_ERR_RUNTIME" + }.get(code, f"Unknown RESP code: {code}") + + def register_precallback(self, message_id, callback_function): + """Registers a pre-callback function for a specific unsolicited message ID.""" + self._precallbacks[message_id] = callback_function + logger.debug(f"pre-Callback registered for unsolicited message ID {message_id:#02x}") + + def register_callback(self, message_id, callback_function): + """Registers a callback function for a specific unsolicited message ID.""" + self._callbacks[message_id] = callback_function + logger.debug(f"Callback registered for unsolicited message ID {message_id:#02x}") + + def unregister_callback(self, message_id): + """Unregisters a callback for a specific unsolicited message ID.""" + if message_id in self._callbacks: del self._callbacks[message_id] + if message_id in self._precallbacks: del self._precallbacks[message_id] + logger.debug(f"Callback unregistered for unsolicited message ID {message_id:#02x}") + + def register_default_callback(self, callback_function): + """Registers a default callback for any unhandled unsolicited messages.""" + self._default_callback = callback_function + logger.debug("Default callback registered for unsolicited messages.") + + def unregister_default_callback(self): + """Unregisters the default callback.""" + self._default_callback = None + logger.debug("Default callback unregistered.") + + def register_nmea_callback(self, callback_nmea): + """Registers a nmea callback for any nmea messages.""" + self._nmea_callback = callback_nmea + logger.debug("NMEA callback registered for NMEA messages.") + + def unregister_nmea_callback(self): + """Unregisters the nmea callback.""" + self._nmea_callback = None + logger.debug("NMEA callback unregistered.") + + def get_unsolicited_message(self, block=True, timeout=None): + """Retrieves a message from the unsolicited message queue.""" + try: return self._unsolicited_queue.get(block=block, timeout=timeout) + except queue.Empty: return None + + # --- Pre-defined callback --- + + def callback_usbl_solution(self, message): + # Skip message received for the time after a valid message + __DELAY_ECHO_REMOVER = 1 + if self._vehicleType=="AUV": + # Add filter to be sure to receive the same ID for successive message + __NB_SAME_ID_SUCC = 3 + # Or get the ID if the last ok was before this delay in seconds + __DELAY_RESET_SAME_ID = 9999999 + else: + __NB_SAME_ID_SUCC = 0 + __DELAY_RESET_SAME_ID = 9999999 + if not hasattr(self.callback_usbl_solution.__func__, '_tick_last_ok'): + # Local variable + self.callback_usbl_solution.__func__._tick_last_ok = time.time() + self.callback_usbl_solution.__func__._last_snr = 0 + self.callback_usbl_solution.__func__._id_prev = 0 + self.callback_usbl_solution.__func__._id_set = 255 + self.callback_usbl_solution.__func__._cnt_same_id = 0 + + parsed = json.loads(str(message).replace("nan", "'nan'").replace("'",'"')) + logger.debug("callback_usbl_solution:"+str(json.dumps(parsed, indent=2))) + + # Low SNR = skip + _delay_previous_ok = time.time() - self.callback_usbl_solution.__func__._tick_last_ok + self.callback_usbl_solution.__func__._tick_last = time.time() + if message.get('id', 255)==255 or message.get("snr", 0.0)= __NB_SAME_ID_SUCC-1 or _delay_previous_ok > __DELAY_RESET_SAME_ID: + # Set ID if valid + self.callback_usbl_solution.__func__._id_set = message.get('id', 255) + + self.callback_usbl_solution.__func__._id_prev = message.get('id', 255) + self.usbl_data = message + self.usbl_data["id"] = self.callback_usbl_solution.__func__._id_set + self.usbl_data["timestamp_pi"] = time.time() + self.usbl_data["slot_index"] = self.get_slot_index_from_time(self.usbl_data["timestamp_pi"]) + self.callback_usbl_solution.__func__._last_snr = message.get("snr", 0) + self.callback_usbl_solution.__func__._tick_last_ok = time.time() + return 1 + + # --- Payload Parsers --- + + def _parse_timestamp_payload(self, frame): + if frame['length'] == 4: + timestamp = struct.unpack('> 3) & 0x07 + if resp_version == 0 and frame['length'] == 4: + distance = struct.unpack('= 6: + try: + seq_offset, res_mm, abs_offset = struct.unpack_from('> 3) & 0x07 + if resp_version == 0 and frame['length'] == 6: + yaw, pitch, roll = struct.unpack('> 3) & 0x07 + if resp_version == 1 and frame['length'] == 8: + start_offset, max_dist = struct.unpack('> 3) & 0x07 + payload = frame['payload'] + if resp_version == 0 and frame['length'] == 9: + key, uart_id, baud = struct.unpack('> 3) & 0x07 + if resp_version==0 and frame['length'] == 34: + try: + data = struct.unpack('> 3) & 0x07) == 2: + try: + data = struct.unpack('> 3) & 0x07 + if resp_version != version: + logger.warning(f"Requested distance v{version}, received v{resp_version}") + return self._parse_distance_payload(response) + return None + + # TODO : This takes ~160ms to respond (heavy message) in several messages + # so it's not working… + def get_chart_data(self): + response = self._execute_command(ID_CHART, request_resp_flag=False) + return self._parse_chart_data_payload(response) if response else None + + def get_attitude(self, version=0): + response = self._execute_command(ID_ATTITUDE, version_val=version, request_resp_flag=False) + if response: + resp_version = (response['mode'] >> 3) & 0x07 + if resp_version != version: + logger.warning(f"Requested attitude v{version}, received v{resp_version}") + return self._parse_attitude_payload(response) + return None + + def get_temperature(self): + response = self._execute_command(ID_TEMP, request_resp_flag=False) + return self._parse_temperature_payload(response) if response else None + + def set_dataset_config(self, channel_id, channel_period_ms, channel_mask): + if not (0 <= channel_id <= 2): + raise ValueError("Channel ID must be between 0 and 2.") + payload = struct.pack(' 126: raise ValueError("Chunk size exceeds 126 bytes.") + if packet_number < 1: raise ValueError("Packet number must be >= 1.") + payload = struct.pack(' int: + """ + Calculates which slot index is active at a specific timestamp. + Args: + timestamp (float): The time to check (e.g., time.time()). + Returns: + int: The index of the slot (0 to total_slots - 1). + """ + # 1. Calculate the full cycle duration + full_cycle_duration = self._sync_slot_duration * self._sync_slot_total + # 2. Find the position of the timestamp within the cycle (0 to cycle_duration) + position_in_cycle = (timestamp+0.2*self._sync_slot_duration) % full_cycle_duration + # 3. Determine which slot this position falls into + # We use int() to floor the result, as slots are sequential "buckets" + slot_index = int(position_in_cycle / self._sync_slot_duration) + return slot_index + + def set_auto_response_timeout(self, timeout_us): + payload = struct.pack(' 7 and address!=0xff: + logger.error(f"usbl request address filter should be between [0;7] or 0xff:{address}") + return RESP_ERR_PAYLOAD + payload = struct.pack('<8B', addresses) + response = self._execute_command(ID_USBL_CONTROL, payload, TYPE_SETTING, 4, expect_content=False) + return response and response.get('resp_code') == RESP_OK + + # Commands for each request/response events and modem payload handling + # Use cmd_id one by one + # cmd_id : slot index 0 to 7 + # event_on_receive_req_resp : True for request, False for response + # cmd_id_replacement : New cmd_id to put on reception, -1 to disable replacement + # address_replacement : New address_replacement to put on reception, -1 to disable replacement + # send_back_ev_swap : True:Swap event direction, False:keep the same + # receiver_function : 0:Default, 1:BitArray, 2:LLGeoAzimuth + # receive_bit_length : Number of bits to wait during reception + # sender_function : 0:Default, 1:BitArray, 2:LLGeoAzimuth + # send_bit_length : Number of bits to send + def set_usbl_cmd_config(self, cmd_id, event_on_receive_req_resp=False, + cmd_id_replacement=-1, + address_replacement=-1, + send_back_ev_swap=False, + receiver_function=0, receive_bit_length=0, + sender_function=0, send_bit_length=0): + if (not (0 <= cmd_id <= 7)): + logger.warning(f"USBL cmd config cmd_id should typically be 0-7. Got {cmd_id}.") + payload = struct.pack(' 0: + value_diff = value - last_value + if metric in ["Azimuth", "Elev"]: + value_diff = (value_diff + 90) % 180 - 90 + value_diff = value_diff / time_diff + output_rows.append([timestamp_str, f"{metric}_diff", f"{value_diff:.6f}"]) + + last_values[metric] = (timestamp, value) + + with open(output_file, 'w', newline='') as outfile: + writer = csv.writer(outfile) + writer.writerows(output_rows) + +if __name__ == "__main__": + import sys + if len(sys.argv) != 3 and len(sys.argv) != 2: + print("Usage: python process_log.py ") + sys.exit(1) + + input_file = sys.argv[1] + if sys.argv == 3: + output_file = sys.argv[2] + else: + name, extension = input_file.rsplit('.', 1) + # Add "_diff" to the name and then add the extension back + output_file = f"{name}_diff.{extension}" + + print("Generate new diff file :"+str(output_file)) + + calculate_diff(input_file, output_file) diff --git a/driver/log_generator_launcher.py b/driver/log_generator_launcher.py new file mode 100755 index 0000000..5055096 --- /dev/null +++ b/driver/log_generator_launcher.py @@ -0,0 +1,64 @@ +#! /usr/bin/env python +import sys +import subprocess +from pathlib import Path +import os # Import the 'os' module for path manipulation + +# --- Configuration --- +# Add the names of the scripts you want to run in order. +scripts_to_run = ['log_to_csv.py', 'log_add_diff.py', 'log_to_human.py', 'log_merge_nav_usbl.py'] +# ------------------- + +def main(): + """ + Runs a list of scripts, modifying arguments for specific scripts as needed. + """ + # Get the absolute path of the directory where this launcher script is located. + launcher_dir = Path(__file__).parent.resolve() + + # Get all arguments passed to this launcher. + args = sys.argv[1:] + + if not args: + print("❌ Error: No arguments provided.") + print(f"Usage: python {sys.argv[0]} ARGUMENT1 ARGUMENT2 ...") + sys.exit(1) + + print(f"🚀 Starting launcher with arguments: {', '.join(args)}\n") + + # Iterate through each script defined in the list. + for script_name in scripts_to_run: + # Construct the full, absolute path to the target script. + script_path = launcher_dir / script_name + + # For each script, iterate through each argument provided. + for arg in args: + + # This is our new logic block + # --------------------------------------------------------------- + arg_for_script = arg # Default to the original argument + + if script_name == 'log_add_diff.py' and arg.lower().endswith('.csv'): + # Split the argument into the part before the extension and the extension itself + basename, extension = os.path.splitext(arg) + # Create the new, modified argument + arg_for_script = f"{basename}_csv{extension}" + # --------------------------------------------------------------- + + # The command now uses the potentially modified argument. + command = ['python', str(script_path), arg_for_script] + print(f"▶️ Running: {' '.join(command)}") + + try: + subprocess.run(command, check=True, text=True) + except FileNotFoundError: + print(f"❌ Error: Script '{script_path}' not found.") + sys.exit(1) + except subprocess.CalledProcessError as e: + print(f"❌ Error running '{' '.join(command)}'. Returned code: {e.returncode}") + sys.exit(1) + + print("\n✅ All scripts completed successfully!") + +if __name__ == "__main__": + main() diff --git a/driver/log_merge_nav_usbl.py b/driver/log_merge_nav_usbl.py new file mode 100755 index 0000000..82f9866 --- /dev/null +++ b/driver/log_merge_nav_usbl.py @@ -0,0 +1,111 @@ +#! /usr/bin/env python +import csv +import glob +import os +from datetime import datetime +import sys + +def merge_log_files(nav_file, usbl_file, output_file): + """ + Merges navigation and USBL log files into a single CSV file with the format: + timestamp,data,value + + Args: + nav_file (str): Path to the navigation log CSV file. + usbl_file (str): Path to the USBL log CSV file. + output_file (str): Path to the output merged CSV file. + """ + try: + all_lines = [] + + # Read the navigation log + with open(nav_file, 'r') as f: + reader = csv.reader(f) + header = next(reader) # Skip header + for row in reader: + all_lines.append(row) + # Remove last line if broken + if len(all_lines[-1]) != 3: + all_lines.pop() + + # Read the USBL log + with open(usbl_file, 'r') as f: + reader = csv.reader(f) + for row in reader: + timestamp, data, value = row + if timestamp == "timestamp": + # Remove timestamp header if present (in merge from other merge) + continue + data = 'usbl_' + data + if data == 'usbl_ID': + value = str(int(float(value))) + all_lines.append([timestamp, data, value]) + if len(all_lines[-1]) != 3: + all_lines.pop() + + # Sort by timestamp + all_lines.sort(key=lambda x: datetime.strptime(x[0], '%Y-%m-%d %H:%M:%S.%f')) + + # Write to CSV + with open(output_file, 'w', newline='') as f: + writer = csv.writer(f) + writer.writerow(['timestamp', 'data', 'value']) + writer.writerows(all_lines) + + print(f"Successfully merged {nav_file} and {usbl_file} into {output_file}") + + except Exception as e: + print(f"An error occurred: {e}") + +if __name__ == "__main__": + try: + if len(sys.argv) == 2: + input_file_path = sys.argv[1] + target_directory = os.path.dirname(input_file_path) + if target_directory == "": + target_directory = "." + + filename_only = os.path.basename(input_file_path) + base_name = "_".join(filename_only.split('_')[:3]) + if not base_name: + raise ValueError("Could not parse base name from filename.") + print(f"Processing specific files for base name: {base_name}") + + nav_log_file = os.path.join(target_directory, f'{base_name}_navigation_log.csv') + usbl_csv_file = os.path.join(target_directory, f'{base_name}_usbl_csv.csv') + output_filename = os.path.join(target_directory, f'{base_name}_merged_log.csv') + + print(f"Merging {nav_log_file} and {usbl_csv_file} to {output_filename}") + merge_log_files(nav_log_file, usbl_csv_file, output_filename) + + elif len(sys.argv) == 3: + nav_file = sys.argv[1] + usbl_file = sys.argv[2] + + nav_basename = os.path.splitext(os.path.basename(nav_file))[0] + usbl_basename = os.path.splitext(os.path.basename(usbl_file))[0] + + output_dir = os.path.dirname(nav_file) + if not output_dir: + output_dir = "." + + output_filename = os.path.join(output_dir, f"{nav_basename}_{usbl_basename}_merged.csv") + + print(f"Merging {nav_file} and {usbl_file} to {output_filename}") + merge_log_files(nav_file, usbl_file, output_filename) + + else: + print("Error: Invalid number of arguments. Please provide 1 or 2 arguments.") + print("") + print("Usage:") + print(" 1 argument: python log_merge_nav_usbl.py ") + print(" (This will find navigation and usbl logs from the same session and merge them)") + print(" 2 arguments: python log_merge_nav_usbl.py ") + print(" (This will merge the two specified files)") + sys.exit(1) + + except FileNotFoundError as e: + print(f"Error: File not found: {e}") + except Exception as e: + print(f"An unexpected error occurred: {e}") + diff --git a/driver/log_to_csv.py b/driver/log_to_csv.py new file mode 100755 index 0000000..d5f6ae1 --- /dev/null +++ b/driver/log_to_csv.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +""" +A script to parse Kogger SB protocol logs and convert UsblSolution messages +directly to a CSV file. +""" + +import argparse +import ast +import csv +import struct +import sys + +# Based on the Kogger SB protocol specification +ID_MAP = { + 0x01: "ID_TIMESTAMP", + 0x02: "ID_DIST", + 0x03: "ID_CHART", + 0x04: "ID_ATTITUDE", + 0x05: "ID_TEMP", + 0x10: "ID_DATASET", + 0x11: "ID_DIST_SETUP", + 0x12: "ID_CHART_SETUP", + 0x14: "ID_TRANSC", + 0x15: "ID_SND_SPD", + 0x18: "ID_UART", + 0x1B: "ID_IMU_SETUP", + 0x20: "ID_VERSION", + 0x21: "ID_MARK", + 0x22: "ID_DIAG", + 0x23: "ID_FLASH", + 0x24: "ID_BOOT", + 0x25: "ID_UPDATE", + 0x64: "ID_NAV", + 0x65: "ID_USBL_SOLUTION", + 0x66: "ID_SIGNAL_ENCODER", + 0x67: "ID_SIGNAL_DECODER", + 0x68: "ID_USBL_CONTROL", + 0x79: "ID_DVL_VEL", + # Aliases + 102: "ID_SIGNAL_ENCODER", + 103: "ID_SIGNAL_DECODER", + 121: "ID_DVL_VEL", +} + +def parse_route(route_byte): + """Parses the ROUTE byte.""" + dev_address = route_byte & 0b1111 + return {"dev_address": dev_address} + +def parse_mode(mode_byte): + """Parses the MODE byte.""" + type_val = mode_byte & 0b11 + version = (mode_byte >> 3) & 0b111 + return { + "type_val": type_val, + "version": version, + } + +def parse_payload_structured(msg_id, mode, payload): + """ + Parses the payload and returns a structured dictionary for UsblSolution, + or a simple representation for other types. + """ + msg_name = ID_MAP.get(msg_id) + + if msg_name == "ID_USBL_SOLUTION" and mode['version'] == 0: + if len(payload) == 152: + try: + fmt = '= 8: # Minimum message size + if not reassembly_buffer.startswith(b'\xbbU'): + sync_pos = reassembly_buffer.find(b'\xbbU') + if sync_pos == -1: + reassembly_buffer = b'' + break + else: + reassembly_buffer = reassembly_buffer[sync_pos:] + + if len(reassembly_buffer) < 6: + break + + payload_len = reassembly_buffer[5] + full_msg_len = 6 + payload_len + 2 # header + payload + checksum + + if len(reassembly_buffer) >= full_msg_len: + message_to_parse = reassembly_buffer[:full_msg_len] + parsed_data = parse_message_structured(message_to_parse) + + if (parsed_data and not parsed_data.get("error") and + isinstance(parsed_data.get('payload_parsed'), dict)): + + payload_info = parsed_data['payload_parsed'] + if payload_info.get('type') == 'UsblSolution': + solution_data = payload_info['data'] + for key, value in solution_data.items(): + csv_writer.writerow([timestamp, key, value]) + + reassembly_buffer = reassembly_buffer[full_msg_len:] + else: + break + + except FileNotFoundError: + print(f"Error: File not found at {input_file}", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"An unexpected error occurred: {e}", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Convert Kogger SB protocol logs directly to a CSV file, extracting UsblSolution data." + ) + input_file = sys.argv[1] + if sys.argv == 3: + output_file = sys.argv[2] + else: + name, extension = input_file.rsplit('.', 1) + # Add "_diff" to the name and then add the extension back + output_file = f"{name}_csv.{extension}" + + print("Generate new csv file :"+str(output_file)) + + process_log_to_csv(input_file, output_file) diff --git a/driver/log_to_human.py b/driver/log_to_human.py new file mode 100755 index 0000000..cca2cda --- /dev/null +++ b/driver/log_to_human.py @@ -0,0 +1,276 @@ +#!/usr/bin/env python3 +""" +A script to parse and convert Kogger SB protocol logs into a human-readable format. +""" + +import csv +import struct +import sys +import ast +import os + +# Based on the Kogger SB protocol specification PDF and user-provided details. + +# (From PDF page 5 and others) +ID_MAP = { + 0x01: "ID_TIMESTAMP", + 0x02: "ID_DIST", + 0x03: "ID_CHART", + 0x04: "ID_ATTITUDE", + 0x05: "ID_TEMP", + 0x10: "ID_DATASET", + 0x11: "ID_DIST_SETUP", + 0x12: "ID_CHART_SETUP", + 0x14: "ID_TRANSC", + 0x15: "ID_SND_SPD", + 0x18: "ID_UART", + 0x1B: "ID_IMU_SETUP", + 0x20: "ID_VERSION", + 0x21: "ID_MARK", + 0x22: "ID_DIAG", + 0x23: "ID_FLASH", + 0x24: "ID_BOOT", + 0x25: "ID_UPDATE", + 0x64: "ID_NAV", + 0x65: "ID_USBL_SOLUTION", + 0x66: "ID_SIGNAL_ENCODER", + 0x67: "ID_SIGNAL_DECODER", + 0x68: "ID_USBL_CONTROL", + 0x79: "ID_DVL_VEL", + # Some IDs have aliases + 102: "ID_SIGNAL_ENCODER", + 103: "ID_SIGNAL_DECODER", + 121: "ID_DVL_VEL", +} + +def parse_route(route_byte): + """Parses the ROUTE byte.""" + dev_address = route_byte & 0b1111 + return {"dev_address": dev_address} + +def parse_mode(mode_byte): + """Parses the MODE byte.""" + type_val = mode_byte & 0b11 + type_map = { + 0: "Reserved", + 1: "CON:DEV→HST", + 2: "SET:HST→DEV", + 3: "GET:HST→DEV", + } + version = (mode_byte >> 3) & 0b111 + mark = (mode_byte >> 6) & 0b1 + response = (mode_byte >> 7) & 0b1 + return { + "type": type_map.get(type_val, "Unknown"), + "type_val": type_val, + "version": version, + "mark": mark, + "response": response, + } + +def parse_payload(msg_id, mode, payload): + """Parses the payload based on message ID, mode, and version.""" + msg_name = ID_MAP.get(msg_id) + + # Check for generic RESP message first (PDF page 6) + if mode['type_val'] == 1 and len(payload) == 3: # CONTENT from DEVICE + try: + code, _check1, _check2 = struct.unpack('= 8: # Minimum possible message size + if not reassembly_buffer.startswith(b'\xbbU'): + # Buffer doesn't start with sync bytes, find the next sync sequence + sync_pos = reassembly_buffer.find(b'\xbbU') + if sync_pos == -1: + # No sync bytes, discard the whole buffer + reassembly_buffer = b'' + break + else: + # Discard garbage before the sync bytes + reassembly_buffer = reassembly_buffer[sync_pos:] + + if len(reassembly_buffer) < 6: + # Not enough data for a header + break + + # Try to read the payload length + payload_len = reassembly_buffer[5] + full_msg_len = 6 + payload_len + 2 # header + payload + checksum + + if len(reassembly_buffer) >= full_msg_len: + # We have a complete message + message_to_parse = reassembly_buffer[:full_msg_len] + human_readable_msg = parse_message(message_to_parse) + print_save("RX", timestamp, human_readable_msg, outfile) + + # Keep the rest of the buffer for the next message + reassembly_buffer = reassembly_buffer[full_msg_len:] + else: + # Not enough data for a full message, wait for more + break + + except FileNotFoundError: + print(f"Error: File not found at {log_file}", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"An unexpected error occurred: {e}", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + if len(sys.argv) != 2: + print(f"Usage: python {sys.argv[0]} ", file=sys.stderr) + sys.exit(1) + main(sys.argv[1]) \ No newline at end of file diff --git a/driver/print_log.py b/driver/print_log.py new file mode 100755 index 0000000..66a6a42 --- /dev/null +++ b/driver/print_log.py @@ -0,0 +1,139 @@ +#! /usr/bin/env python + +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt +from mpl_toolkits.mplot3d import Axes3D +from matplotlib.animation import FuncAnimation +from matplotlib.widgets import Slider +import sys + + +# --- Thresholds for filtering --- +DIST_DIFF_THRESHOLD = 20000000000.0 +AZIMUTH_DIFF_THRESHOLD = 20000000000.0 +ELEV_DIFF_THRESHOLD = 20000000000.0 +SNR_DIFF_THRESHOLD = 50000000000.0 + + +def create_animated_plot(filepath): + """ + Creates an animated 3D plot of AUV movement with play/pause and speed control. + """ + # --- Data Loading and Processing --- + df = pd.read_csv(filepath, header=None, names=['Timestamp', 'Measurement', 'Value']) + df_pivot = df.pivot_table(index='Timestamp', columns='Measurement', values='Value', aggfunc='first').reset_index() + + # --- Filtering based on thresholds --- + for col in ['Dist_diff', 'Azimuth_diff', 'Elev_diff', 'SNR_diff']: + if col in df_pivot.columns: + df_pivot[col] = pd.to_numeric(df_pivot[col]) + df_pivot[col] = df_pivot[col].bfill() + + if 'Dist_diff' in df_pivot.columns: + df_pivot = df_pivot[np.abs(df_pivot['Dist_diff']) <= DIST_DIFF_THRESHOLD] + if 'Azimuth_diff' in df_pivot.columns: + df_pivot = df_pivot[np.abs(df_pivot['Azimuth_diff']) <= AZIMUTH_DIFF_THRESHOLD] + if 'Elev_diff' in df_pivot.columns: + df_pivot = df_pivot[np.abs(df_pivot['Elev_diff']) <= ELEV_DIFF_THRESHOLD] + if 'SNR_diff' in df_pivot.columns: + df_pivot = df_pivot[np.abs(df_pivot['SNR_diff']) <= SNR_DIFF_THRESHOLD] + + try: + df_pivot = df_pivot.dropna(subset=['Dist', 'Azimuth', 'Elev']).copy() + except: + df_pivot = df_pivot.dropna(subset=['Azimuth', 'Elev']).copy() + + df_pivot = df_pivot.reset_index(drop=True) + + try: + df_pivot['Dist'] = pd.to_numeric(df_pivot['Dist']) + except: + df_pivot['Dist'] = pd.to_numeric(10000) + pass + df_pivot['Azimuth'] = pd.to_numeric(df_pivot['Azimuth']) + df_pivot['Elev'] = pd.to_numeric(df_pivot['Elev']) + df_pivot['Azimuth_rad'] = np.deg2rad(df_pivot['Azimuth']) + df_pivot['Elev_rad'] = np.deg2rad(df_pivot['Elev']) + df_pivot['x'] = df_pivot['Dist'] * np.cos(df_pivot['Elev_rad']) * np.cos(df_pivot['Azimuth_rad']) + df_pivot['y'] = df_pivot['Dist'] * np.cos(df_pivot['Elev_rad']) * np.sin(df_pivot['Azimuth_rad']) + df_pivot['z'] = df_pivot['Dist'] * np.sin(df_pivot['Elev_rad']) + + # --- Plot Setup --- + fig = plt.figure(figsize=(12, 10)) + ax = fig.add_subplot(111, projection='3d') + plt.subplots_adjust(bottom=0.25) # Adjust subplot to make room for slider + + # Set plot limits + ax.set_xlim(df_pivot['x'].min() - 1, df_pivot['x'].max() + 1) + ax.set_ylim(df_pivot['y'].min() - 1, df_pivot['y'].max() + 1) + ax.set_zlim(df_pivot['z'].min() - 1, df_pivot['z'].max() + 1) + + # Initialize plot elements + ax.plot(df_pivot['x'], df_pivot['y'], df_pivot['z'], c='gray', linestyle='--', label='Full Trajectory') + auv_position, = ax.plot([], [], [], 'ro', markersize=10, label='AUV Position') + current_trajectory, = ax.plot([], [], [], c='b', label='Current Trajectory') + usv_position = ax.scatter(0, 0, 0, c='g', marker='^', s=100, label='USV') + + ax.set_xlabel('X coordinate (meters)') + ax.set_ylabel('Y coordinate (meters)') + ax.set_zlabel('Z coordinate (meters)') + ax.legend() + + # --- Animation --- + def update(frame): + auv_position.set_data([df_pivot['x'][frame]], [df_pivot['y'][frame]]) + auv_position.set_3d_properties([df_pivot['z'][frame]]) + current_trajectory.set_data(df_pivot['x'][:frame+1], df_pivot['y'][:frame+1]) + current_trajectory.set_3d_properties(df_pivot['z'][:frame+1]) + time_label = pd.to_datetime(df_pivot['Timestamp'][frame]).strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] + ax.set_title(f'AUV Movement at: {time_label}') + return auv_position, current_trajectory + + fig.ani = FuncAnimation(fig, update, frames=len(df_pivot), blit=False, interval=50) + + # --- Slider for Interval Control --- + axcolor = 'lightgoldenrodyellow' + ax_interval = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=axcolor) + s_interval = Slider( + ax=ax_interval, + label='Interval (ms)', + valmin=1, + valmax=1000, + valinit=50, + valstep=10 + ) + + def update_interval(val): + if fig.ani.event_source: + fig.ani.event_source.stop() + + new_interval = int(val) + fig.ani = FuncAnimation(fig, update, frames=len(df_pivot), blit=False, interval=new_interval) + fig.canvas.draw_idle() + + s_interval.on_changed(update_interval) + fig.s_interval = s_interval + + # --- Play/Pause Functionality --- + paused = False + def toggle_pause(event): + nonlocal paused + if event.key == ' ': + if paused: + fig.ani.resume() + else: + fig.ani.pause() + paused = not paused + + fig.canvas.mpl_connect('key_press_event', toggle_pause) + + plt.show() + +if __name__ == '__main__': + if len(sys.argv) != 2: + print("Usage: python animated_auv_tracker_fixed.py ") + sys.exit(1) + + filepath = sys.argv[1] + create_animated_plot(filepath) diff --git a/driver/simulation_kogger.py b/driver/simulation_kogger.py new file mode 100644 index 0000000..3213282 --- /dev/null +++ b/driver/simulation_kogger.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python +from loguru import logger +import os +import time +import csv +from datetime import datetime +import struct + +# 0:Nothing, 1:Only important, 2:All +PRINT_LEVEL = 1 + +# PATH File for kogger log +# AUV log +#KOGGER_FILE = "2025-09-01_11-40-00_kogger_log_auv.csv" +# USV log +KOGGER_FILE = "2025-09-01_11-40-02_kogger_log_usv.csv" + +# --- Frame parsing logic for simulation flexibility --- +SYNC1 = 0xBB +SYNC2 = 0x55 +ID_USBL_CONTROL = 0x68 +TYPE_SETTING = 2 + +def _parse_sim_frame(frame_bytes): + """A simple parser for simulation matching flexibility.""" + if not isinstance(frame_bytes, bytes) or len(frame_bytes) < 8: + return None + if frame_bytes[0] != SYNC1 or frame_bytes[1] != SYNC2: + return None + + try: + s1, s2, route, mode, cmd_id, length = struct.unpack_from('> 3) & 0x07 + + return { + 'id': cmd_id, + 'type': msg_type, + 'version': version, + 'payload': frame_bytes[6:6+length] + } + except (struct.error, IndexError): + return None +# --- End of parsing logic --- + + +class simu_serial(): + is_open = True + name = "" + log_messages = [] + log_idx = 0 + in_waiting = 1 + kogger_next = None + received_write = 0 + start_sim_time = None + start_log_time = None + + def _print(text, level): + if level <= PRINT_LEVEL: + logger.debug(text) + + def _parse_and_merge_csv(filename): + messages = [] + script_dir = os.path.dirname(__file__) + file_path = os.path.join(script_dir, filename) + if not os.path.exists(file_path): + # Fallback to current dir if not found next to script + file_path = filename + + with open(file_path, 'r') as f: + reader = csv.reader(f) + for row in reader: + ts_str, msg_type, msg_content = row + timestamp = datetime.strptime(ts_str, '%Y-%m-%d %H:%M:%S.%f') + try: + byte_msg = eval(msg_content) + except Exception as e: + simu_serial._print(f"Could not eval message content: {msg_content}, error: {e}", 1) + byte_msg = msg_content.encode() + messages.append({'timestamp': timestamp, 'type': msg_type, 'msg': byte_msg}) + + merged = [] + i = 0 + while i < len(messages): + msg = messages[i] + if msg['type'] == 'RECEIVED' and msg['msg'] == b'\xbb': + full_msg = b'\xbb' + ts = msg['timestamp'] + i += 1 + while i < len(messages) and messages[i]['type'] == 'RECEIVED' and messages[i]['msg'] != b'\xbb': + full_msg += messages[i]['msg'] + i += 1 + merged.append({'timestamp': ts, 'type': 'RECEIVED', 'msg': full_msg}) + else: + merged.append(msg) + i += 1 + return merged + + def Serial(port, baudrate=921600, timeout=1): + logger.error("!!!!!!Init kogger in simulation mode!!!!!!!") + simu_serial.name = port + simu_serial.log_messages = simu_serial._parse_and_merge_csv(KOGGER_FILE) + if not simu_serial.log_messages: + logger.error(f"No messages loaded from {KOGGER_FILE}") + return simu_serial + + simu_serial.log_idx = 0 + simu_serial.start_sim_time = time.time() + simu_serial.start_log_time = simu_serial.log_messages[0]['timestamp'] + + return simu_serial + + def close(): + simu_serial._print("Close kogger", 1) + return True + + def read(size=1): + if simu_serial.received_write == 1: + line = simu_serial.kogger_next + simu_serial.kogger_next = None + simu_serial.received_write = 0 + simu_serial._print(f"read (from write): {line}", 2) + return line + + if simu_serial.log_idx >= len(simu_serial.log_messages): + simu_serial._print("End of log file.", 1) + time.sleep(1) + return b'' + + next_msg = simu_serial.log_messages[simu_serial.log_idx] + + log_time_elapsed = (next_msg['timestamp'] - simu_serial.start_log_time).total_seconds() + sim_time_elapsed = time.time() - simu_serial.start_sim_time + + if sim_time_elapsed < log_time_elapsed: + sleep_duration = log_time_elapsed - sim_time_elapsed + if sleep_duration > 0: + time.sleep(sleep_duration) + + # Re-check current message as time has passed + if simu_serial.log_idx >= len(simu_serial.log_messages): + return b'' + + next_msg = simu_serial.log_messages[simu_serial.log_idx] + if next_msg['type'] == 'RECEIVED': + simu_serial.log_idx += 1 + simu_serial._print(f"read (unsolicited): {next_msg['msg']}", 2) + return next_msg['msg'] + else: # SENT + return b'' + + def write(text): + simu_serial._print(f"Write: {text}", 2) + incoming_frame_info = _parse_sim_frame(text) + search_idx = simu_serial.log_idx + while search_idx < len(simu_serial.log_messages): + log_entry = simu_serial.log_messages[search_idx] + if log_entry['type'] == 'SENT': + + match = False + # Check for flexible match on acoustic ping (USBL_CONTROL with state) + if (incoming_frame_info and + incoming_frame_info['id'] == ID_USBL_CONTROL and + incoming_frame_info['type'] == TYPE_SETTING and + incoming_frame_info['version'] == 1): + + log_frame_info = _parse_sim_frame(log_entry['msg']) + if (log_frame_info and + log_frame_info['id'] == ID_USBL_CONTROL and + log_frame_info['type'] == TYPE_SETTING and + log_frame_info['version'] == 1): + + simu_serial._print(f"Flexible match for USBL_CONTROL message. Incoming: {text.hex()}, Logged: {log_entry['msg'].hex()}", 2) + match = True + + # Fallback to exact match for all other messages + if not match and log_entry['msg'] == text: + match = True + + if match: + # Sync time + log_time_elapsed = (log_entry['timestamp'] - simu_serial.start_log_time).total_seconds() + simu_serial.start_sim_time = time.time() - log_time_elapsed + + response_idx = search_idx + 1 + while response_idx < len(simu_serial.log_messages): + if simu_serial.log_messages[response_idx]['type'] == 'RECEIVED': + simu_serial.kogger_next = simu_serial.log_messages[response_idx]['msg'] + simu_serial.received_write = 1 + + # Advance log time to response time + log_time_elapsed_resp = (simu_serial.log_messages[response_idx]['timestamp'] - simu_serial.start_log_time).total_seconds() + sim_time_elapsed = time.time() - simu_serial.start_sim_time + if sim_time_elapsed < log_time_elapsed_resp: + sleep_duration = log_time_elapsed_resp - sim_time_elapsed + if sleep_duration > 0: + time.sleep(sleep_duration) + + simu_serial.log_idx = response_idx + 1 + return + response_idx += 1 + + logger.warning(f"No RECEIVED message found after SENT: {text}") + simu_serial.log_idx = search_idx + 1 + return + search_idx += 1 + + logger.error(f"Unexpected SENT message: {text} from index {simu_serial.log_idx}") + + def reset_output_buffer(): + pass diff --git a/driver/test/listen_to_antenna.py b/driver/test/listen_to_antenna.py new file mode 100755 index 0000000..7c248cb --- /dev/null +++ b/driver/test/listen_to_antenna.py @@ -0,0 +1,153 @@ +#! /usr/bin/env python + +import time +import sys +from kogger_protocol_driver import KoggerSBPDevice, setup_logging +import json + +# --- Script Configuration --- +# Set the desired logging level: "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL" +LOG_LEVEL = "DEBUG" +# Default serial port if not provided via command line +DEFAULT_SERIAL_PORT = "/dev/ttyUSB0" +serial_speed = 921600 + +# --- Setup Logging --- +# Configure the logger for the Kogger driver module +setup_logging(LOG_LEVEL) + + +def print_message(message): + """ + This function is a callback that will be executed for each message + received from the antenna that is not a direct response to a command. + """ + print("Message = "+str(message)) + parsed = json.loads(str(message).replace("nan", "'nan'").replace("'",'"')) + print("parsed="+str(parsed)) + print(json.dumps(parsed, indent=2)) + + + +def check_all_getters(antenna): + """ + Calls all get_* methods on the antenna object and prints the results. + """ + print("\n--- Checking all getter methods ---") + + # A list of all getter methods to call. + # Some methods require arguments, which are provided as tuples. + # (method_name, args_tuple) + getters_to_test = [ + ('get_timestamp', ()), + ('get_distance', (0,)), + ('get_distance', (1,)), + ('get_chart_data', ()), + ('get_attitude', (0,)), #!!OK + ('get_attitude', (1,)), + ('get_temperature', ()), + ('get_dataset_config', (0,)), + ('get_distance_setup', ()), + ('get_chart_setup', ()), + ('get_transceiver_settings', ()), + ('get_sound_speed', ()), + ('get_uart_config', (1, 0)), # uart_id=1, version=0 + ('get_uart_config', (1, 1)), # uart_id=1, version=1 + ('get_version_info', ()), #!! OK + ('get_mark_status', ()), #!! OK + ('get_diagnostics', ()), + ('get_navigation_data', ()), + ('get_dvl_velocity_data', ()), + ('get_signal_encoder_data', ()), + ('get_signal_decoder_data', ()), + ('get_auto_response_timeout', ()), + ('get_auto_response_filter', ()), + ('get_auto_response_payload', ()), + ('get_usbl_solution', ()), + ] + + for method_name, args in getters_to_test: + try: + method = getattr(antenna, method_name) + print(f"Calling {method_name}{args}...") + result = method(*args) + print(f"Result: {result}") + except Exception as e: + print(f"An error occurred while calling {method_name}: {e}") + print("-" * 20) + time.sleep(0.01) # Give the device a moment between commands + + print("--- Finished checking all getter methods ---\n") + + +def main(): + """ + Main function to connect to the Kogger antenna and listen for messages. + """ + # Determine the serial port to use + if len(sys.argv) > 1: + serial_port = sys.argv[1] + else: + serial_port = DEFAULT_SERIAL_PORT + print(f"No serial port provided. Using default: {serial_port}") + + # Instantiate the driver + antenna = KoggerSBPDevice(serial_port, serial_speed, default_timeout=0.02) + + try: + # Connect to the antenna + if not antenna.connect(): + print(f"Failed to connect to the antenna on port {serial_port}", file=sys.stderr) + sys.exit(1) + + #antenna.register_default_callback(print_message) + #time.sleep(20) + #exit() + + + + #result = antenna.get_dataset_config(0) + result = antenna.set_auto_response_filter(0) + print("result="+str(result)) + result = antenna.set_auto_response_timeout(0xffffffff) + print("result="+str(result)) + result = antenna.set_auto_response_payload(0xff) + print("result="+str(result)) + #exit() + antenna.register_default_callback(print_message) + for i in range(10000): + #result = antenna.send_acoustic_ping(i%9) + result = antenna.send_acoustic_ping(0) + print("sent "+str(i%9)+"result="+str(result)) + result = antenna.set_auto_response_filter(0) + result = antenna.set_auto_response_timeout(0xffffffff) + result = antenna.set_auto_response_payload(0xff) + time.sleep(1) + + exit() + + print(f"Successfully connected to the antenna on {serial_port}.") + # Perform a one-time check of all getter functions + check_all_getters(antenna) + + # Register the callback function to handle incoming messages + antenna.register_default_callback(print_message) + print("Listening for unsolicited messages... Press Ctrl+C to exit.") + + # Keep the main thread alive to allow the background reader thread to run + time.sleep(5) + #exit() + while True: + time.sleep(10) + + except KeyboardInterrupt: + print("\nExiting...") + except Exception as e: + print(f"An error occurred: {e}", file=sys.stderr) + finally: + # Ensure the connection is closed gracefully + print("Disconnecting from the antenna.") + antenna.disconnect() + +if __name__ == "__main__": + main() diff --git a/driver/test/log/2026-03-19_12-51-12_AUV_usbl.csv b/driver/test/log/2026-03-19_12-51-12_AUV_usbl.csv new file mode 100644 index 0000000..e69de29 diff --git a/driver/test/log/2026-03-19_12-51-12_log_usv.log b/driver/test/log/2026-03-19_12-51-12_log_usv.log new file mode 100644 index 0000000..68bd65c --- /dev/null +++ b/driver/test/log/2026-03-19_12-51-12_log_usv.log @@ -0,0 +1,5 @@ +2026-03-19 12:51:12.478 | INFO | kogger_protocol_driver:setup_logging:81 - Kogger Protocol Driver: Loguru logging configured to level INFO and file log/2026-03-19_12-51-12_log_usv.log. +2026-03-19 12:51:12.478 | INFO | kogger_protocol_driver:__init__:224 - KoggerSBPDevice configured for port /dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0, baudrate 921600, address 0 +2026-03-19 12:51:12.479 | INFO | kogger_protocol_driver:_open_file:43 - CSV logging enabled to log/2026-03-19_12-51-12_AUV_usbl.csv +2026-03-19 12:51:12.480 | ERROR | kogger_protocol_driver:connect:317 - Error connecting to /dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0: [Errno 2] could not open port /dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0: [Errno 2] No such file or directory: '/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0' +2026-03-19 12:51:12.480 | WARNING | kogger_protocol_driver:disconnect:341 - Not connected or already disconnected. diff --git a/driver/test/log/2026-03-19_12-52-10_AUV_usbl.csv b/driver/test/log/2026-03-19_12-52-10_AUV_usbl.csv new file mode 100644 index 0000000..e69de29 diff --git a/driver/test/log/2026-03-19_12-52-10_log_usv.log b/driver/test/log/2026-03-19_12-52-10_log_usv.log new file mode 100644 index 0000000..9a5c5b9 --- /dev/null +++ b/driver/test/log/2026-03-19_12-52-10_log_usv.log @@ -0,0 +1,5 @@ +2026-03-19 12:52:10.592 | INFO | kogger_protocol_driver:setup_logging:81 - Kogger Protocol Driver: Loguru logging configured to level INFO and file log/2026-03-19_12-52-10_log_usv.log. +2026-03-19 12:52:10.593 | INFO | kogger_protocol_driver:__init__:224 - KoggerSBPDevice configured for port /dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0, baudrate 921600, address 0 +2026-03-19 12:52:10.593 | INFO | kogger_protocol_driver:_open_file:43 - CSV logging enabled to log/2026-03-19_12-52-10_AUV_usbl.csv +2026-03-19 12:52:10.594 | ERROR | kogger_protocol_driver:connect:317 - Error connecting to /dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0: [Errno 2] could not open port /dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0: [Errno 2] No such file or directory: '/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0' +2026-03-19 12:52:10.594 | WARNING | kogger_protocol_driver:disconnect:341 - Not connected or already disconnected. diff --git a/driver/test/log/2026-03-19_12-52-42_AUV_usbl.csv b/driver/test/log/2026-03-19_12-52-42_AUV_usbl.csv new file mode 100644 index 0000000..85cdd66 --- /dev/null +++ b/driver/test/log/2026-03-19_12-52-42_AUV_usbl.csv @@ -0,0 +1,69 @@ +2026-03-19 12:52:43.093386,SENT,"b'\xbbU\x00\x03 \x00#I'" +2026-03-19 12:52:43.094057,RECEIVED,"b'U'" +2026-03-19 12:52:43.095431,RECEIVED,"b'\x00A ""\x00\x0f\x00\x00\x00\x00\x00\x00\x9d\x0eDT\x00\x02\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe5\xf8'" +2026-03-19 12:52:43.125697,SENT,"b'\xbbU\x00\xa2h\x04\x00\x00\x00\x00\x0e\xf2'" +2026-03-19 12:52:43.126435,RECEIVED,"b'\xbb'" +2026-03-19 12:52:43.127291,RECEIVED,"b'U\x00\xe1h\x03\x01\x0e\xf2Mk'" +2026-03-19 12:52:43.130497,SENT,"b'\xbbU\x00\x9ah\x04\xff\xff\xff\xff\x02\xb0'" +2026-03-19 12:52:43.131124,RECEIVED,"b'\xbb'" +2026-03-19 12:52:43.132227,RECEIVED,"b'U\x00\xd9h\x03\x01\x02\xb0\xf7\xe1'" +2026-03-19 12:52:43.134784,SENT,"b'\xbbU\x00\xaah\x01\xff\x12\xe1'" +2026-03-19 12:52:43.135455,RECEIVED,"b'\xbb'" +2026-03-19 12:52:43.136261,RECEIVED,"b'U\x00\xe9h\x03\x01\x12\xe1H\x92'" +2026-03-19 12:52:43.141038,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:43.141343,RECEIVED,"b'\xbb'" +2026-03-19 12:52:43.142370,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-19 12:52:44.146667,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:44.147236,RECEIVED,"b'\xbb'" +2026-03-19 12:52:44.148315,RECEIVED,"b'\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-19 12:52:45.160688,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:45.161715,RECEIVED,"b'U'" +2026-03-19 12:52:46.166966,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:46.168249,RECEIVED,"b'U'" +2026-03-19 12:52:47.173747,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:47.174169,RECEIVED,"b'\xbb'" +2026-03-19 12:52:48.179130,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:48.179593,RECEIVED,"b'\xbb'" +2026-03-19 12:52:48.181462,RECEIVED,"b'\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-19 12:52:49.184861,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:49.185219,RECEIVED,"b'\xbb'" +2026-03-19 12:52:49.185898,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-19 12:52:50.188687,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:50.189324,RECEIVED,"b'\xbb'" +2026-03-19 12:52:50.189944,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-19 12:52:51.193924,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:51.194330,RECEIVED,"b'\xbb'" +2026-03-19 12:52:51.195135,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-19 12:52:52.198062,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:52.198732,RECEIVED,"b'\xbb'" +2026-03-19 12:52:52.199484,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-19 12:52:53.203130,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:53.203577,RECEIVED,"b'\xbb'" +2026-03-19 12:52:53.204300,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-19 12:52:54.207989,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:54.208469,RECEIVED,"b'\xbb'" +2026-03-19 12:52:54.209071,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-19 12:52:55.210958,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:55.211257,RECEIVED,"b'\xbb'" +2026-03-19 12:52:55.211822,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-19 12:52:56.213940,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:56.214449,RECEIVED,"b'\xbb'" +2026-03-19 12:52:56.215109,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-19 12:52:57.218519,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:57.219257,RECEIVED,"b'\xbb'" +2026-03-19 12:52:57.219862,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-19 12:52:58.226270,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:58.227078,RECEIVED,"b'\xbb'" +2026-03-19 12:52:58.227755,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-19 12:52:59.230747,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:52:59.231662,RECEIVED,"b'\xbb'" +2026-03-19 12:52:59.235276,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-19 12:53:00.234070,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:53:00.234623,RECEIVED,"b'\xbb'" +2026-03-19 12:53:00.235221,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-19 12:53:01.237701,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:53:01.238012,RECEIVED,"b'\xbb'" +2026-03-19 12:53:01.238538,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-19 12:53:02.240233,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-19 12:53:02.240568,RECEIVED,"b'\xbb'" +2026-03-19 12:53:02.241076,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" diff --git a/driver/test/log/2026-03-19_12-52-42_log_usv.log b/driver/test/log/2026-03-19_12-52-42_log_usv.log new file mode 100644 index 0000000..a0adf69 --- /dev/null +++ b/driver/test/log/2026-03-19_12-52-42_log_usv.log @@ -0,0 +1,17 @@ +2026-03-19 12:52:43.085 | INFO | kogger_protocol_driver:setup_logging:81 - Kogger Protocol Driver: Loguru logging configured to level INFO and file log/2026-03-19_12-52-42_log_usv.log. +2026-03-19 12:52:43.086 | INFO | kogger_protocol_driver:__init__:224 - KoggerSBPDevice configured for port /dev/ttyAMA4, baudrate 921600, address 0 +2026-03-19 12:52:43.087 | INFO | kogger_protocol_driver:_open_file:43 - CSV logging enabled to log/2026-03-19_12-52-42_AUV_usbl.csv +2026-03-19 12:52:43.089 | INFO | kogger_protocol_driver:_reader_thread_loop:491 - Reader thread started. +2026-03-19 12:52:43.090 | SUCCESS | kogger_protocol_driver:connect:314 - Successfully connected to /dev/ttyAMA4 at 921600 and started reader thread. +2026-03-19 12:52:43.114 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x20 +2026-03-19 12:52:43.128 | INFO | __main__:main:127 - set_auto_response_filter(0)=True +2026-03-19 12:52:43.133 | INFO | __main__:main:129 - set_auto_response_timeout(0xffffffff)=True +2026-03-19 12:52:43.137 | INFO | __main__:main:131 - set_auto_response_payload(0xff)=True +2026-03-19 12:52:44.167 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-19 12:52:45.181 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-19 12:52:46.187 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-19 12:52:47.194 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-19 12:52:48.199 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-19 12:53:02.544 | INFO | kogger_protocol_driver:_reader_thread_loop:568 - Reader thread finished. +2026-03-19 12:53:02.545 | INFO | kogger_protocol_driver:disconnect:352 - Serial port /dev/ttyAMA4 closed. +2026-03-19 12:53:02.546 | INFO | kogger_protocol_driver:disconnect:364 - Disconnected and cleaned up. diff --git a/driver/test/log/2026-03-20_13-29-40_AUV_usbl.csv b/driver/test/log/2026-03-20_13-29-40_AUV_usbl.csv new file mode 100644 index 0000000..a91bdad --- /dev/null +++ b/driver/test/log/2026-03-20_13-29-40_AUV_usbl.csv @@ -0,0 +1,171 @@ +2026-03-20 13:29:40.685622,SENT,"b'\xbbU\x00\x03 \x00#I'" +2026-03-20 13:29:40.686113,RECEIVED,"b'\xbb'" +2026-03-20 13:29:40.686631,RECEIVED,"b'U\x00A ""\x00\x0f\x00\x00\x00\x00\x00\x00\xa4\xdeX\xcb\x00\x02\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00=\xc7'" +2026-03-20 13:29:40.697732,SENT,"b'\xbbU\x00\xa2h\x04\x00\x00\x00\x00\x0e\xf2'" +2026-03-20 13:29:40.698036,RECEIVED,"b'\xbb'" +2026-03-20 13:29:40.698530,RECEIVED,"b'U\x00\xe1h\x03\x01\x0e\xf2Mk'" +2026-03-20 13:29:40.699549,SENT,"b'\xbbU\x00\x9ah\x04\xff\xff\xff\xff\x02\xb0'" +2026-03-20 13:29:40.700153,RECEIVED,"b'\xbb'" +2026-03-20 13:29:40.700901,RECEIVED,"b'U\x00\xd9h\x03\x01\x02\xb0\xf7\xe1'" +2026-03-20 13:29:40.702460,SENT,"b'\xbbU\x00\xaah\x01\xff\x12\xe1'" +2026-03-20 13:29:40.702795,RECEIVED,"b'\xbb'" +2026-03-20 13:29:40.703348,RECEIVED,"b'U\x00\xe9h\x03\x01\x12\xe1H\x92'" +2026-03-20 13:29:40.705177,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-20 13:29:40.705467,RECEIVED,"b'\xbb'" +2026-03-20 13:29:40.705933,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-20 13:29:41.707772,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-20 13:29:41.708136,RECEIVED,"b'\xbb'" +2026-03-20 13:29:41.708629,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-20 13:29:42.710038,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-20 13:29:42.710418,RECEIVED,"b'\xbb'" +2026-03-20 13:29:42.710945,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-20 13:29:43.712949,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-20 13:29:43.713285,RECEIVED,"b'\xbb'" +2026-03-20 13:29:43.713788,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-20 13:29:44.716604,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:29:44.717163,RECEIVED,"b'\xbb'" +2026-03-20 13:29:44.717754,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:29:45.719019,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:29:45.719331,RECEIVED,"b'\xbb'" +2026-03-20 13:29:45.719787,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:29:46.721761,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:29:46.722218,RECEIVED,"b'\xbb'" +2026-03-20 13:29:46.722973,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:29:47.724551,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:29:47.725083,RECEIVED,"b'\xbb'" +2026-03-20 13:29:47.725589,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:29:48.726724,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:29:48.727124,RECEIVED,"b'\xbb'" +2026-03-20 13:29:48.727642,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:29:49.729311,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:29:49.729972,RECEIVED,"b'\xbb'" +2026-03-20 13:29:49.730481,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:29:50.732974,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:29:50.733321,RECEIVED,"b'\xbb'" +2026-03-20 13:29:50.745741,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:29:51.735340,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:29:51.735698,RECEIVED,"b'\xbb'" +2026-03-20 13:29:51.736201,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:29:52.737902,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:29:52.738444,RECEIVED,"b'\xbb'" +2026-03-20 13:29:52.738981,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:29:53.740506,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:29:53.741278,RECEIVED,"b'\xbb'" +2026-03-20 13:29:53.741774,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:29:54.743950,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:29:54.744286,RECEIVED,"b'\xbb'" +2026-03-20 13:29:54.744782,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:29:55.746302,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:29:55.746747,RECEIVED,"b'\xbb'" +2026-03-20 13:29:55.752265,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:29:56.748667,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:29:56.749048,RECEIVED,"b'\xbb'" +2026-03-20 13:29:56.749546,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:29:57.752107,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:29:57.752642,RECEIVED,"b'\xbb'" +2026-03-20 13:29:57.753174,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:29:58.754402,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:29:58.754769,RECEIVED,"b'\xbb'" +2026-03-20 13:29:58.755267,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:29:59.756942,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:29:59.757496,RECEIVED,"b'\xbb'" +2026-03-20 13:29:59.758021,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:00.759423,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:00.759968,RECEIVED,"b'\xbb'" +2026-03-20 13:30:00.764284,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:01.762172,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:01.762762,RECEIVED,"b'\xbb'" +2026-03-20 13:30:01.763307,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:02.764335,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:02.764668,RECEIVED,"b'\xbb'" +2026-03-20 13:30:02.765179,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:03.767188,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:03.767518,RECEIVED,"b'\xbb'" +2026-03-20 13:30:03.768034,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:04.770094,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:04.770493,RECEIVED,"b'\xbb'" +2026-03-20 13:30:04.771154,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:05.773078,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:05.773608,RECEIVED,"b'\xbb'" +2026-03-20 13:30:05.774126,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:06.775738,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:06.776200,RECEIVED,"b'\xbb'" +2026-03-20 13:30:06.776696,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:07.778103,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:07.778635,RECEIVED,"b'\xbb'" +2026-03-20 13:30:07.779151,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:08.780532,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:08.781126,RECEIVED,"b'\xbb'" +2026-03-20 13:30:08.781632,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:09.783211,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:09.783757,RECEIVED,"b'\xbb'" +2026-03-20 13:30:09.784256,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:10.785543,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:10.786107,RECEIVED,"b'\xbb'" +2026-03-20 13:30:10.786639,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:11.788390,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:11.789317,RECEIVED,"b'\xbb'" +2026-03-20 13:30:11.789855,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:12.790970,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:12.791292,RECEIVED,"b'\xbb'" +2026-03-20 13:30:12.791782,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:13.793085,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:13.793639,RECEIVED,"b'\xbb'" +2026-03-20 13:30:13.794168,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:14.795325,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:14.795862,RECEIVED,"b'\xbb'" +2026-03-20 13:30:14.796378,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:15.798342,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:15.798981,RECEIVED,"b'\xbb'" +2026-03-20 13:30:15.799565,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:16.802200,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:16.802991,RECEIVED,"b'\xbb'" +2026-03-20 13:30:16.803606,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:17.804806,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:17.805175,RECEIVED,"b'\xbb'" +2026-03-20 13:30:17.805710,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:18.807125,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:18.807735,RECEIVED,"b'\xbb'" +2026-03-20 13:30:18.808259,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:19.809658,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:19.810065,RECEIVED,"b'\xbb'" +2026-03-20 13:30:19.810613,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:20.812778,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:20.813465,RECEIVED,"b'\xbb'" +2026-03-20 13:30:20.814135,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:21.815928,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:21.816587,RECEIVED,"b'\xbb'" +2026-03-20 13:30:21.817199,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:22.819069,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:22.819606,RECEIVED,"b'\xbb'" +2026-03-20 13:30:22.820110,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:23.821592,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:23.823013,RECEIVED,"b'\xbb'" +2026-03-20 13:30:23.823554,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:24.824027,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:24.824404,RECEIVED,"b'\xbb'" +2026-03-20 13:30:24.824900,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:25.826202,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:25.826795,RECEIVED,"b'\xbb'" +2026-03-20 13:30:25.827323,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:26.828607,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:26.829128,RECEIVED,"b'\xbb'" +2026-03-20 13:30:26.829628,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:27.830915,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:27.831230,RECEIVED,"b'\xbb'" +2026-03-20 13:30:27.831752,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:28.833564,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:28.834096,RECEIVED,"b'\xbb'" +2026-03-20 13:30:28.834595,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:29.836626,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:29.837565,RECEIVED,"b'\xbb'" +2026-03-20 13:30:29.838242,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:30.839058,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:30.839598,RECEIVED,"b'\xbb'" +2026-03-20 13:30:30.840193,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:31.842311,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:31.842829,RECEIVED,"b'\xbb'" +2026-03-20 13:30:31.843374,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 13:30:32.844556,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 13:30:32.845112,RECEIVED,"b'\xbb'" +2026-03-20 13:30:32.845615,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" diff --git a/driver/test/log/2026-03-20_13-29-40_log_usv.log b/driver/test/log/2026-03-20_13-29-40_log_usv.log new file mode 100644 index 0000000..0e0213a --- /dev/null +++ b/driver/test/log/2026-03-20_13-29-40_log_usv.log @@ -0,0 +1,11 @@ +2026-03-20 13:29:40.681 | INFO | kogger_protocol_driver:setup_logging:81 - Kogger Protocol Driver: Loguru logging configured to level INFO and file log/2026-03-20_13-29-40_log_usv.log. +2026-03-20 13:29:40.682 | INFO | kogger_protocol_driver:__init__:224 - KoggerSBPDevice configured for port /dev/ttyAMA4, baudrate 921600, address 0 +2026-03-20 13:29:40.683 | INFO | kogger_protocol_driver:_open_file:43 - CSV logging enabled to log/2026-03-20_13-29-40_AUV_usbl.csv +2026-03-20 13:29:40.684 | INFO | kogger_protocol_driver:_reader_thread_loop:491 - Reader thread started. +2026-03-20 13:29:40.684 | SUCCESS | kogger_protocol_driver:connect:314 - Successfully connected to /dev/ttyAMA4 at 921600 and started reader thread. +2026-03-20 13:29:40.699 | INFO | __main__:main:127 - set_auto_response_filter(0)=True +2026-03-20 13:29:40.701 | INFO | __main__:main:129 - set_auto_response_timeout(0xffffffff)=True +2026-03-20 13:29:40.703 | INFO | __main__:main:131 - set_auto_response_payload(0xff)=True +2026-03-20 13:30:33.550 | INFO | kogger_protocol_driver:_reader_thread_loop:568 - Reader thread finished. +2026-03-20 13:30:33.552 | INFO | kogger_protocol_driver:disconnect:352 - Serial port /dev/ttyAMA4 closed. +2026-03-20 13:30:33.552 | INFO | kogger_protocol_driver:disconnect:364 - Disconnected and cleaned up. diff --git a/driver/test/log/2026-03-20_13-35-32_AUV_usbl.csv b/driver/test/log/2026-03-20_13-35-32_AUV_usbl.csv new file mode 100644 index 0000000..e69de29 diff --git a/driver/test/log/2026-03-20_13-35-32_log_auv.log b/driver/test/log/2026-03-20_13-35-32_log_auv.log new file mode 100644 index 0000000..108e1fe --- /dev/null +++ b/driver/test/log/2026-03-20_13-35-32_log_auv.log @@ -0,0 +1,5 @@ +2026-03-20 13:35:32.057 | INFO | kogger_protocol_driver:setup_logging:81 - Kogger Protocol Driver: Loguru logging configured to level INFO and file log/2026-03-20_13-35-32_log_auv.log. +2026-03-20 13:35:32.057 | INFO | kogger_protocol_driver:__init__:224 - KoggerSBPDevice configured for port /dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0, baudrate 921600, address 0 +2026-03-20 13:35:32.058 | INFO | kogger_protocol_driver:_open_file:43 - CSV logging enabled to log/2026-03-20_13-35-32_AUV_usbl.csv +2026-03-20 13:35:32.058 | ERROR | kogger_protocol_driver:connect:317 - Error connecting to /dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0: [Errno 2] could not open port /dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0: [Errno 2] No such file or directory: '/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0' +2026-03-20 13:35:32.059 | WARNING | kogger_protocol_driver:disconnect:341 - Not connected or already disconnected. diff --git a/driver/test/log/2026-03-20_13-35-54_AUV_usbl.csv b/driver/test/log/2026-03-20_13-35-54_AUV_usbl.csv new file mode 100644 index 0000000..38d3cc3 --- /dev/null +++ b/driver/test/log/2026-03-20_13-35-54_AUV_usbl.csv @@ -0,0 +1,81 @@ +2026-03-20 13:35:54.062535,SENT,"b'\xbbU\x00\x03\x04\x00\x07\x11'" +2026-03-20 13:35:54.093789,SENT,"b'\xbbU\x00\x03 \x00#I'" +2026-03-20 13:35:54.094104,RECEIVED,"b'\xbb'" +2026-03-20 13:35:54.094674,RECEIVED,"b'U\x00A ""\x00\x0f\x00\x00\x00\x00\x00\x00\xa4\xdeX\xcb\x00\x02\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00=\xc7'" +2026-03-20 13:35:54.105845,SENT,"b'\xbbU\x00\x03!\x00$K'" +2026-03-20 13:35:54.106104,RECEIVED,"b'\xbb'" +2026-03-20 13:35:54.106595,RECEIVED,"b'U\x00A!\x01\x00ci'" +2026-03-20 13:35:54.117558,SENT,"b'\xbbU\x00\x03e\x00h\xd3'" +2026-03-20 13:35:54.148832,SENT,"b'\xbbU\x00\xa2h\x04\x00\x00\x00\x00\x0e\xf2'" +2026-03-20 13:35:54.149126,RECEIVED,"b'\xbb'" +2026-03-20 13:35:54.149675,RECEIVED,"b'U\x00\xe1h\x03\x01\x0e\xf2Mk'" +2026-03-20 13:35:54.151330,SENT,"b'\xbbU\x00\x9ah\x04\xff\xff\xff\xff\x02\xb0'" +2026-03-20 13:35:54.152194,RECEIVED,"b'\xbb'" +2026-03-20 13:35:54.152954,RECEIVED,"b'U\x00\xd9h\x03\x01\x02\xb0\xf7\xe1'" +2026-03-20 13:35:54.154269,SENT,"b'\xbbU\x00\xaah\x01\xff\x12\xe1'" +2026-03-20 13:35:54.154500,RECEIVED,"b'\xbb'" +2026-03-20 13:35:54.155038,RECEIVED,"b'U\x00\xe9h\x03\x01\x12\xe1H\x92'" +2026-03-20 13:35:59.189642,RECEIVED,"b'\xbb'" +2026-03-20 13:35:59.190610,RECEIVED,"b'U\x00Ae\x98\x03\x00\x00\x00\xb5\xb458\x00\x00\x00\x00\x00\x00\x00\x00B\xa6\xaf\x01\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\x00\x00\x00\x00\xb6B\x00\x00\x00\x0033\x8a\xc2\x00\x00\x00\x00\xfe\xe6\x8aA\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\xac\xfb\x98\xc2WAa\xc1\x15K'" +2026-03-20 13:35:59.191222,RECEIVED,"b'-\xc3\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f""\xf2'" +2026-03-20 13:36:00.159731,SENT,"b'\xbbU\x00\xaah\x01\xff\x12\xe1'" +2026-03-20 13:36:00.160188,RECEIVED,"b'\xbb'" +2026-03-20 13:36:00.160709,RECEIVED,"b'U\x00\xe9h\x03\x01\x12\xe1H\x92'" +2026-03-20 13:36:03.182896,RECEIVED,"b'\xbb'" +2026-03-20 13:36:03.183435,RECEIVED,"b'U\x00Ae\x98\x04\x00\x00\x00r\xa3r8\x00\x00\x00\x00\x00\x00\x00\x00\x9b|\xb1\x01\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\x00\x00f\xe6\x1b\xc3\x00\x00\x00\x0043\x83\xc1\x00\x00\x00\x00\x84\xf6\x88A\x00\x00\xc0\x7f\x00\x00'" +2026-03-20 13:36:03.183903,RECEIVED,"b'\xc0\xff\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\xc2\x1c\x9d\xc2\x18\xc1Y\xc1\xcc\xdf.\xc3\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-20 13:36:03.184392,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00'" +2026-03-20 13:36:03.184851,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\xbd('" +2026-03-20 13:36:19.222085,RECEIVED,"b'\xbb'" +2026-03-20 13:36:19.222666,RECEIVED,"b'U\x00Ae\x98\x01\x00\x00\x00\xccfg9\x00\x00\x00\x00\x00\x00\x00\x00A\xd5\xb8\x01\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\x00\x00\xcd\xcc/\xc3\x00\x00\x00\x00\xff\xffCB\x00\x00\x00\x00\x8b\xe7\xa9A\x00\x00\xc0\x7f\x00\x00'" +2026-03-20 13:36:19.223147,RECEIVED,"b'\xc0\xff\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00`\xe1\xad\xc2d g\xc1\xe8\xcf+\xc3\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-20 13:36:19.223606,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7fZ\xf7'" +2026-03-20 13:36:20.248037,RECEIVED,"b'\xbb'" +2026-03-20 13:36:20.248606,RECEIVED,"b'U\x00Ae\x98\x04\x00\x00\x00[\x0ew9\x00\x00\x00\x00\x00\x00\x00\x00\xc1J\xb9\x01\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\x00\x00\x00\x00*\xc3\x00\x00\x00\x00ef0B\x00\x00\x00\x00\xc9\x87\x9eA\x00\x00\xc0\x7f\x00\x00'" +2026-03-20 13:36:20.249080,RECEIVED,"b""\xc0\xff\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\xa9-\xad\xc2\xd1HW\xc1\x05',\xc3\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00""" +2026-03-20 13:36:20.249590,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x19]'" +2026-03-20 13:36:21.238002,RECEIVED,"b'\xbb'" +2026-03-20 13:36:21.238660,RECEIVED,"b'U\x00Ae\x98\x05\x00\x00\x00\x95)\x869\x00\x00\x00\x00\x00\x00\x00\x00L\xc0\xb9\x01\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\x00\x00\x00\x80)\xc3\x00\x00\x00\x00efJB\x00\x00\x00\x00\xd2\xf7\xa1A\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-20 13:36:21.239190,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x005\x02\xab\xc2\x03\xc4k\xc1\x86N-\xc3\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00'" +2026-03-20 13:36:21.239659,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f^\xa0'" +2026-03-20 13:36:22.227912,RECEIVED,"b'\xbb'" +2026-03-20 13:36:22.228483,RECEIVED,"b'U\x00Ae\x98\x05\x00\x00\x00\xb4D\x959\x00\x00\x00\x00\x00\x00\x00\x00\xcd5\xba\x01\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\x00\x005\xb3,\xc3\x00\x00\x00\x00\xcb\xccDB\x00\x00\x00\x00\x0e \xaeA\x00\x00\xc0\x7f\x00\x00'" +2026-03-20 13:36:22.228959,RECEIVED,"b'\xc0\xff\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x17\x9b\xab\xc2\xd3x~\xc1\x14\xce,\xc3\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-20 13:36:22.229490,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f`\xa1'" +2026-03-20 13:36:23.251746,RECEIVED,"b'\xbb'" +2026-03-20 13:36:23.252436,RECEIVED,"b""U\x00Ae\x98\x05\x00\x00\x00\x1c\xe1\xa49\x00\x00\x00\x00\x00\x00\x00\x00N\xab\xba\x01\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\x00\x00\xceL'\xc3\x00\x00\x00\x00\xfe\xff=B\x00\x00\x00\x00s\x02\x9eA\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00""" +2026-03-20 13:36:23.252980,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00;D\xab\xc2f\\J\xc1\xa6\xd8+\xc3\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00'" +2026-03-20 13:36:23.253531,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\xd9\xc9'" +2026-03-20 13:36:24.241702,RECEIVED,"b'\xbb'" +2026-03-20 13:36:24.242474,RECEIVED,"b'U\x00Ae\x98\x06\x00\x00\x00I\xfc\xb39\x00\x00\x00\x00\x00\x00\x00\x00\xd8 \xbb\x01\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\x00\x00432\xc3\x00\x00\x00\x00ff2B\x00\x00\x00\x00\x86\x8b\xaaA\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-20 13:36:24.243066,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00L\xaf\xaa\xc2S\x08\x85\xc1\n\xe7-\xc3\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00'" +2026-03-20 13:36:24.243548,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\xb9\xd8'" +2026-03-20 13:36:25.263660,RECEIVED,"b'\xbb'" +2026-03-20 13:36:25.264421,RECEIVED,"b'U\x00Ae\x98\x05\x00\x00\x00z\x98\xc39\x00\x00\x00\x00\x00\x00\x00\x00d\x96\xbb\x01\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\x00\x00\x9b\x99-\xc3\x00\x00\x00\x00\x98\x995B\x00\x00\x00\x00\x93/\x9cA\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8'" +2026-03-20 13:36:25.265012,RECEIVED,"b'\x7f\x00\x00\x00\x00n\xeb\xab\xc2\xb8\xb1F\xc1\xc1\x12,\xc3\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0'" +2026-03-20 13:36:25.265532,RECEIVED,"b'\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7fy\x9f'" +2026-03-20 13:36:26.176979,SENT,"b'\xbbU\x00\xaah\x01\x05\x18\xe7'" +2026-03-20 13:36:26.177475,RECEIVED,"b'\xbb'" +2026-03-20 13:36:26.177969,RECEIVED,"b'U\x00\xe9h\x03\x01\x18\xe7T\xa4'" +2026-03-20 13:36:26.253913,RECEIVED,"b'\xbb'" +2026-03-20 13:36:26.254833,RECEIVED,"b'U\x00Ae\x98\x05\x00\x00\x00\x96\xb3\xd29\x00\x00\x00\x00\x00\x00\x00\x00\xfa\x0b\xbc\x01\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\x00\x00\xceL\x1d\xc3\x00\x00\x00\x00\x97\x99mB\x00\x00\x00\x00\xc0\x0b\xa3A\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\xc4\xec\xaa\xc2\x16pi\xc1\xb47'" +2026-03-20 13:36:26.255563,RECEIVED,"b'-\xc3\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x97\xff'" +2026-03-20 13:36:27.243846,RECEIVED,"b'\xbb'" +2026-03-20 13:36:27.244622,RECEIVED,"b'U\x00Ae\x98\x06\x00\x00\x00\xc0\xce\xe19\x00\x00\x00\x00\x00\x00\x00\x00\x92\x81\xbc\x01\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\x00\x00\xcd\xcc/\xc3\x00\x00\x00\x00\xcb\xccNB\x00\x00\x00\x00\xc2k\xa5A\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-20 13:36:27.245273,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00q\x88\xaa\xc24os\xc1F0,\xc3\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00'" +2026-03-20 13:36:27.245761,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f1\xc1'" +2026-03-20 13:36:28.269778,RECEIVED,"b'\xbb'" +2026-03-20 13:36:28.270363,RECEIVED,"b'U\x00Ae\x98\x05\x00\x00\x00Zv\xf19\x00\x00\x00\x00\x00\x00\x00\x00\x1f\xf7\xbc\x01\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\x00\x00gf2\xc3\x00\x00\x00\x00\xcb\xccRB\x00\x00\x00\x00YH\x99A\x00\x00\xc0\x7f\x00\x00'" +2026-03-20 13:36:28.270947,RECEIVED,"b'\xc0\xff\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00p=\xaa\xc2\xbe\xb6_\xc1\\4-\xc3\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00'" +2026-03-20 13:36:28.271457,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\xb4\x00'" +2026-03-20 13:36:29.259732,RECEIVED,"b'\xbb'" +2026-03-20 13:36:29.260337,RECEIVED,"b""U\x00Ae\x98\x04\x00\x00\x00i\x91\x00:\x00\x00\x00\x00\x00\x00\x00\x00\xa3l\xbd\x01\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\x00\x00\x9a\x19'\xc3\x00\x00\x00\x00\xfe\xff{B\x00\x00\x00\x00\x04\x91\xa8A\x00\x00\xc0\x7f\x00\x00""" +2026-03-20 13:36:29.260921,RECEIVED,"b'\xc0\xff\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00-<\xaa\xc2\xddhu\xc1Y\x8e+\xc3\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00'" +2026-03-20 13:36:29.261362,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7fL='" +2026-03-20 13:36:30.283273,RECEIVED,"b'\xbb'" +2026-03-20 13:36:30.283980,RECEIVED,"b'U\x00Ae\x98\x06\x00\x00\x00\xea-\x10:\x00\x00\x00\x00\x00\x00\x00\x00%\xe2\xbd\x01\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\x00\x00433\xc3\x00\x00\x00\x00\xcc\xcc*B\x00\x00\x00\x00\x0b7\x8bA\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-20 13:36:30.284828,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00F\xa1\xa9\xc2@5_\xc1\xd3\xf5,\xc3\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x869'" +2026-03-20 13:36:31.273220,RECEIVED,"b'\xbb'" +2026-03-20 13:36:31.273794,RECEIVED,"b'U\x00Ae\x98\x00\x00\x00\x00\x08I\x1f:\x00\x00\x00\x00\x00\x00\x00\x00\xb0W\xbe\x01\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\x00\x00\x98\x99\xddA\x00\x00\x00\x00\x9a\x99\xe6\xc2\x00\x00\x00\x009\xcc\x92A\x00\x00\xc0\x7f\x00\x00'" +2026-03-20 13:36:31.274340,RECEIVED,"b'\xc0\xff\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x82\x95\xa9\xc25\xaf`\xc1\xd2\xf0+\xc3\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xc0\x7f\x00\x00\xc0\xff\x00\x00'" +2026-03-20 13:36:31.274867,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\xf6\xbf'" diff --git a/driver/test/log/2026-03-20_13-35-54_log_auv.log b/driver/test/log/2026-03-20_13-35-54_log_auv.log new file mode 100644 index 0000000..a0902cf --- /dev/null +++ b/driver/test/log/2026-03-20_13-35-54_log_auv.log @@ -0,0 +1,77 @@ +2026-03-20 13:35:54.058 | INFO | kogger_protocol_driver:setup_logging:81 - Kogger Protocol Driver: Loguru logging configured to level INFO and file log/2026-03-20_13-35-54_log_auv.log. +2026-03-20 13:35:54.059 | INFO | kogger_protocol_driver:__init__:224 - KoggerSBPDevice configured for port /dev/ttyAMA4, baudrate 921600, address 0 +2026-03-20 13:35:54.059 | INFO | kogger_protocol_driver:_open_file:43 - CSV logging enabled to log/2026-03-20_13-35-54_AUV_usbl.csv +2026-03-20 13:35:54.061 | INFO | kogger_protocol_driver:_reader_thread_loop:491 - Reader thread started. +2026-03-20 13:35:54.061 | SUCCESS | kogger_protocol_driver:connect:314 - Successfully connected to /dev/ttyAMA4 at 921600 and started reader thread. +2026-03-20 13:35:54.082 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x4 +2026-03-20 13:35:54.137 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x65 +2026-03-20 13:35:54.150 | INFO | __main__:main:136 - set_auto_response_filter(0)=True +2026-03-20 13:35:54.153 | INFO | __main__:main:138 - set_auto_response_timeout(0xffffffff)=True +2026-03-20 13:35:54.155 | INFO | __main__:main:140 - set_auto_response_payload(0xff)=True +2026-03-20 13:35:54.155 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 13:35:55.156 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 13:35:56.156 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 13:35:57.157 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 13:35:58.157 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 13:35:59.158 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 13:35:59.192 | ERROR | kogger_protocol_driver:_reader_thread_loop:544 - Error in precallback for ID 0x65: float modulo +2026-03-20 13:35:59.193 | INFO | __main__:test_callback:57 - test!!!!!!{'id': 255, 'role': 0, 'reserved': 0, 'timestamp_us': 943043765, 'ping_counter': 0, 'carrier_counter': 28288578, 'distance_m': nan, 'distance_unc': 0.0, 'azimuth_deg': 91.0, 'azimuth_unc': 0.0, 'elevation_deg': -69.0999984741211, 'elevation_unc': 0.0, 'snr': 17.362789154052734, 'beacon_x_m': nan, 'beacon_y_m': nan, 'beacon_latitude': nan, 'beacon_longitude': nan, 'beacon_depth': 0.0, 'usbl_yaw': -76.49154663085938, 'usbl_pitch': -14.078452110290527, 'usbl_roll': -173.2932891845703, 'usbl_latitude': nan, 'usbl_longitude': nan, 'last_iTOW': 0, 'beacon_n_m': nan, 'beacon_e_m': nan, 'code_snr': [nan, nan, nan, nan, nan, nan, nan, nan], 'timestamp_pi': 1774013759.1926465} +2026-03-20 13:36:00.159 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:00.161 | INFO | __main__:main:154 - antenna.set_auto_response_payload(255)=True +2026-03-20 13:36:01.161 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:02.162 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:03.162 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:03.185 | ERROR | kogger_protocol_driver:_reader_thread_loop:544 - Error in precallback for ID 0x65: float modulo +2026-03-20 13:36:03.186 | INFO | __main__:test_callback:57 - test!!!!!!{'id': 255, 'role': 0, 'reserved': 0, 'timestamp_us': 947037042, 'ping_counter': 0, 'carrier_counter': 28408987, 'distance_m': nan, 'distance_unc': 0.0, 'azimuth_deg': -155.89999389648438, 'azimuth_unc': 0.0, 'elevation_deg': -16.400001525878906, 'elevation_unc': 0.0, 'snr': 17.12036895751953, 'beacon_x_m': nan, 'beacon_y_m': nan, 'beacon_latitude': nan, 'beacon_longitude': nan, 'beacon_depth': 0.0, 'usbl_yaw': -78.55616760253906, 'usbl_pitch': -13.609642028808594, 'usbl_roll': -174.87420654296875, 'usbl_latitude': nan, 'usbl_longitude': nan, 'last_iTOW': 0, 'beacon_n_m': nan, 'beacon_e_m': nan, 'code_snr': [nan, nan, nan, nan, nan, nan, nan, nan], 'timestamp_pi': 1774013763.18576} +2026-03-20 13:36:04.163 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:05.163 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:06.164 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:07.165 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:08.165 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:09.166 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:10.166 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:11.167 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:12.167 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:13.168 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:14.169 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:15.169 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:16.170 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:17.170 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:18.171 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:19.172 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:19.224 | ERROR | kogger_protocol_driver:_reader_thread_loop:544 - Error in precallback for ID 0x65: float modulo +2026-03-20 13:36:19.225 | INFO | __main__:test_callback:57 - test!!!!!!{'id': 255, 'role': 0, 'reserved': 0, 'timestamp_us': 963077836, 'ping_counter': 0, 'carrier_counter': 28890433, 'distance_m': nan, 'distance_unc': 0.0, 'azimuth_deg': -175.8000030517578, 'azimuth_unc': 0.0, 'elevation_deg': 48.999996185302734, 'elevation_unc': 0.0, 'snr': 21.23805809020996, 'beacon_x_m': nan, 'beacon_y_m': nan, 'beacon_latitude': nan, 'beacon_longitude': nan, 'beacon_depth': 0.0, 'usbl_yaw': -86.940185546875, 'usbl_pitch': -14.44540786743164, 'usbl_roll': -171.8121337890625, 'usbl_latitude': nan, 'usbl_longitude': nan, 'last_iTOW': 0, 'beacon_n_m': nan, 'beacon_e_m': nan, 'code_snr': [nan, nan, nan, nan, nan, nan, nan, nan], 'timestamp_pi': 1774013779.2245278} +2026-03-20 13:36:20.172 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:20.250 | ERROR | kogger_protocol_driver:_reader_thread_loop:544 - Error in precallback for ID 0x65: float modulo +2026-03-20 13:36:20.251 | INFO | __main__:test_callback:57 - test!!!!!!{'id': 255, 'role': 0, 'reserved': 0, 'timestamp_us': 964103771, 'ping_counter': 0, 'carrier_counter': 28920513, 'distance_m': nan, 'distance_unc': 0.0, 'azimuth_deg': -170.0, 'azimuth_unc': 0.0, 'elevation_deg': 44.09999465942383, 'elevation_unc': 0.0, 'snr': 19.816301345825195, 'beacon_x_m': nan, 'beacon_y_m': nan, 'beacon_latitude': nan, 'beacon_longitude': nan, 'beacon_depth': 0.0, 'usbl_yaw': -86.58917999267578, 'usbl_pitch': -13.455277442932129, 'usbl_roll': -172.1524200439453, 'usbl_latitude': nan, 'usbl_longitude': nan, 'last_iTOW': 0, 'beacon_n_m': nan, 'beacon_e_m': nan, 'code_snr': [nan, nan, nan, nan, nan, nan, nan, nan], 'timestamp_pi': 1774013780.2505426} +2026-03-20 13:36:21.173 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:22.173 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:22.230 | ERROR | kogger_protocol_driver:_reader_thread_loop:544 - Error in precallback for ID 0x65: float modulo +2026-03-20 13:36:22.231 | INFO | __main__:test_callback:57 - test!!!!!!{'id': 255, 'role': 0, 'reserved': 0, 'timestamp_us': 966083764, 'ping_counter': 0, 'carrier_counter': 28980685, 'distance_m': nan, 'distance_unc': 0.0, 'azimuth_deg': -172.7000274658203, 'azimuth_unc': 0.0, 'elevation_deg': 49.19999313354492, 'elevation_unc': 0.0, 'snr': 21.76565170288086, 'beacon_x_m': nan, 'beacon_y_m': nan, 'beacon_latitude': nan, 'beacon_longitude': nan, 'beacon_depth': 0.0, 'usbl_yaw': -85.80290985107422, 'usbl_pitch': -15.904498100280762, 'usbl_roll': -172.80499267578125, 'usbl_latitude': nan, 'usbl_longitude': nan, 'last_iTOW': 0, 'beacon_n_m': nan, 'beacon_e_m': nan, 'code_snr': [nan, nan, nan, nan, nan, nan, nan, nan], 'timestamp_pi': 1774013782.2305374} +2026-03-20 13:36:23.174 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:23.254 | ERROR | kogger_protocol_driver:_reader_thread_loop:544 - Error in precallback for ID 0x65: float modulo +2026-03-20 13:36:23.255 | INFO | __main__:test_callback:57 - test!!!!!!{'id': 255, 'role': 0, 'reserved': 0, 'timestamp_us': 967106844, 'ping_counter': 0, 'carrier_counter': 29010766, 'distance_m': nan, 'distance_unc': 0.0, 'azimuth_deg': -167.30001831054688, 'azimuth_unc': 0.0, 'elevation_deg': 47.49999237060547, 'elevation_unc': 0.0, 'snr': 19.751195907592773, 'beacon_x_m': nan, 'beacon_y_m': nan, 'beacon_latitude': nan, 'beacon_longitude': nan, 'beacon_depth': 0.0, 'usbl_yaw': -85.63326263427734, 'usbl_pitch': -12.647558212280273, 'usbl_roll': -171.84628295898438, 'usbl_latitude': nan, 'usbl_longitude': nan, 'last_iTOW': 0, 'beacon_n_m': nan, 'beacon_e_m': nan, 'code_snr': [nan, nan, nan, nan, nan, nan, nan, nan], 'timestamp_pi': 1774013783.2544994} +2026-03-20 13:36:24.175 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:25.175 | INFO | __main__:main:147 - response_received=255 +2026-03-20 13:36:25.266 | ERROR | kogger_protocol_driver:_reader_thread_loop:544 - Error in precallback for ID 0x65: float modulo +2026-03-20 13:36:25.266 | INFO | __main__:test_callback:57 - test!!!!!!{'id': 5, 'role': 0, 'reserved': 0, 'timestamp_us': 969119866, 'ping_counter': 0, 'carrier_counter': 29070948, 'distance_m': nan, 'distance_unc': 0.0, 'azimuth_deg': -173.6000213623047, 'azimuth_unc': 0.0, 'elevation_deg': 45.399993896484375, 'elevation_unc': 0.0, 'snr': 19.523229598999023, 'beacon_x_m': nan, 'beacon_y_m': nan, 'beacon_latitude': nan, 'beacon_longitude': nan, 'beacon_depth': 0.0, 'usbl_yaw': -85.95982360839844, 'usbl_pitch': -12.418388366699219, 'usbl_roll': -172.07325744628906, 'usbl_latitude': nan, 'usbl_longitude': nan, 'last_iTOW': 0, 'beacon_n_m': nan, 'beacon_e_m': nan, 'code_snr': [nan, nan, nan, nan, nan, nan, nan, nan], 'timestamp_pi': 1774013785.2664862} +2026-03-20 13:36:26.176 | INFO | __main__:main:147 - response_received=5 +2026-03-20 13:36:26.178 | INFO | __main__:main:154 - antenna.set_auto_response_payload(5)=True +2026-03-20 13:36:27.179 | INFO | __main__:main:147 - response_received=5 +2026-03-20 13:36:27.247 | ERROR | kogger_protocol_driver:_reader_thread_loop:544 - Error in precallback for ID 0x65: float modulo +2026-03-20 13:36:27.248 | INFO | __main__:test_callback:57 - test!!!!!!{'id': 5, 'role': 0, 'reserved': 0, 'timestamp_us': 971099840, 'ping_counter': 0, 'carrier_counter': 29131154, 'distance_m': nan, 'distance_unc': 0.0, 'azimuth_deg': -175.8000030517578, 'azimuth_unc': 0.0, 'elevation_deg': 51.69999313354492, 'elevation_unc': 0.0, 'snr': 20.677616119384766, 'beacon_x_m': nan, 'beacon_y_m': nan, 'beacon_latitude': nan, 'beacon_longitude': nan, 'beacon_depth': 0.0, 'usbl_yaw': -85.26648712158203, 'usbl_pitch': -15.214649200439453, 'usbl_roll': -172.18856811523438, 'usbl_latitude': nan, 'usbl_longitude': nan, 'last_iTOW': 0, 'beacon_n_m': nan, 'beacon_e_m': nan, 'code_snr': [nan, nan, nan, nan, nan, nan, nan, nan], 'timestamp_pi': 1774013787.2476614} +2026-03-20 13:36:28.179 | INFO | __main__:main:147 - response_received=5 +2026-03-20 13:36:28.272 | ERROR | kogger_protocol_driver:_reader_thread_loop:544 - Error in precallback for ID 0x65: float modulo +2026-03-20 13:36:28.273 | INFO | __main__:test_callback:57 - test!!!!!!{'id': 5, 'role': 0, 'reserved': 0, 'timestamp_us': 972125786, 'ping_counter': 0, 'carrier_counter': 29161247, 'distance_m': nan, 'distance_unc': 0.0, 'azimuth_deg': -178.40000915527344, 'azimuth_unc': 0.0, 'elevation_deg': 52.69999313354492, 'elevation_unc': 0.0, 'snr': 19.16032600402832, 'beacon_x_m': nan, 'beacon_y_m': nan, 'beacon_latitude': nan, 'beacon_longitude': nan, 'beacon_depth': 0.0, 'usbl_yaw': -85.1199951171875, 'usbl_pitch': -13.982114791870117, 'usbl_roll': -173.20452880859375, 'usbl_latitude': nan, 'usbl_longitude': nan, 'last_iTOW': 0, 'beacon_n_m': nan, 'beacon_e_m': nan, 'code_snr': [nan, nan, nan, nan, nan, nan, nan, nan], 'timestamp_pi': 1774013788.2723386} +2026-03-20 13:36:29.180 | INFO | __main__:main:147 - response_received=5 +2026-03-20 13:36:30.181 | INFO | __main__:main:147 - response_received=5 +2026-03-20 13:36:30.286 | ERROR | kogger_protocol_driver:_reader_thread_loop:544 - Error in precallback for ID 0x65: float modulo +2026-03-20 13:36:30.287 | INFO | __main__:test_callback:57 - test!!!!!!{'id': 5, 'role': 0, 'reserved': 0, 'timestamp_us': 974138858, 'ping_counter': 0, 'carrier_counter': 29221413, 'distance_m': nan, 'distance_unc': 0.0, 'azimuth_deg': -179.20001220703125, 'azimuth_unc': 0.0, 'elevation_deg': 42.69999694824219, 'elevation_unc': 0.0, 'snr': 17.40187644958496, 'beacon_x_m': nan, 'beacon_y_m': nan, 'beacon_latitude': nan, 'beacon_longitude': nan, 'beacon_depth': 0.0, 'usbl_yaw': -84.81498718261719, 'usbl_pitch': -13.95050048828125, 'usbl_roll': -172.9602508544922, 'usbl_latitude': nan, 'usbl_longitude': nan, 'last_iTOW': 0, 'beacon_n_m': nan, 'beacon_e_m': nan, 'code_snr': [nan, nan, nan, nan, nan, nan, nan, nan], 'timestamp_pi': 1774013790.2861822} +2026-03-20 13:36:31.181 | INFO | __main__:main:147 - response_received=5 +2026-03-20 13:36:32.182 | INFO | __main__:main:147 - response_received=5 +2026-03-20 13:36:33.182 | INFO | __main__:main:147 - response_received=5 +2026-03-20 13:36:34.183 | INFO | __main__:main:147 - response_received=5 +2026-03-20 13:36:35.183 | INFO | __main__:main:147 - response_received=5 +2026-03-20 13:36:35.605 | INFO | kogger_protocol_driver:_reader_thread_loop:568 - Reader thread finished. +2026-03-20 13:36:35.606 | INFO | kogger_protocol_driver:disconnect:352 - Serial port /dev/ttyAMA4 closed. +2026-03-20 13:36:35.607 | INFO | kogger_protocol_driver:disconnect:364 - Disconnected and cleaned up. diff --git a/driver/test/log/2026-03-20_14-32-59_AUV_usbl.csv b/driver/test/log/2026-03-20_14-32-59_AUV_usbl.csv new file mode 100644 index 0000000..877af86 --- /dev/null +++ b/driver/test/log/2026-03-20_14-32-59_AUV_usbl.csv @@ -0,0 +1,7 @@ +2026-03-20 14:32:59.498952,SENT,"b'\xbbU\x00\x03\x04\x00\x07\x11'" +2026-03-20 14:32:59.530348,SENT,"b'\xbbU\x00\x03 \x00#I'" +2026-03-20 14:32:59.561610,SENT,"b'\xbbU\x00\x03!\x00$K'" +2026-03-20 14:32:59.593257,SENT,"b'\xbbU\x00\x03e\x00h\xd3'" +2026-03-20 14:32:59.624624,SENT,"b'\xbbU\x00\xa2h\x04\x00\x00\x00\x00\x0e\xf2'" +2026-03-20 14:32:59.645984,SENT,"b'\xbbU\x00\x9ah\x04\xff\xff\xff\xff\x02\xb0'" +2026-03-20 14:32:59.667180,SENT,"b'\xbbU\x00\xaah\x01\xff\x12\xe1'" diff --git a/driver/test/log/2026-03-20_14-32-59_log_auv.log b/driver/test/log/2026-03-20_14-32-59_log_auv.log new file mode 100644 index 0000000..7657d72 --- /dev/null +++ b/driver/test/log/2026-03-20_14-32-59_log_auv.log @@ -0,0 +1,28 @@ +2026-03-20 14:32:59.493 | INFO | kogger_protocol_driver:setup_logging:81 - Kogger Protocol Driver: Loguru logging configured to level INFO and file log/2026-03-20_14-32-59_log_auv.log. +2026-03-20 14:32:59.494 | INFO | kogger_protocol_driver:__init__:224 - KoggerSBPDevice configured for port /dev/ttyAMA4, baudrate 921600, address 0 +2026-03-20 14:32:59.495 | INFO | kogger_protocol_driver:_open_file:43 - CSV logging enabled to log/2026-03-20_14-32-59_AUV_usbl.csv +2026-03-20 14:32:59.497 | INFO | kogger_protocol_driver:_reader_thread_loop:491 - Reader thread started. +2026-03-20 14:32:59.497 | SUCCESS | kogger_protocol_driver:connect:314 - Successfully connected to /dev/ttyAMA4 at 921600 and started reader thread. +2026-03-20 14:32:59.519 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x4 +2026-03-20 14:32:59.550 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x20 +2026-03-20 14:32:59.581 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x21 +2026-03-20 14:32:59.613 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x65 +2026-03-20 14:32:59.644 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:32:59.645 | INFO | __main__:main:136 - set_auto_response_filter(0)=None +2026-03-20 14:32:59.666 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:32:59.666 | INFO | __main__:main:138 - set_auto_response_timeout(0xffffffff)=None +2026-03-20 14:32:59.687 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:32:59.687 | INFO | __main__:main:140 - set_auto_response_payload(0xff)=None +2026-03-20 14:32:59.688 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:33:00.688 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:33:01.688 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:33:02.689 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:33:03.690 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:33:04.690 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:33:05.691 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:33:06.692 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:33:07.692 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:33:08.693 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:33:09.565 | INFO | kogger_protocol_driver:_reader_thread_loop:568 - Reader thread finished. +2026-03-20 14:33:09.566 | INFO | kogger_protocol_driver:disconnect:352 - Serial port /dev/ttyAMA4 closed. +2026-03-20 14:33:09.566 | INFO | kogger_protocol_driver:disconnect:364 - Disconnected and cleaned up. diff --git a/driver/test/log/2026-03-20_14-34-01_AUV_usbl.csv b/driver/test/log/2026-03-20_14-34-01_AUV_usbl.csv new file mode 100644 index 0000000..e633a4b --- /dev/null +++ b/driver/test/log/2026-03-20_14-34-01_AUV_usbl.csv @@ -0,0 +1,7 @@ +2026-03-20 14:34:01.918493,SENT,"b'\xbbU\x00\x03\x04\x00\x07\x11'" +2026-03-20 14:34:01.949800,SENT,"b'\xbbU\x00\x03 \x00#I'" +2026-03-20 14:34:01.981096,SENT,"b'\xbbU\x00\x03!\x00$K'" +2026-03-20 14:34:02.012666,SENT,"b'\xbbU\x00\x03e\x00h\xd3'" +2026-03-20 14:34:02.043854,SENT,"b'\xbbU\x00\xa2h\x04\x00\x00\x00\x00\x0e\xf2'" +2026-03-20 14:34:02.064989,SENT,"b'\xbbU\x00\x9ah\x04\xff\xff\xff\xff\x02\xb0'" +2026-03-20 14:34:02.086130,SENT,"b'\xbbU\x00\xaah\x01\xff\x12\xe1'" diff --git a/driver/test/log/2026-03-20_14-34-01_log_auv.log b/driver/test/log/2026-03-20_14-34-01_log_auv.log new file mode 100644 index 0000000..326476d --- /dev/null +++ b/driver/test/log/2026-03-20_14-34-01_log_auv.log @@ -0,0 +1,23 @@ +2026-03-20 14:34:01.914 | INFO | kogger_protocol_driver:setup_logging:81 - Kogger Protocol Driver: Loguru logging configured to level INFO and file log/2026-03-20_14-34-01_log_auv.log. +2026-03-20 14:34:01.915 | INFO | kogger_protocol_driver:__init__:224 - KoggerSBPDevice configured for port /dev/ttyAMA4, baudrate 921600, address 0 +2026-03-20 14:34:01.915 | INFO | kogger_protocol_driver:_open_file:43 - CSV logging enabled to log/2026-03-20_14-34-01_AUV_usbl.csv +2026-03-20 14:34:01.917 | INFO | kogger_protocol_driver:_reader_thread_loop:491 - Reader thread started. +2026-03-20 14:34:01.917 | SUCCESS | kogger_protocol_driver:connect:314 - Successfully connected to /dev/ttyAMA4 at 921600 and started reader thread. +2026-03-20 14:34:01.938 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x4 +2026-03-20 14:34:01.970 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x20 +2026-03-20 14:34:02.001 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x21 +2026-03-20 14:34:02.032 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x65 +2026-03-20 14:34:02.064 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:34:02.064 | INFO | __main__:main:136 - set_auto_response_filter(0)=None +2026-03-20 14:34:02.085 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:34:02.085 | INFO | __main__:main:138 - set_auto_response_timeout(0xffffffff)=None +2026-03-20 14:34:02.106 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:34:02.106 | INFO | __main__:main:140 - set_auto_response_payload(0xff)=None +2026-03-20 14:34:02.107 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:34:03.107 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:34:04.108 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:34:05.108 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:34:06.109 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:34:07.052 | INFO | kogger_protocol_driver:_reader_thread_loop:568 - Reader thread finished. +2026-03-20 14:34:07.053 | INFO | kogger_protocol_driver:disconnect:352 - Serial port /dev/ttyAMA4 closed. +2026-03-20 14:34:07.053 | INFO | kogger_protocol_driver:disconnect:364 - Disconnected and cleaned up. diff --git a/driver/test/log/2026-03-20_14-35-07_AUV_usbl.csv b/driver/test/log/2026-03-20_14-35-07_AUV_usbl.csv new file mode 100644 index 0000000..6577ac8 --- /dev/null +++ b/driver/test/log/2026-03-20_14-35-07_AUV_usbl.csv @@ -0,0 +1,7 @@ +2026-03-20 14:35:07.395381,SENT,"b'\xbbU\x00\x03\x04\x00\x07\x11'" +2026-03-20 14:35:07.426623,SENT,"b'\xbbU\x00\x03 \x00#I'" +2026-03-20 14:35:07.457854,SENT,"b'\xbbU\x00\x03!\x00$K'" +2026-03-20 14:35:07.489055,SENT,"b'\xbbU\x00\x03e\x00h\xd3'" +2026-03-20 14:35:07.520263,SENT,"b'\xbbU\x00\xa2h\x04\x00\x00\x00\x00\x0e\xf2'" +2026-03-20 14:35:07.541942,SENT,"b'\xbbU\x00\x9ah\x04\xff\xff\xff\xff\x02\xb0'" +2026-03-20 14:35:07.563092,SENT,"b'\xbbU\x00\xaah\x01\xff\x12\xe1'" diff --git a/driver/test/log/2026-03-20_14-35-07_log_auv.log b/driver/test/log/2026-03-20_14-35-07_log_auv.log new file mode 100644 index 0000000..fb65fb4 --- /dev/null +++ b/driver/test/log/2026-03-20_14-35-07_log_auv.log @@ -0,0 +1,27 @@ +2026-03-20 14:35:07.391 | INFO | kogger_protocol_driver:setup_logging:81 - Kogger Protocol Driver: Loguru logging configured to level INFO and file log/2026-03-20_14-35-07_log_auv.log. +2026-03-20 14:35:07.392 | INFO | kogger_protocol_driver:__init__:224 - KoggerSBPDevice configured for port /dev/ttyAMA4, baudrate 921600, address 0 +2026-03-20 14:35:07.392 | INFO | kogger_protocol_driver:_open_file:43 - CSV logging enabled to log/2026-03-20_14-35-07_AUV_usbl.csv +2026-03-20 14:35:07.394 | INFO | kogger_protocol_driver:_reader_thread_loop:491 - Reader thread started. +2026-03-20 14:35:07.394 | SUCCESS | kogger_protocol_driver:connect:314 - Successfully connected to /dev/ttyAMA4 at 921600 and started reader thread. +2026-03-20 14:35:07.415 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x4 +2026-03-20 14:35:07.446 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x20 +2026-03-20 14:35:07.478 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x21 +2026-03-20 14:35:07.509 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x65 +2026-03-20 14:35:07.540 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:35:07.541 | INFO | __main__:main:136 - set_auto_response_filter(0)=None +2026-03-20 14:35:07.562 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:35:07.562 | INFO | __main__:main:138 - set_auto_response_timeout(0xffffffff)=None +2026-03-20 14:35:07.583 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:35:07.583 | INFO | __main__:main:140 - set_auto_response_payload(0xff)=None +2026-03-20 14:35:07.584 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:35:08.584 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:35:09.584 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:35:10.585 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:35:11.585 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:35:12.586 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:35:13.587 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:35:14.587 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:35:15.588 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:35:15.953 | INFO | kogger_protocol_driver:_reader_thread_loop:568 - Reader thread finished. +2026-03-20 14:35:15.954 | INFO | kogger_protocol_driver:disconnect:352 - Serial port /dev/ttyAMA4 closed. +2026-03-20 14:35:15.955 | INFO | kogger_protocol_driver:disconnect:364 - Disconnected and cleaned up. diff --git a/driver/test/log/2026-03-20_14-36-35_AUV_usbl.csv b/driver/test/log/2026-03-20_14-36-35_AUV_usbl.csv new file mode 100644 index 0000000..d34080c --- /dev/null +++ b/driver/test/log/2026-03-20_14-36-35_AUV_usbl.csv @@ -0,0 +1,7 @@ +2026-03-20 14:36:35.270157,SENT,"b'\xbbU\x00\x03\x04\x00\x07\x11'" +2026-03-20 14:36:35.301556,SENT,"b'\xbbU\x00\x03 \x00#I'" +2026-03-20 14:36:35.332872,SENT,"b'\xbbU\x00\x03!\x00$K'" +2026-03-20 14:36:35.363991,SENT,"b'\xbbU\x00\x03e\x00h\xd3'" +2026-03-20 14:36:35.395159,SENT,"b'\xbbU\x00\xa2h\x04\x00\x00\x00\x00\x0e\xf2'" +2026-03-20 14:36:35.416269,SENT,"b'\xbbU\x00\x9ah\x04\xff\xff\xff\xff\x02\xb0'" +2026-03-20 14:36:35.437376,SENT,"b'\xbbU\x00\xaah\x01\xff\x12\xe1'" diff --git a/driver/test/log/2026-03-20_14-36-35_log_auv.log b/driver/test/log/2026-03-20_14-36-35_log_auv.log new file mode 100644 index 0000000..ab80cf8 --- /dev/null +++ b/driver/test/log/2026-03-20_14-36-35_log_auv.log @@ -0,0 +1,26 @@ +2026-03-20 14:36:35.266 | INFO | kogger_protocol_driver:setup_logging:81 - Kogger Protocol Driver: Loguru logging configured to level INFO and file log/2026-03-20_14-36-35_log_auv.log. +2026-03-20 14:36:35.267 | INFO | kogger_protocol_driver:__init__:224 - KoggerSBPDevice configured for port /dev/ttyAMA4, baudrate 921600, address 0 +2026-03-20 14:36:35.267 | INFO | kogger_protocol_driver:_open_file:43 - CSV logging enabled to log/2026-03-20_14-36-35_AUV_usbl.csv +2026-03-20 14:36:35.269 | INFO | kogger_protocol_driver:_reader_thread_loop:491 - Reader thread started. +2026-03-20 14:36:35.269 | SUCCESS | kogger_protocol_driver:connect:314 - Successfully connected to /dev/ttyAMA4 at 921600 and started reader thread. +2026-03-20 14:36:35.290 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x4 +2026-03-20 14:36:35.321 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x20 +2026-03-20 14:36:35.353 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x21 +2026-03-20 14:36:35.384 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x65 +2026-03-20 14:36:35.415 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:36:35.415 | INFO | __main__:main:136 - set_auto_response_filter(0)=None +2026-03-20 14:36:35.436 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:36:35.436 | INFO | __main__:main:138 - set_auto_response_timeout(0xffffffff)=None +2026-03-20 14:36:35.458 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:36:35.458 | INFO | __main__:main:140 - set_auto_response_payload(0xff)=None +2026-03-20 14:36:35.458 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:36:36.459 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:36:37.460 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:36:38.460 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:36:39.461 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:36:40.463 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:36:41.466 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:36:42.467 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:36:42.632 | INFO | kogger_protocol_driver:_reader_thread_loop:568 - Reader thread finished. +2026-03-20 14:36:42.633 | INFO | kogger_protocol_driver:disconnect:352 - Serial port /dev/ttyAMA4 closed. +2026-03-20 14:36:42.634 | INFO | kogger_protocol_driver:disconnect:364 - Disconnected and cleaned up. diff --git a/driver/test/log/2026-03-20_14-37-46_AUV_usbl.csv b/driver/test/log/2026-03-20_14-37-46_AUV_usbl.csv new file mode 100644 index 0000000..e2ebe4b --- /dev/null +++ b/driver/test/log/2026-03-20_14-37-46_AUV_usbl.csv @@ -0,0 +1,7 @@ +2026-03-20 14:37:46.615358,SENT,"b'\xbbU\x00\x03\x04\x00\x07\x11'" +2026-03-20 14:37:46.646959,SENT,"b'\xbbU\x00\x03 \x00#I'" +2026-03-20 14:37:46.678407,SENT,"b'\xbbU\x00\x03!\x00$K'" +2026-03-20 14:37:46.710030,SENT,"b'\xbbU\x00\x03e\x00h\xd3'" +2026-03-20 14:37:46.741664,SENT,"b'\xbbU\x00\xa2h\x04\x00\x00\x00\x00\x0e\xf2'" +2026-03-20 14:37:46.763085,SENT,"b'\xbbU\x00\x9ah\x04\xff\xff\xff\xff\x02\xb0'" +2026-03-20 14:37:46.784460,SENT,"b'\xbbU\x00\xaah\x01\xff\x12\xe1'" diff --git a/driver/test/log/2026-03-20_14-37-46_log_auv.log b/driver/test/log/2026-03-20_14-37-46_log_auv.log new file mode 100644 index 0000000..4a32f42 --- /dev/null +++ b/driver/test/log/2026-03-20_14-37-46_log_auv.log @@ -0,0 +1,20 @@ +2026-03-20 14:37:46.610 | INFO | kogger_protocol_driver:setup_logging:81 - Kogger Protocol Driver: Loguru logging configured to level INFO and file log/2026-03-20_14-37-46_log_auv.log. +2026-03-20 14:37:46.611 | INFO | kogger_protocol_driver:__init__:224 - KoggerSBPDevice configured for port /dev/ttyAMA4, baudrate 921600, address 0 +2026-03-20 14:37:46.611 | INFO | kogger_protocol_driver:_open_file:43 - CSV logging enabled to log/2026-03-20_14-37-46_AUV_usbl.csv +2026-03-20 14:37:46.612 | INFO | kogger_protocol_driver:_reader_thread_loop:491 - Reader thread started. +2026-03-20 14:37:46.613 | SUCCESS | kogger_protocol_driver:connect:314 - Successfully connected to /dev/ttyAMA4 at 921600 and started reader thread. +2026-03-20 14:37:46.635 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x4 +2026-03-20 14:37:46.667 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x20 +2026-03-20 14:37:46.698 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x21 +2026-03-20 14:37:46.730 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x65 +2026-03-20 14:37:46.761 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:37:46.762 | INFO | __main__:main:136 - set_auto_response_filter(0)=None +2026-03-20 14:37:46.783 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:37:46.783 | INFO | __main__:main:138 - set_auto_response_timeout(0xffffffff)=None +2026-03-20 14:37:46.804 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:37:46.805 | INFO | __main__:main:140 - set_auto_response_payload(0xff)=None +2026-03-20 14:37:46.805 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:37:47.806 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:37:48.325 | INFO | kogger_protocol_driver:_reader_thread_loop:568 - Reader thread finished. +2026-03-20 14:37:48.326 | INFO | kogger_protocol_driver:disconnect:352 - Serial port /dev/ttyAMA4 closed. +2026-03-20 14:37:48.326 | INFO | kogger_protocol_driver:disconnect:364 - Disconnected and cleaned up. diff --git a/driver/test/log/2026-03-20_14-37-51_AUV_usbl.csv b/driver/test/log/2026-03-20_14-37-51_AUV_usbl.csv new file mode 100644 index 0000000..d3215f4 --- /dev/null +++ b/driver/test/log/2026-03-20_14-37-51_AUV_usbl.csv @@ -0,0 +1,5 @@ +2026-03-20 14:37:51.494968,SENT,"b'\xbbU\x00\x03 \x00#I'" +2026-03-20 14:37:51.526384,SENT,"b'\xbbU\x00\xa2h\x04\x00\x00\x00\x00\x0e\xf2'" +2026-03-20 14:37:51.547620,SENT,"b'\xbbU\x00\x9ah\x04\xff\xff\xff\xff\x02\xb0'" +2026-03-20 14:37:51.568929,SENT,"b'\xbbU\x00\xaah\x01\xff\x12\xe1'" +2026-03-20 14:37:51.590691,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" diff --git a/driver/test/log/2026-03-20_14-37-51_log_usv.log b/driver/test/log/2026-03-20_14-37-51_log_usv.log new file mode 100644 index 0000000..52f70a5 --- /dev/null +++ b/driver/test/log/2026-03-20_14-37-51_log_usv.log @@ -0,0 +1,16 @@ +2026-03-20 14:37:51.490 | INFO | kogger_protocol_driver:setup_logging:81 - Kogger Protocol Driver: Loguru logging configured to level INFO and file log/2026-03-20_14-37-51_log_usv.log. +2026-03-20 14:37:51.491 | INFO | kogger_protocol_driver:__init__:224 - KoggerSBPDevice configured for port /dev/ttyAMA4, baudrate 921600, address 0 +2026-03-20 14:37:51.491 | INFO | kogger_protocol_driver:_open_file:43 - CSV logging enabled to log/2026-03-20_14-37-51_AUV_usbl.csv +2026-03-20 14:37:51.493 | INFO | kogger_protocol_driver:_reader_thread_loop:491 - Reader thread started. +2026-03-20 14:37:51.493 | SUCCESS | kogger_protocol_driver:connect:314 - Successfully connected to /dev/ttyAMA4 at 921600 and started reader thread. +2026-03-20 14:37:51.515 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x20 +2026-03-20 14:37:51.546 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:37:51.547 | INFO | __main__:main:127 - set_auto_response_filter(0)=None +2026-03-20 14:37:51.567 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:37:51.568 | INFO | __main__:main:129 - set_auto_response_timeout(0xffffffff)=None +2026-03-20 14:37:51.589 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:37:51.589 | INFO | __main__:main:131 - set_auto_response_payload(0xff)=None +2026-03-20 14:37:51.610 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:37:52.501 | INFO | kogger_protocol_driver:_reader_thread_loop:568 - Reader thread finished. +2026-03-20 14:37:52.502 | INFO | kogger_protocol_driver:disconnect:352 - Serial port /dev/ttyAMA4 closed. +2026-03-20 14:37:52.502 | INFO | kogger_protocol_driver:disconnect:364 - Disconnected and cleaned up. diff --git a/driver/test/log/2026-03-20_14-44-29_AUV_usbl.csv b/driver/test/log/2026-03-20_14-44-29_AUV_usbl.csv new file mode 100644 index 0000000..ca704b4 --- /dev/null +++ b/driver/test/log/2026-03-20_14-44-29_AUV_usbl.csv @@ -0,0 +1,7 @@ +2026-03-20 14:44:29.693853,SENT,"b'\xbbU\x00\x03\x04\x00\x07\x11'" +2026-03-20 14:44:29.725054,SENT,"b'\xbbU\x00\x03 \x00#I'" +2026-03-20 14:44:29.757207,SENT,"b'\xbbU\x00\x03!\x00$K'" +2026-03-20 14:44:29.790020,SENT,"b'\xbbU\x00\x03e\x00h\xd3'" +2026-03-20 14:44:29.821891,SENT,"b'\xbbU\x00\xa2h\x04\x00\x00\x00\x00\x0e\xf2'" +2026-03-20 14:44:29.843202,SENT,"b'\xbbU\x00\x9ah\x04\xff\xff\xff\xff\x02\xb0'" +2026-03-20 14:44:29.864806,SENT,"b'\xbbU\x00\xaah\x01\xff\x12\xe1'" diff --git a/driver/test/log/2026-03-20_14-44-29_log_auv.log b/driver/test/log/2026-03-20_14-44-29_log_auv.log new file mode 100644 index 0000000..8d40360 --- /dev/null +++ b/driver/test/log/2026-03-20_14-44-29_log_auv.log @@ -0,0 +1,21 @@ +2026-03-20 14:44:29.689 | INFO | kogger_protocol_driver:setup_logging:81 - Kogger Protocol Driver: Loguru logging configured to level INFO and file log/2026-03-20_14-44-29_log_auv.log. +2026-03-20 14:44:29.689 | INFO | kogger_protocol_driver:__init__:224 - KoggerSBPDevice configured for port /dev/ttyAMA4, baudrate 921600, address 0 +2026-03-20 14:44:29.690 | INFO | kogger_protocol_driver:_open_file:43 - CSV logging enabled to log/2026-03-20_14-44-29_AUV_usbl.csv +2026-03-20 14:44:29.692 | INFO | kogger_protocol_driver:_reader_thread_loop:491 - Reader thread started. +2026-03-20 14:44:29.692 | SUCCESS | kogger_protocol_driver:connect:314 - Successfully connected to /dev/ttyAMA4 at 921600 and started reader thread. +2026-03-20 14:44:29.714 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x4 +2026-03-20 14:44:29.745 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x20 +2026-03-20 14:44:29.777 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x21 +2026-03-20 14:44:29.810 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x65 +2026-03-20 14:44:29.842 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:44:29.842 | INFO | __main__:main:136 - set_auto_response_filter(0)=None +2026-03-20 14:44:29.863 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:44:29.864 | INFO | __main__:main:138 - set_auto_response_timeout(0xffffffff)=None +2026-03-20 14:44:29.885 | WARNING | kogger_protocol_driver:_execute_command:643 - Timeout waiting for response for command ID 0x68 +2026-03-20 14:44:29.886 | INFO | __main__:main:140 - set_auto_response_payload(0xff)=None +2026-03-20 14:44:29.886 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:44:30.886 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:44:31.887 | INFO | __main__:main:147 - response_received=-1 +2026-03-20 14:44:32.819 | INFO | kogger_protocol_driver:_reader_thread_loop:568 - Reader thread finished. +2026-03-20 14:44:32.821 | INFO | kogger_protocol_driver:disconnect:352 - Serial port /dev/ttyAMA4 closed. +2026-03-20 14:44:32.822 | INFO | kogger_protocol_driver:disconnect:364 - Disconnected and cleaned up. diff --git a/driver/test/log/2026-03-20_14-56-47_AUV_usbl.csv b/driver/test/log/2026-03-20_14-56-47_AUV_usbl.csv new file mode 100644 index 0000000..2f43a4b --- /dev/null +++ b/driver/test/log/2026-03-20_14-56-47_AUV_usbl.csv @@ -0,0 +1,57 @@ +2026-03-20 14:56:47.958726,SENT,"b'\xbbU\x00\x03 \x00#I'" +2026-03-20 14:56:47.959041,RECEIVED,"b'\xbb'" +2026-03-20 14:56:47.959643,RECEIVED,"b'U\x00A ""\x00\x0f\x00\x00\x00\x00\x00\x00B\x04\x81(\x00\x02\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x93\xac'" +2026-03-20 14:56:47.970747,SENT,"b'\xbbU\x00\xa2h\x04\x00\x00\x00\x00\x0e\xf2'" +2026-03-20 14:56:47.971035,RECEIVED,"b'\xbb'" +2026-03-20 14:56:47.971533,RECEIVED,"b'U\x00\xe1h\x03\x01\x0e\xf2Mk'" +2026-03-20 14:56:47.972554,SENT,"b'\xbbU\x00\x9ah\x04\xff\xff\xff\xff\x02\xb0'" +2026-03-20 14:56:47.972819,RECEIVED,"b'\xbb'" +2026-03-20 14:56:47.973294,RECEIVED,"b'U\x00\xd9h\x03\x01\x02\xb0\xf7\xe1'" +2026-03-20 14:56:47.974430,SENT,"b'\xbbU\x00\xaah\x01\xff\x12\xe1'" +2026-03-20 14:56:47.974962,RECEIVED,"b'\xbb'" +2026-03-20 14:56:47.975475,RECEIVED,"b'U\x00\xe9h\x03\x01\x12\xe1H\x92'" +2026-03-20 14:56:47.977297,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-20 14:56:47.977574,RECEIVED,"b'\xbb'" +2026-03-20 14:56:47.978049,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-20 14:56:48.980709,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-20 14:56:48.981293,RECEIVED,"b'\xbb'" +2026-03-20 14:56:48.981809,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-20 14:56:49.983206,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-20 14:56:49.983633,RECEIVED,"b'\xbb'" +2026-03-20 14:56:49.984310,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-20 14:56:50.985612,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 14:56:50.986198,RECEIVED,"b'\xbb'" +2026-03-20 14:56:50.986747,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 14:56:51.988174,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 14:56:51.988630,RECEIVED,"b'\xbb'" +2026-03-20 14:56:51.989155,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 14:56:52.991411,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 14:56:52.991927,RECEIVED,"b'\xbb'" +2026-03-20 14:56:52.992391,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 14:56:53.993851,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 14:56:53.994366,RECEIVED,"b'\xbb'" +2026-03-20 14:56:53.994896,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 14:56:54.996209,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 14:56:54.996536,RECEIVED,"b'\xbb'" +2026-03-20 14:56:54.997075,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 14:56:55.998790,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 14:56:55.999053,RECEIVED,"b'\xbb'" +2026-03-20 14:56:55.999548,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 14:56:57.001186,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 14:56:57.001702,RECEIVED,"b'\xbb'" +2026-03-20 14:56:57.002202,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 14:56:58.003753,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 14:56:58.004379,RECEIVED,"b'\xbb'" +2026-03-20 14:56:58.004967,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 14:56:59.006241,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 14:56:59.006573,RECEIVED,"b'\xbb'" +2026-03-20 14:56:59.007106,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 14:57:00.008571,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 14:57:00.009919,RECEIVED,"b'\xbb'" +2026-03-20 14:57:00.011049,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 14:57:01.011479,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 14:57:01.012009,RECEIVED,"b'\xbb'" +2026-03-20 14:57:01.012508,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-20 14:57:02.013783,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-20 14:57:02.014309,RECEIVED,"b'\xbb'" +2026-03-20 14:57:02.014832,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" diff --git a/driver/test/log/2026-03-20_14-56-47_log_usv.log b/driver/test/log/2026-03-20_14-56-47_log_usv.log new file mode 100644 index 0000000..246f259 --- /dev/null +++ b/driver/test/log/2026-03-20_14-56-47_log_usv.log @@ -0,0 +1,11 @@ +2026-03-20 14:56:47.954 | INFO | kogger_protocol_driver:setup_logging:81 - Kogger Protocol Driver: Loguru logging configured to level INFO and file log/2026-03-20_14-56-47_log_usv.log. +2026-03-20 14:56:47.955 | INFO | kogger_protocol_driver:__init__:224 - KoggerSBPDevice configured for port /dev/ttyAMA4, baudrate 921600, address 0 +2026-03-20 14:56:47.956 | INFO | kogger_protocol_driver:_open_file:43 - CSV logging enabled to log/2026-03-20_14-56-47_AUV_usbl.csv +2026-03-20 14:56:47.957 | INFO | kogger_protocol_driver:_reader_thread_loop:491 - Reader thread started. +2026-03-20 14:56:47.957 | SUCCESS | kogger_protocol_driver:connect:314 - Successfully connected to /dev/ttyAMA4 at 921600 and started reader thread. +2026-03-20 14:56:47.972 | INFO | __main__:main:127 - set_auto_response_filter(0)=True +2026-03-20 14:56:47.973 | INFO | __main__:main:129 - set_auto_response_timeout(0xffffffff)=True +2026-03-20 14:56:47.976 | INFO | __main__:main:131 - set_auto_response_payload(0xff)=True +2026-03-20 14:57:02.116 | INFO | kogger_protocol_driver:_reader_thread_loop:568 - Reader thread finished. +2026-03-20 14:57:02.117 | INFO | kogger_protocol_driver:disconnect:352 - Serial port /dev/ttyAMA4 closed. +2026-03-20 14:57:02.117 | INFO | kogger_protocol_driver:disconnect:364 - Disconnected and cleaned up. diff --git a/driver/test/log/2026-03-22_08-42-12_AUV_usbl.csv b/driver/test/log/2026-03-22_08-42-12_AUV_usbl.csv new file mode 100644 index 0000000..cdd0c1f --- /dev/null +++ b/driver/test/log/2026-03-22_08-42-12_AUV_usbl.csv @@ -0,0 +1,210 @@ +2026-03-22 08:42:12.676430,SENT,"b'\xbbU\x00\x03 \x00#I'" +2026-03-22 08:42:12.676791,RECEIVED,"b'\xbb'" +2026-03-22 08:42:12.677342,RECEIVED,"b'U\x00A ""\x00\x0f\x00\x00\x00\x00\x00\x00\x02\x04\x81\t\x00\x02\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004c'" +2026-03-22 08:42:12.688465,SENT,"b'\xbbU\x00\xa2h\x04\x00\x00\x00\x00\x0e\xf2'" +2026-03-22 08:42:12.688855,RECEIVED,"b'\xbb'" +2026-03-22 08:42:12.689360,RECEIVED,"b'U\x00\xe1h\x03\x01\x0e\xf2Mk'" +2026-03-22 08:42:12.690520,SENT,"b'\xbbU\x00\x9ah\x04\xff\xff\xff\xff\x02\xb0'" +2026-03-22 08:42:12.690895,RECEIVED,"b'\xbb'" +2026-03-22 08:42:12.691435,RECEIVED,"b'U\x00\xd9h\x03\x01\x02\xb0\xf7\xe1'" +2026-03-22 08:42:12.693535,SENT,"b'\xbbU\x00\xaah\x01\xff\x12\xe1'" +2026-03-22 08:42:12.693870,RECEIVED,"b'\xbb'" +2026-03-22 08:42:12.694402,RECEIVED,"b'U\x00\xe9h\x03\x01\x12\xe1H\x92'" +2026-03-22 08:42:12.696190,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:42:12.696502,RECEIVED,"b'\xbb'" +2026-03-22 08:42:12.696979,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:42:13.019849,RECEIVED,"b'\xbb'" +2026-03-22 08:42:13.020424,RECEIVED,"b'U\x00Ae\x98\xff\x02\x00\x00,]a%\x00\x00\x00\x00\x00\x00\x00\x00:\x0c\x1f\x01\x00\x00\x00\x00\xfa\xc5\xae@\x00\x00\x00\x00\x01\x00\xf9B\x00\x00\x00\x00\xc8\xcc\x8c@\x00\x00\x00\x008C\x95AG\xfcE\xc0\x08\t\x90\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:42:13.020965,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00%g\xe0AQn:\xc1\xf1*/C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\xc3\x18\x9b\xc0O!!@\x00\x00'" +2026-03-22 08:42:13.021443,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\xc6O'" +2026-03-22 08:42:13.700061,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:42:13.701084,RECEIVED,"b'\xbb'" +2026-03-22 08:42:13.701628,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:42:14.045905,RECEIVED,"b'\xbb'" +2026-03-22 08:42:14.046476,RECEIVED,"b'U\x00Ae\x98\xff\x02\x00\x00\xbf\x04q%\x00\x00\x00\x00\x00\x00\x00\x00\xe3\x81\x1f\x01\x00\x00\x00\x00#""\xaa@\x00\x00\x00\x00\xcd\xcc\xfc\xc2\x00\x00\x00\x00\x00\x00L\xc2\x00\x00\x00\x00y\xf4\x98A\xb2\xebI\xc0t\xf0\x88@\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:42:14.046954,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00k\xa7\xedA\xfb\xd0.\xc1@\xc3.C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:42:14.047444,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x002\xa3\x1e\xbfJ\xf9\xa8\xc0\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7fg\xfe'" +2026-03-22 08:42:14.702475,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:42:14.703038,RECEIVED,"b'\xbb'" +2026-03-22 08:42:14.703541,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:42:15.035878,RECEIVED,"b'\xbb'" +2026-03-22 08:42:15.036461,RECEIVED,"b'U\x00Ae\x98\x01\x02\x00\x00\xe6\x1f\x80%\x00\x00\x00\x00\x00\x00\x00\x00S\xf7\x1f\x01\x00\x00\x00\x00*\\\xa9@\x00\x00\x00\x0053\x1e\xc3\x00\x00\x00\x0053u\xc2\x00\x00\x00\x00:\x88\x99A\x97?\x9d\xc0G\x94'" +2026-03-22 08:42:15.036970,RECEIVED,"b'\xfb?\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x15\xdc\xf2AN=9\xc13\xc1\x950/C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:42:19.031409,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00P\xca\xc4\xc0\xe1\xe2,?\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x81\x11'" +2026-03-22 08:42:19.714877,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:42:19.715393,RECEIVED,"b'\xbb'" +2026-03-22 08:42:19.715917,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:42:20.052359,RECEIVED,"b'\xbb'" +2026-03-22 08:42:20.053004,RECEIVED,"b'U\x00Ae\x98\x01\x02\x00\x00\xea\xa9\xcc%\x00\x00\x00\x00\x00\x00\x00\x00\xb6B""\x01\x00\x00\x00\x00\xf3\x8b\xaf@\x00\x00\x00\x00\x9e\x99\xb0\xc2\x00\x00\x00\x00ef>B\x00\x00\x00\x00\x151\x97Aw\xa5&>,x\xaf@\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:42:20.056098,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00c\x84\x02B\x8eT7\xc1#\x08/C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x8f\xffE@\xe2\xf7\x90\xc0\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f$-'" +2026-03-22 08:42:20.717426,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-22 08:42:20.717781,RECEIVED,"b'\xbb'" +2026-03-22 08:42:20.718320,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-22 08:42:21.042938,RECEIVED,"b'\xbb'" +2026-03-22 08:42:21.043644,RECEIVED,"b'U\x00Ae\x98\xff\x02\x00\x00\x86\xc4\xdb%\x00\x00\x00\x00\x00\x00\x00\x00\x1e\xb8""\x01\x00\x00\x00\x00ED\xa6@\x00\x00\x00\x00\x9a\x99\xfdA\x00\x00\x00\x0043?\xc2\x00\x00\x00\x00\xcbq\x8bA\'v\x8d@\xa3\xbc.\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x06\xd7\x03B!n<\xc1\x83\xd3.C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x19R\x0e@\xe6D\x96@\x00\x00\xc0'" +2026-03-22 08:42:21.044334,RECEIVED,"b'\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x8b.'" +2026-03-22 08:42:21.719886,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-22 08:42:21.720271,RECEIVED,"b'\xbb'" +2026-03-22 08:42:21.720847,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-22 08:42:22.721970,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x02\xf9H'" +2026-03-22 08:42:22.722483,RECEIVED,"b'\xbb'" +2026-03-22 08:42:22.722977,RECEIVED,"b'U\x00\xc9h\x03\x01\xf9Hv\x07'" +2026-03-22 08:42:23.724779,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x03\xfaI'" +2026-03-22 08:42:23.725368,RECEIVED,"b'\xbb'" +2026-03-22 08:42:23.725991,RECEIVED,"b'U\x00\xc9h\x03\x01\xfaIx\n'" +2026-03-22 08:42:24.046169,RECEIVED,"b'\xbb'" +2026-03-22 08:42:24.046902,RECEIVED,"b'U\x00Ae\x98\xff\x02\x00\x00V\x97\t&\x00\x00\x00\x00\x00\x00\x00\x00\xae\x18$\x01\x00\x00\x00\x00r=\xbe@\x00\x00\x00\x00hf\xa6A\x00\x00\x00\x00\x9a\x99\x89\xc1\x00\x00\x00\x00\xa0\x7f\xa6A\\\xd7\xb1@s\x1c'" +2026-03-22 08:42:24.047381,RECEIVED,"b'\x07\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\xb4\xe0\x04BF\x9d=\xc1lE/C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00>\x89_@\x02\xf2\x99@\x00\x00'" +2026-03-22 08:42:24.047874,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x8a\x8d'" +2026-03-22 08:42:24.727711,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x03\xfaI'" +2026-03-22 08:42:24.728225,RECEIVED,"b'\xbb'" +2026-03-22 08:42:24.728937,RECEIVED,"b'U\x00\xc9h\x03\x01\xfaIx\n'" +2026-03-22 08:42:25.072187,RECEIVED,"b'\xbb'" +2026-03-22 08:42:25.072760,RECEIVED,"b'U\x00Ae\x98\xff\x02\x00\x00s?\x19&\x00\x00\x00\x00\x00\x00\x00\x00Q\x8e$\x01\x00\x00\x00\x00\xac\xaa\xd4@\x00\x00\x00\x00\xcd\xcc\x19C\x00\x00\x00\x0023\x87A\x00\x00\x00\x00L\xc5\xa5A%\xd1\xbe\xc0\x84\xc9'" +2026-03-22 08:42:25.073230,RECEIVED,"b';\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00dQ\x06B\x1c\xe4D\xc1\xf9i/C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:42:25.073670,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00\xbc\xe7\xd2\xc06\x85Z\xbf\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f&\xed'" +2026-03-22 08:42:25.730192,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x03\xfaI'" +2026-03-22 08:42:25.730520,RECEIVED,"b'\xbb'" +2026-03-22 08:42:25.731013,RECEIVED,"b'U\x00\xc9h\x03\x01\xfaIx\n'" +2026-03-22 08:42:26.062249,RECEIVED,"b'\xbb'" +2026-03-22 08:42:26.062824,RECEIVED,"b'U\x00Ae\x98\x01\x02\x00\x00\xa3Z(&\x00\x00\x00\x00\x00\x00\x00\x00\x8e\x03%\x01\x00\x00\x00\x00#""\xaa@\x00\x00\x00\x0033\xcdB\x00\x00\x00\x00\x04\x00\xf0\xc0\x00\x00\x00\x00d\xba\x8bA*t\x94\xbf6\t'" +2026-03-22 08:42:26.063301,RECEIVED,"b'\xa6\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x98\x9f\x07B\xa8\xa3=\xc1i\x8b/C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:42:26.063764,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00<\xd8v\xc0\xec2j@\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f0\xae'" +2026-03-22 08:42:26.733277,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x03\xfaI'" +2026-03-22 08:42:26.733832,RECEIVED,"b'\xbb'" +2026-03-22 08:42:26.734331,RECEIVED,"b'U\x00\xc9h\x03\x01\xfaIx\n'" +2026-03-22 08:42:27.736630,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x03\xfaI'" +2026-03-22 08:42:27.737202,RECEIVED,"b'\xbb'" +2026-03-22 08:42:27.737839,RECEIVED,"b'U\x00\xc9h\x03\x01\xfaIx\n'" +2026-03-22 08:42:28.738809,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x03\xfaI'" +2026-03-22 08:42:28.739148,RECEIVED,"b'\xbb'" +2026-03-22 08:42:28.739718,RECEIVED,"b'U\x00\xc9h\x03\x01\xfaIx\n'" +2026-03-22 08:42:29.741047,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x03\xfaI'" +2026-03-22 08:42:29.741606,RECEIVED,"b'\xbb'" +2026-03-22 08:42:29.742409,RECEIVED,"b'U\x00\xc9h\x03\x01\xfaIx\n'" +2026-03-22 08:42:30.056370,RECEIVED,"b'\xbb'" +2026-03-22 08:42:30.057027,RECEIVED,"b'U\x00Ae\x98\x04\x02\x00\x00]He&\x00\x00\x00\x00\x00\x00\x00\x00\xb1\xd9&\x01\x00\x00\x00\x00d\xc9\xbf@\x00\x00\x00\x003\xb3\x02C\x00\x00\x00\x0013\x13A\x00\x00\x00\x00\xbd\xa0\x9aA\xa9 z\xc0kf\x91\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:42:30.057526,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00\x82\x8a\x08B\xb5\xef?\xc12j/C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\xb4\x1b\xb9\xc0\x91\xae\xc8?\x00\x00'" +2026-03-22 08:42:30.057982,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\xfb\xd0'" +2026-03-22 08:42:30.744330,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x03\xfaI'" +2026-03-22 08:42:30.744652,RECEIVED,"b'\xbb'" +2026-03-22 08:42:30.745147,RECEIVED,"b'U\x00\xc9h\x03\x01\xfaIx\n'" +2026-03-22 08:42:31.746296,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x03\xfaI'" +2026-03-22 08:42:31.746822,RECEIVED,"b'\xbb'" +2026-03-22 08:42:31.747323,RECEIVED,"b'U\x00\xc9h\x03\x01\xfaIx\n'" +2026-03-22 08:42:32.749381,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x03\xfaI'" +2026-03-22 08:42:32.749901,RECEIVED,"b'\xbb'" +2026-03-22 08:42:32.750437,RECEIVED,"b'U\x00\xc9h\x03\x01\xfaIx\n'" +2026-03-22 08:42:33.752323,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x03\xfaI'" +2026-03-22 08:42:33.753289,RECEIVED,"b'\xbb'" +2026-03-22 08:42:33.754503,RECEIVED,"b'U\x00\xc9h\x03\x01\xfaIx\n'" +2026-03-22 08:42:34.754901,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x03\xfaI'" +2026-03-22 08:42:34.755713,RECEIVED,"b'\xbb'" +2026-03-22 08:42:34.761780,RECEIVED,"b'U\x00\xc9h\x03\x01\xfaIx\n'" +2026-03-22 08:42:35.072468,RECEIVED,"b'\xbb'" +2026-03-22 08:42:35.073038,RECEIVED,"b'U\x00Ae\x98\x03\x02\x00\x00\x1c\xd2\xb1&\x00\x00\x00\x00\x00\x00\x00\x00>%)\x01\x00\x00\x00\x00\x86\xeb\xbb@\x00\x00\x00\x00\xcdL\x1c\xc3\x00\x00\x00\x00\xca\xcc\xf4A\x00\x00\x00\x00#C\x90AB\x12\xac\xc0l\x11'" +2026-03-22 08:42:35.073534,RECEIVED,"b'\x17@\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x0ba\x11B\x00\x00\x00\x00\x00\x00\x00\x00\x9c/\xe2\x01\x00\x00\x00\x00\x16\xae\xab@\x00\x00\x00\x00\xcd\xcc\xa8B\x00\x00\x00\x00if\x96\xc0\x00\x00\x00\x00\x11k\x8dA9\x06\x06?T\xdc\xaa\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:49:19.321420,RECEIVED,"b""\xf8\x7f\x00\x00\x00\x00LI\x04C\xbd'(\xc1:\xff/C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00""" +2026-03-22 08:49:19.322031,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00c\xac\x89\xc0\x81""M\xc0\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7fi\x94'" +2026-03-22 08:49:19.994056,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:49:19.994661,RECEIVED,"b'\xbb'" +2026-03-22 08:49:19.995180,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:49:20.310453,RECEIVED,"b'\xbb'" +2026-03-22 08:49:20.311181,RECEIVED,"b""U\x00Ae\x98\xff\x02\x00\x00\xad'\xd9>\x00\x00\x00\x00\x00\x00\x00\x008\xa5\xe2\x01\x00\x00\x00\x00\xca/\xb4@\x00\x00\x00\x00\x00\x00\xddB\x00\x00\x00\x00\x00\x00`\xc2\x00\x00\x00\x002\x8b\x90A-i\xfc\xbf\x9e\xc6\xa8\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00""" +2026-03-22 08:49:20.311786,RECEIVED,"b""\xf8\x7f\x00\x00\x00\x00\x15\xb8\x03C \xe87\xc1)\x1e1C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\xa1\xf7'\xc0\xeaj\x9f\xc0\x00\x00""" +2026-03-22 08:49:20.312648,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x97o'" +2026-03-22 08:49:20.996393,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:49:20.996719,RECEIVED,"b'\xbb'" +2026-03-22 08:49:20.997201,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:49:21.999021,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:49:21.999632,RECEIVED,"b'\xbb'" +2026-03-22 08:49:22.000160,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:49:22.326766,RECEIVED,"b'\xbb'" +2026-03-22 08:49:22.327492,RECEIVED,"b'U\x00Ae\x98\xff\x02\x00\x00H\xea\xf7>\x00\x00\x00\x00\x00\x00\x00\x00*\x90\xe3\x01\x00\x00\x00\x00\xca/\xb4@\x00\x00\x00\x00\x9a\x99\xd7B\x00\x00\x00\x00gf<\xc2\x00\x00\x00\x00\xfc\xb4\x8dA3T\xdc\xbf\x9f\x8f\xab\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\xedJ\x04C\x08\x9e2\xc1V\xe10'" +2026-03-22 08:49:22.328481,RECEIVED,"b'C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x7f\xae3\xc0\x171\x9c\xc0\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\xda\xde'" +2026-03-22 08:49:23.001397,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:49:23.001928,RECEIVED,"b'\xbb'" +2026-03-22 08:49:23.002825,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:49:23.316892,RECEIVED,"b'\xbb'" +2026-03-22 08:49:23.317468,RECEIVED,"b'U\x00Ae\x98\x02\x02\x00\x00x\x05\x07?\x00\x00\x00\x00\x00\x00\x00\x00\x97\x05\xe4\x01\x00\x00\x00\x00\xe6\x17\xb1@\x00\x00\x00\x00ff\xceB\x00\x00\x00\x00_fF@\x00\x00\x00\x00\xb6\x81\x8aA\xf7\xc1\xa1\xbf\x13j\xac\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:49:23.317998,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00\xe2.\x05C\xce\xf1-\xc1fM0C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00A\x17D\xc0\xd7y\x93\xc0\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00'" +2026-03-22 08:49:23.318464,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x9c+'" +2026-03-22 08:49:24.004559,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:49:24.004925,RECEIVED,"b'\xbb'" +2026-03-22 08:49:24.005435,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:49:24.338848,RECEIVED,"b'\xbb'" +2026-03-22 08:49:24.339449,RECEIVED,"b'U\x00Ae\x98\x01\x02\x00\x00\xdd\xa1\x16?\x00\x00\x00\x00\x00\x00\x00\x00\x1c{\xe4\x01\x00\x00\x00\x00\x16\xae\xab@\x00\x00\x00\x00\xcc\xcc\xdbB\x00\x00\x00\x00\x9a\x99\xb9\xc1\x00\x00\x00\x00\xa6\x9d\x8aA\xd0\xbe\xe9\xbf\xbfm\xa1\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8'" +2026-03-22 08:49:24.339950,RECEIVED,"b'\x7f\x00\x00\x00\x00\x8b1\x05C\xe9z3\xc13\xd10C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\xf8a\x1b\xc0S\x18\x99\xc0\x00\x00\xc0'" +2026-03-22 08:49:24.340418,RECEIVED,"b'\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x10['" +2026-03-22 08:49:25.007423,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:49:25.007952,RECEIVED,"b'\xbb'" +2026-03-22 08:49:25.008466,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:49:25.328822,RECEIVED,"b'\xbb'" +2026-03-22 08:49:25.329412,RECEIVED,"b'U\x00Ae\x98\x01\x02\x00\x00\x1c\xbd%?\x00\x00\x00\x00\x00\x00\x00\x00\xa5\xf0\xe4\x01\x00\x00\x00\x00\x1c\xe8\xaa@\x00\x00\x00\x0033\x8cB\x00\x00\x00\x00\xce\xcc\x04\xc1\x00\x00\x00\x00\xe5\xb2\x8aAJ\xb1\xe8?\x97\xb3'" +2026-03-22 08:49:25.329900,RECEIVED,"b'\xa0\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x19\x84\x05C\x01\x01)\xc1\x83k0C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:49:25.330363,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00\xe9\x97\x9c\xc0\x9e\xee\x08\xc0\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\xdd\xf8'" +2026-03-22 08:49:26.009881,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:49:26.010385,RECEIVED,"b'\xbb'" +2026-03-22 08:49:26.010900,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:49:26.352850,RECEIVED,"b'\xbb'" +2026-03-22 08:49:26.353470,RECEIVED,"b'U\x00Ae\x98\x02\x02\x00\x00%Y5?\x00\x00\x00\x00\x00\x00\x00\x00\x1cf\xe5\x01\x00\x00\x00\x00\x16\xae\xab@\x00\x00\x00\x00\x9a\x99\xfc\xc2\x00\x00\x00\x00\x99\x99\xcf\xc2\x00\x00\x00\x00\x8b\xf5\x88A\x11FK\xc0\x9d\\\x8a@\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:49:26.354141,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00 \x8d\x05C\xf0d2\xc1Q\xcb0C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x95N\xaa@L[-?\x00\x00'" +2026-03-22 08:49:26.354705,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\xec\xbd'" +2026-03-22 08:49:27.013270,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:49:27.013859,RECEIVED,"b'\xbb'" +2026-03-22 08:49:27.014393,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:49:27.343058,RECEIVED,"b'\xbb'" +2026-03-22 08:49:27.344325,RECEIVED,"b'U\x00Ae\x98\x02\x02\x00\x00ctD?\x00\x00\x00\x00\x00\x00\x00\x00\xaf\xdb\xe5\x01\x00\x00\x00\x00\x16\xae\xab@\x00\x00\x00\x00nf\x16A\x00\x00\x00\x0053Q\xc2\x00\x00\x00\x00\x85\xa0\x8aA\xee_\xa9@\x90Q`\xbf\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00 \xa4\x05C\xb5\xe33\xc1\x84z0C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x14/\x89\xc0=qN@\x00\x00\xc0'" +2026-03-22 08:49:27.345087,RECEIVED,"b'\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f,\x90'" +2026-03-22 08:49:28.016585,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:49:28.017111,RECEIVED,"b'\xbb'" +2026-03-22 08:49:28.017618,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:49:28.332984,RECEIVED,"b'\xbb'" +2026-03-22 08:49:28.333555,RECEIVED,"b'U\x00Ae\x98\x01\x02\x00\x00\x06\x8fS?\x00\x00\x00\x00\x00\x00\x00\x00@Q\xe6\x01\x00\x00\x00\x00\x16\xae\xab@\x00\x00\x00\x00\x00\x00\xabB\x00\x00\x00\x0043U\xc2\x00\x00\x00\x00\x13\xee\x8bA\x89\x84\xd7>\x9a&\xab\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:49:28.334041,RECEIVED,"b""\xf8\x7f\x00\x00\x00\x005\xd5\x05C\xc0\x9a'\xc1\x8c\xcd0C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00""" +2026-03-22 08:49:28.334515,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00b\xca\x84\xc0D\xa1Y\xc0\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\xb6\x91'" +2026-03-22 08:49:29.019293,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:49:29.019650,RECEIVED,"b'\xbb'" +2026-03-22 08:49:29.020177,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:49:29.359080,RECEIVED,"b'\xbb'" +2026-03-22 08:49:29.359711,RECEIVED,"b'U\x00Ae\x98\x01\x02\x00\x00i7c?\x00\x00\x00\x00\x00\x00\x00\x00\xb8\xc6\xe6\x01\x00\x00\x00\x00\x16\xae\xab@\x00\x00\x00\x0033\xe7B\x00\x00\x00\x00gf\x0c\xc2\x00\x00\x00\x00\xc9\xd9\x8dAg\\\x14\xc0\x9f\xd3\x9a\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:49:29.360190,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00\xd7""\x06C\x8c\xae0\xc1o\xa40C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x8d\xd7\xed\xbf\xe5\r\xa1\xc0\x00\x00'" +2026-03-22 08:49:29.360692,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\xbe\xda'" +2026-03-22 08:49:30.022268,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:49:30.022543,RECEIVED,"b'\xbb'" +2026-03-22 08:49:30.023194,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:49:30.345253,RECEIVED,"b'\xbb'" +2026-03-22 08:49:30.345883,RECEIVED,"b'U\x00Ae\x98\x01\x02\x00\x00\x9aFr?\x00\x00\x00\x00\x00\x00\x00\x007<\xe7\x01\x00\x00\x00\x00\x16\xae\xab@\x00\x00\x00\x00\x00\x00\xf7B\x00\x00\x00\x00\xce\xcc4\xc1\x00\x00\x00\x00b\xc7\x8eAZ\x83=\xc0[)'" +2026-03-22 08:49:30.346382,RECEIVED,"b'\x8f\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\n\xb4\x06C\x9a\x9f,\xc1Zp0C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:49:30.346902,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00\xa0d\x8c\xbf\xd2\r\xa8\xc0\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x1d\xa0'" +2026-03-22 08:49:31.024770,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:49:31.025084,RECEIVED,"b'\xbb'" +2026-03-22 08:49:31.025581,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:49:31.335278,RECEIVED,"b'\xbb'" +2026-03-22 08:49:31.335932,RECEIVED,"b'U\x00Ae\x98\x01\x02\x00\x00\xb0a\x81?\x00\x00\x00\x00\x00\x00\x00\x00\xb6\xb1\xe7\x01\x00\x00\x00\x00*\\\xa9@\x00\x00\x00\x00\x00\x00\xd0B\x00\x00\x00\x00\xca\xcc\x14A\x00\x00\x00\x00\xf4\x01\x8aA8\xe3\xa3\xbfMT\xa4\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:49:31.336408,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00\x97\xdb\x06Cy\x98-\xc1b\xae0C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x82,/\xc0\xf2\xf3\x90\xc0\x00\x00'" +2026-03-22 08:49:31.336887,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\xf2/'" +2026-03-22 08:49:32.027680,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:49:32.028236,RECEIVED,"b'\xbb'" +2026-03-22 08:49:32.028809,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:49:33.030181,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:49:33.030720,RECEIVED,"b'\xbb'" +2026-03-22 08:49:33.031228,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:49:33.351370,RECEIVED,"b'\xbb'" +2026-03-22 08:49:33.352271,RECEIVED,"b'U\x00Ae\x98\x01\x02\x00\x00\xfc$\xa0?\x00\x00\x00\x00\x00\x00\x00\x00\xbf\x9c\xe8\x01\x00\x00\x00\x00\x1c\xe8\xaa@\x00\x00\x00\x00hfPB\x00\x00\x00\x0043m\xc2\x00\x00\x00\x00~Q\x89A\x8a\xf8Q@\x18\xdc\x86\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x003\x87\x06C\xad?6\xc1F\xc1'" +2026-03-22 08:49:33.352866,RECEIVED,"b'0C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\xae\xc3\xa9\xc0\x8f\xd0\x1d\xbf\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x97\\'" +2026-03-22 08:49:34.032995,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\xff\xf6E'" +2026-03-22 08:49:34.033323,RECEIVED,"b'\xbb'" +2026-03-22 08:49:34.033819,RECEIVED,"b'U\x00\xc9h\x03\x01\xf6Ep\xfe'" +2026-03-22 08:49:35.036321,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x03\xfaI'" +2026-03-22 08:49:35.036952,RECEIVED,"b'\xbb'" +2026-03-22 08:49:35.037569,RECEIVED,"b'U\x00\xc9h\x03\x01\xfaIx\n'" +2026-03-22 08:49:35.365256,RECEIVED,"b'\xbb'" +2026-03-22 08:49:35.365836,RECEIVED,"b'U\x00Ae\x98\x01\x02\x00\x00\x0c\xdc\xbe?\x00\x00\x00\x00\x00\x00\x00\x00\xd3\x87\xe9\x01\x00\x00\x00\x001\x96\xa8@\x00\x00\x00\x00\xcd\xcc\xcbB\x00\x00\x00\x00jf\xd6\xc0\x00\x00\x00\x00\x90D\x8bA\x9b\r\x8b\xbf\xad\xf6'" +2026-03-22 08:49:35.366324,RECEIVED,"b'\xa4\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x08\x11\x08C\x880-\xc1\xa0\xa10C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:49:35.366788,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00z\xd72\xc0\xda\xea\x8e\xc0\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\xa5\x05'" +2026-03-22 08:49:36.038249,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x03\xfaI'" +2026-03-22 08:49:36.038754,RECEIVED,"b'\xbb'" +2026-03-22 08:49:36.039303,RECEIVED,"b'U\x00\xc9h\x03\x01\xfaIx\n'" +2026-03-22 08:49:36.355301,RECEIVED,"b'\xbb'" +2026-03-22 08:49:36.355931,RECEIVED,"b'U\x00Ae\x98\xff\x02\x00\x007\xf7\xcd?\x00\x00\x00\x00\x00\x00\x00\x00J\xfd\xe9\x01\x00\x00\x00\x00\xc4\xf5\xb4@\x00\x00\x00\x00ff\xeaB\x00\x00\x00\x00\x00\x000\xc2\x00\x00\x00\x00\xad%\x93A\xd0n%\xc0\xe5\xf2\xa0\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:49:36.356425,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00\x1d\x16\x08CO).\xc1\x96\xb30C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x07+\xd0\xbf\xb1P\xad\xc0\x00\x00'" +2026-03-22 08:49:36.356885,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\xe5G'" +2026-03-22 08:49:37.041131,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x03\xfaI'" +2026-03-22 08:49:37.041826,RECEIVED,"b'\xbb'" +2026-03-22 08:49:37.043030,RECEIVED,"b'U\x00\xc9h\x03\x01\xfaIx\n'" +2026-03-22 08:49:37.377740,RECEIVED,"b'\xbb'" +2026-03-22 08:49:37.378313,RECEIVED,"b'U\x00Ae\x98\x01\x02\x00\x00I\x93\xdd?\x00\x00\x00\x00\x00\x00\x00\x00\xc3r\xea\x01\x00\x00\x00\x00#""\xaa@\x00\x00\x00\x00ff\xd1B\x00\x00\x00\x00\xcc\xcc\x1eB\x00\x00\x00\x00:\x0c\x8dA\xd3\xb0\xac\xbf\x83\x90'" +2026-03-22 08:49:37.378790,RECEIVED,"b'\xa4\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00L#\x08C\xdd\x93*\xc1\\\xa10C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:49:37.379263,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00U\xce%\xc0h\x91\x94\xc0\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x86\x83'" +2026-03-22 08:49:38.044385,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x03\xfaI'" +2026-03-22 08:49:38.044926,RECEIVED,"b'\xbb'" +2026-03-22 08:49:38.045522,RECEIVED,"b'U\x00\xc9h\x03\x01\xfaIx\n'" +2026-03-22 08:49:39.047282,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x04\xfbJ'" +2026-03-22 08:49:39.047813,RECEIVED,"b'\xbb'" +2026-03-22 08:49:39.048302,RECEIVED,"b'U\x00\xc9h\x03\x01\xfbJz\r'" +2026-03-22 08:49:40.049807,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x04\xfbJ'" +2026-03-22 08:49:40.050570,RECEIVED,"b'\xbb'" +2026-03-22 08:49:40.051108,RECEIVED,"b'U\x00\xc9h\x03\x01\xfbJz\r'" +2026-03-22 08:49:41.052835,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x04\xfbJ'" +2026-03-22 08:49:41.053106,RECEIVED,"b'\xbb'" +2026-03-22 08:49:41.053597,RECEIVED,"b'U\x00\xc9h\x03\x01\xfbJz\r'" +2026-03-22 08:49:41.373764,RECEIVED,"b'\xbb'" +2026-03-22 08:49:41.374375,RECEIVED,"b'U\x00Ae\x98\x08\x02\x00\x00\xfc\x8c\x1a@\x00\x00\x00\x00\x00\x00\x00\x00\xdaH\xec\x01\x00\x00\x00\x00\x16\xae\xab@\x00\x00\x00\x00\x99\x99\xd9A\x00\x00\x00\x00\x99\x99SB\x00\x00\x00\x00E\xee\x8bA\xed\xb1\x98@\xfc\xf2\x1c\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:49:41.374836,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00`&\tC\xe2\x9d(\xc1\xee\xdb0C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\xbaP\xa5\xc0@@\xb9?\x00\x00'" +2026-03-22 08:49:41.375301,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7fk\x85'" +2026-03-22 08:49:42.056010,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x04\xfbJ'" +2026-03-22 08:49:42.056670,RECEIVED,"b'\xbb'" +2026-03-22 08:49:42.057272,RECEIVED,"b'U\x00\xc9h\x03\x01\xfbJz\r'" +2026-03-22 08:49:43.058914,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x04\xfbJ'" +2026-03-22 08:49:43.059513,RECEIVED,"b'\xbb'" +2026-03-22 08:49:43.062982,RECEIVED,"b'U\x00\xc9h\x03\x01\xfbJz\r'" +2026-03-22 08:49:44.061647,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x04\xfbJ'" +2026-03-22 08:49:44.062382,RECEIVED,"b'\xbb'" +2026-03-22 08:49:44.063010,RECEIVED,"b'U\x00\xc9h\x03\x01\xfbJz\r'" +2026-03-22 08:49:44.377544,RECEIVED,"b'\xbb'" +2026-03-22 08:49:44.378149,RECEIVED,"b'U\x00Ae\x98\x08\x02\x00\x00(_H@\x00\x00\x00\x00\x00\x00\x00\x00|\xa9\xed\x01\x00\x00\x00\x00\x16\xae\xab@\x00\x00\x00\x00ef\xc0B\x00\x00\x00\x00433\xc2\x00\x00\x00\x008\xe7\x89A\x97T\x14\xbf\x06\xad'" +2026-03-22 08:49:44.378655,RECEIVED,"b'\xaa\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\xf18\nC\x04-)\xc1s\x8d0C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:49:44.379154,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00\xbd\xc4G\xc0\xdc\xa1\x8b\xc0\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f.\xaf'" +2026-03-22 08:49:45.064388,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x04\xfbJ'" +2026-03-22 08:49:45.064911,RECEIVED,"b'\xbb'" +2026-03-22 08:49:45.065407,RECEIVED,"b'U\x00\xc9h\x03\x01\xfbJz\r'" +2026-03-22 08:49:46.066894,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x04\xfbJ'" +2026-03-22 08:49:46.067216,RECEIVED,"b'\xbb'" +2026-03-22 08:49:46.067719,RECEIVED,"b'U\x00\xc9h\x03\x01\xfbJz\r'" +2026-03-22 08:49:46.389483,RECEIVED,"b'\xbb'" +2026-03-22 08:49:46.390101,RECEIVED,"b'U\x00Ae\x98\x08\x02\x00\x00\xc8\x16g@\x00\x00\x00\x00\x00\x00\x00\x00o\x94\xee\x01\x00\x00\x00\x00\x16\xae\xab@\x00\x00\x00\x00\x9a\x99\x90B\x00\x00\x00\x00\xcd\xcc\x12\xc2\x00\x00\x00\x00\x18\xb6\x8cA\x1a\xc9\xd0?\x8e\x8d'" +2026-03-22 08:49:46.390584,RECEIVED,"b'\xa3\xc0\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00&\x8d\nC\x16\xa0!\xc1\xe8>0C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:49:46.391094,RECEIVED,"b""\xf8\x7f\x00\x00\x00\x00-c\x93\xc0P\x140\xc0\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f'\xfe""" +2026-03-22 08:49:47.069257,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x04\xfbJ'" +2026-03-22 08:49:47.069783,RECEIVED,"b'\xbb'" +2026-03-22 08:49:47.070282,RECEIVED,"b'U\x00\xc9h\x03\x01\xfbJz\r'" +2026-03-22 08:49:48.072188,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x04\xfbJ'" +2026-03-22 08:49:48.079211,RECEIVED,"b'\xbb'" +2026-03-22 08:49:48.079786,RECEIVED,"b'U\x00\xc9h\x03\x01\xfbJz\r'" +2026-03-22 08:49:49.074749,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x04\xfbJ'" +2026-03-22 08:49:49.075073,RECEIVED,"b'\xbb'" +2026-03-22 08:49:49.075583,RECEIVED,"b'U\x00\xc9h\x03\x01\xfbJz\r'" +2026-03-22 08:49:49.393552,RECEIVED,"b'\xbb'" +2026-03-22 08:49:49.394184,RECEIVED,"b'U\x00Ae\x98\x08\x02\x00\x00\xb6\xea\x94@\x00\x00\x00\x00\x00\x00\x00\x00\xe9\xf4\xef\x01\x00\x00\x00\x00\x16\xae\xab@\x00\x00\x00\x00f\xe6,C\x00\x00\x00\x00hf>\xc1\x00\x00\x00\x00+\xf0\x8fA\x13]\xaa\xc0d\xc2)\xbf\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00'" +2026-03-22 08:49:49.394754,RECEIVED,"b'\xf8\x7f\x00\x00\x00\x00a\xf7\nC\x9b\x1b0\xc1G\x9e0C\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00\x00\x00\xf8\x7f\x00\x00\x00\x00#(e@\x9a\xb3\x7f\xc0\x00\x00'" +2026-03-22 08:49:49.395271,RECEIVED,"b'\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x00\x00\xc0\x7f\x87%'" +2026-03-22 08:49:50.077293,SENT,"b'\xbbU\x00\x8ah\x05\x00\x00\x00\x00\x04\xfbJ'" +2026-03-22 08:49:50.077812,RECEIVED,"b'\xbb'" +2026-03-22 08:49:50.078301,RECEIVED,"b'U\x00\xc9h\x03\x01\xfbJz\r'" diff --git a/driver/test/log/2026-03-22_08-49-18_log_usv.log b/driver/test/log/2026-03-22_08-49-18_log_usv.log new file mode 100644 index 0000000..16082ef --- /dev/null +++ b/driver/test/log/2026-03-22_08-49-18_log_usv.log @@ -0,0 +1,771 @@ +2026-03-22 08:49:18.968 | INFO | kogger_protocol_driver:setup_logging:81 - Kogger Protocol Driver: Loguru logging configured to level INFO and file log/2026-03-22_08-49-18_log_usv.log. +2026-03-22 08:49:18.968 | INFO | kogger_protocol_driver:__init__:224 - KoggerSBPDevice configured for port /dev/ttyAMA4, baudrate 921600, address 0 +2026-03-22 08:49:18.969 | INFO | kogger_protocol_driver:_open_file:43 - CSV logging enabled to log/2026-03-22_08-49-18_AUV_usbl.csv +2026-03-22 08:49:18.970 | INFO | kogger_protocol_driver:_reader_thread_loop:491 - Reader thread started. +2026-03-22 08:49:18.971 | SUCCESS | kogger_protocol_driver:connect:314 - Successfully connected to /dev/ttyAMA4 at 921600 and started reader thread. +2026-03-22 08:49:18.985 | INFO | __main__:main:127 - set_auto_response_filter(0)=True +2026-03-22 08:49:18.987 | INFO | __main__:main:129 - set_auto_response_timeout(0xffffffff)=True +2026-03-22 08:49:18.989 | INFO | __main__:main:131 - set_auto_response_payload(0xff)=True +2026-03-22 08:49:19.323 | INFO | __main__:print_message:34 - printer:{ + "id": 1, + "role": 2, + "reserved": 0, + "timestamp_us": 1053428855, + "ping_counter": 0, + "carrier_counter": 31600540, + "distance_m": 5.3650007247924805, + "distance_unc": 0.0, + "azimuth_deg": 84.4000015258789, + "azimuth_unc": 0.0, + "elevation_deg": -4.700001239776611, + "elevation_unc": 0.0, + "snr": 17.677278518676758, + "beacon_x_m": 0.5235324501991272, + "beacon_y_m": -5.339395523071289, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 132.28631591796875, + "usbl_pitch": -10.5097017288208, + "usbl_roll": 175.99697875976562, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -4.302293300628662, + "beacon_e_m": -3.205230951309204, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:20.314 | INFO | __main__:print_message:34 - printer:{ + "id": 255, + "role": 2, + "reserved": 0, + "timestamp_us": 1054418861, + "ping_counter": 0, + "carrier_counter": 31630648, + "distance_m": 5.630833625793457, + "distance_unc": 0.0, + "azimuth_deg": 110.5, + "azimuth_unc": 0.0, + "elevation_deg": -56.0, + "elevation_unc": 0.0, + "snr": 18.06796646118164, + "beacon_x_m": -1.9719597101211548, + "beacon_y_m": -5.274245262145996, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 131.7190704345703, + "usbl_pitch": -11.494171142578125, + "usbl_roll": 177.11781311035156, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -2.6244890689849854, + "beacon_e_m": -4.9818010330200195, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:22.329 | INFO | __main__:print_message:34 - printer:{ + "id": 255, + "role": 2, + "reserved": 0, + "timestamp_us": 1056434760, + "ping_counter": 0, + "carrier_counter": 31690794, + "distance_m": 5.630833625793457, + "distance_unc": 0.0, + "azimuth_deg": 107.80000305175781, + "azimuth_unc": 0.0, + "elevation_deg": -47.10000228881836, + "elevation_unc": 0.0, + "snr": 17.71337127685547, + "beacon_x_m": -1.721319556236267, + "beacon_y_m": -5.361281871795654, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 132.2926788330078, + "usbl_pitch": -11.163581848144531, + "usbl_roll": 176.88021850585938, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -2.807525396347046, + "beacon_e_m": -4.880992412567139, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:23.319 | INFO | __main__:print_message:34 - printer:{ + "id": 2, + "role": 2, + "reserved": 0, + "timestamp_us": 1057424760, + "ping_counter": 0, + "carrier_counter": 31720855, + "distance_m": 5.534167289733887, + "distance_unc": 0.0, + "azimuth_deg": 103.19999694824219, + "azimuth_unc": 0.0, + "elevation_deg": 3.0999982357025146, + "elevation_unc": 0.0, + "snr": 17.313335418701172, + "beacon_x_m": -1.263731837272644, + "beacon_y_m": -5.387948513031006, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 133.18313598632812, + "usbl_pitch": -10.87153434753418, + "usbl_roll": 176.30233764648438, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -3.0639193058013916, + "beacon_e_m": -4.608623027801514, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:24.341 | INFO | __main__:print_message:34 - printer:{ + "id": 1, + "role": 2, + "reserved": 0, + "timestamp_us": 1058447837, + "ping_counter": 0, + "carrier_counter": 31750940, + "distance_m": 5.3650007247924805, + "distance_unc": 0.0, + "azimuth_deg": 109.89999389648438, + "azimuth_unc": 0.0, + "elevation_deg": -23.200000762939453, + "elevation_unc": 0.0, + "snr": 17.326976776123047, + "beacon_x_m": -1.8261356353759766, + "beacon_y_m": -5.044646739959717, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 133.1935272216797, + "usbl_pitch": -11.217507362365723, + "usbl_roll": 176.8171844482422, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -2.427854537963867, + "beacon_e_m": -4.784219264984131, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:25.331 | INFO | __main__:print_message:34 - printer:{ + "id": 1, + "role": 2, + "reserved": 0, + "timestamp_us": 1059437852, + "ping_counter": 0, + "carrier_counter": 31781029, + "distance_m": 5.34083366394043, + "distance_unc": 0.0, + "azimuth_deg": 70.0999984741211, + "azimuth_unc": 0.0, + "elevation_deg": -8.30000114440918, + "elevation_unc": 0.0, + "snr": 17.337350845336914, + "beacon_x_m": 1.8179104328155518, + "beacon_y_m": -5.021922588348389, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 133.51600646972656, + "usbl_pitch": -10.562745094299316, + "usbl_roll": 176.4199676513672, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -4.893543720245361, + "beacon_e_m": -2.139564037322998, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:26.355 | INFO | __main__:print_message:34 - printer:{ + "id": 2, + "role": 2, + "reserved": 0, + "timestamp_us": 1060460837, + "ping_counter": 0, + "carrier_counter": 31811100, + "distance_m": 5.3650007247924805, + "distance_unc": 0.0, + "azimuth_deg": -126.30000305175781, + "azimuth_unc": 0.0, + "elevation_deg": -103.79999542236328, + "elevation_unc": 0.0, + "snr": 17.11989402770996, + "beacon_x_m": -3.1761515140533447, + "beacon_y_m": 4.323805332183838, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 133.55126953125, + "usbl_pitch": -11.149642944335938, + "usbl_roll": 176.79420471191406, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": 5.322092533111572, + "beacon_e_m": 0.6771743297576904, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:27.346 | INFO | __main__:print_message:34 - printer:{ + "id": 2, + "role": 2, + "reserved": 0, + "timestamp_us": 1061450851, + "ping_counter": 0, + "carrier_counter": 31841199, + "distance_m": 5.3650007247924805, + "distance_unc": 0.0, + "azimuth_deg": 9.400007247924805, + "azimuth_unc": 0.0, + "elevation_deg": -52.30000686645508, + "elevation_unc": 0.0, + "snr": 17.328378677368164, + "beacon_x_m": 5.292960166931152, + "beacon_y_m": -0.8762445449829102, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 133.64111328125, + "usbl_pitch": -11.24309253692627, + "usbl_roll": 176.47857666015625, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -4.286996841430664, + "beacon_e_m": 3.225661516189575, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:28.335 | INFO | __main__:print_message:34 - printer:{ + "id": 1, + "role": 2, + "reserved": 0, + "timestamp_us": 1062440710, + "ping_counter": 0, + "carrier_counter": 31871296, + "distance_m": 5.3650007247924805, + "distance_unc": 0.0, + "azimuth_deg": 85.5, + "azimuth_unc": 0.0, + "elevation_deg": -53.30000305175781, + "elevation_unc": 0.0, + "snr": 17.491247177124023, + "beacon_x_m": 0.4209330379962921, + "beacon_y_m": -5.348462104797363, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 133.8328399658203, + "usbl_pitch": -10.47528076171875, + "usbl_roll": 176.80291748046875, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -4.149704933166504, + "beacon_e_m": -3.400467872619629, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:29.361 | INFO | __main__:print_message:34 - printer:{ + "id": 1, + "role": 2, + "reserved": 0, + "timestamp_us": 1063466857, + "ping_counter": 0, + "carrier_counter": 31901368, + "distance_m": 5.3650007247924805, + "distance_unc": 0.0, + "azimuth_deg": 115.5999984741211, + "azimuth_unc": 0.0, + "elevation_deg": -35.10000228881836, + "elevation_unc": 0.0, + "snr": 17.731340408325195, + "beacon_x_m": -2.3181397914886475, + "beacon_y_m": -4.838332653045654, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 134.13609313964844, + "usbl_pitch": -11.042613983154297, + "usbl_roll": 176.64231872558594, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -1.8581405878067017, + "beacon_e_m": -5.0329461097717285, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:30.347 | INFO | __main__:print_message:34 - printer:{ + "id": 1, + "role": 2, + "reserved": 0, + "timestamp_us": 1064453786, + "ping_counter": 0, + "carrier_counter": 31931447, + "distance_m": 5.3650007247924805, + "distance_unc": 0.0, + "azimuth_deg": 123.5, + "azimuth_unc": 0.0, + "elevation_deg": -11.30000114440918, + "elevation_unc": 0.0, + "snr": 17.847354888916016, + "beacon_x_m": -2.961142063140869, + "beacon_y_m": -4.4737982749938965, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 134.70327758789062, + "usbl_pitch": -10.788965225219727, + "usbl_roll": 176.43887329101562, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -1.0968208312988281, + "beacon_e_m": -5.251687049865723, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:31.337 | INFO | __main__:print_message:34 - printer:{ + "id": 1, + "role": 2, + "reserved": 0, + "timestamp_us": 1065443760, + "ping_counter": 0, + "carrier_counter": 31961526, + "distance_m": 5.2925004959106445, + "distance_unc": 0.0, + "azimuth_deg": 104.0, + "azimuth_unc": 0.0, + "elevation_deg": 9.299997329711914, + "elevation_unc": 0.0, + "snr": 17.250953674316406, + "beacon_x_m": -1.2803716659545898, + "beacon_y_m": -5.135290622711182, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 134.85777282714844, + "usbl_pitch": -10.849724769592285, + "usbl_roll": 176.68118286132812, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -2.737091541290283, + "beacon_e_m": -4.529778480529785, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:33.353 | INFO | __main__:print_message:34 - printer:{ + "id": 1, + "role": 2, + "reserved": 0, + "timestamp_us": 1067459836, + "ping_counter": 0, + "carrier_counter": 32021695, + "distance_m": 5.34083366394043, + "distance_unc": 0.0, + "azimuth_deg": 52.100006103515625, + "azimuth_unc": 0.0, + "elevation_deg": -59.30000305175781, + "elevation_unc": 0.0, + "snr": 17.164791107177734, + "beacon_x_m": 3.280794620513916, + "beacon_y_m": -4.214366912841797, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 134.5281219482422, + "usbl_pitch": -11.390545845031738, + "usbl_roll": 176.75497436523438, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -5.305136680603027, + "beacon_e_m": -0.6164636015892029, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:35.367 | INFO | __main__:print_message:34 - printer:{ + "id": 1, + "role": 2, + "reserved": 0, + "timestamp_us": 1069472780, + "ping_counter": 0, + "carrier_counter": 32081875, + "distance_m": 5.268333911895752, + "distance_unc": 0.0, + "azimuth_deg": 101.9000015258789, + "azimuth_unc": 0.0, + "elevation_deg": -6.7000017166137695, + "elevation_unc": 0.0, + "snr": 17.408477783203125, + "beacon_x_m": -1.0863527059555054, + "beacon_y_m": -5.155111789703369, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 136.0665283203125, + "usbl_pitch": -10.824348449707031, + "usbl_roll": 176.63134765625, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -2.7944016456604004, + "beacon_e_m": -4.466168403625488, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:36.357 | INFO | __main__:print_message:34 - printer:{ + "id": 255, + "role": 2, + "reserved": 0, + "timestamp_us": 1070462775, + "ping_counter": 0, + "carrier_counter": 32111946, + "distance_m": 5.655000686645508, + "distance_unc": 0.0, + "azimuth_deg": 117.19999694824219, + "azimuth_unc": 0.0, + "elevation_deg": -44.0, + "elevation_unc": 0.0, + "snr": 18.393396377563477, + "beacon_x_m": -2.584888458251953, + "beacon_y_m": -5.0296502113342285, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 136.0863800048828, + "usbl_pitch": -10.885085105895996, + "usbl_roll": 176.70150756835938, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -1.6263130903244019, + "beacon_e_m": -5.416100025177002, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:37.380 | INFO | __main__:print_message:34 - printer:{ + "id": 1, + "role": 2, + "reserved": 0, + "timestamp_us": 1071485769, + "ping_counter": 0, + "carrier_counter": 32142019, + "distance_m": 5.316667079925537, + "distance_unc": 0.0, + "azimuth_deg": 104.69999694824219, + "azimuth_unc": 0.0, + "elevation_deg": 39.69999694824219, + "elevation_unc": 0.0, + "snr": 17.630970001220703, + "beacon_x_m": -1.3491462469100952, + "beacon_y_m": -5.142640590667725, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 136.13787841796875, + "usbl_pitch": -10.661099433898926, + "usbl_roll": 176.63031005859375, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -2.5907185077667236, + "beacon_e_m": -4.642749786376953, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:41.376 | INFO | __main__:print_message:34 - printer:{ + "id": 8, + "role": 2, + "reserved": 0, + "timestamp_us": 1075481852, + "ping_counter": 0, + "carrier_counter": 32262362, + "distance_m": 5.3650007247924805, + "distance_unc": 0.0, + "azimuth_deg": 27.19999885559082, + "azimuth_unc": 0.0, + "elevation_deg": 52.89999771118164, + "elevation_unc": 0.0, + "snr": 17.491342544555664, + "beacon_x_m": 4.771719455718994, + "beacon_y_m": -2.4523305892944336, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 137.14990234375, + "usbl_pitch": -10.538545608520508, + "usbl_roll": 176.85910034179688, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -5.166104316711426, + "beacon_e_m": 1.4472732543945312, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:44.380 | INFO | __main__:print_message:34 - printer:{ + "id": 8, + "role": 2, + "reserved": 0, + "timestamp_us": 1078484776, + "ping_counter": 0, + "carrier_counter": 32352636, + "distance_m": 5.3650007247924805, + "distance_unc": 0.0, + "azimuth_deg": 96.19998931884766, + "azimuth_unc": 0.0, + "elevation_deg": -44.80000305175781, + "elevation_unc": 0.0, + "snr": 17.237899780273438, + "beacon_x_m": -0.5794157385826111, + "beacon_y_m": -5.333621025085449, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 138.22242736816406, + "usbl_pitch": -10.573490142822266, + "usbl_roll": 176.5525360107422, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -3.12138295173645, + "beacon_e_m": -4.363508224487305, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:46.392 | INFO | __main__:print_message:34 - printer:{ + "id": 8, + "role": 2, + "reserved": 0, + "timestamp_us": 1080497864, + "ping_counter": 0, + "carrier_counter": 32412783, + "distance_m": 5.3650007247924805, + "distance_unc": 0.0, + "azimuth_deg": 72.30000305175781, + "azimuth_unc": 0.0, + "elevation_deg": -36.70000076293945, + "elevation_unc": 0.0, + "snr": 17.588912963867188, + "beacon_x_m": 1.6311371326446533, + "beacon_y_m": -5.111029624938965, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 138.55136108398438, + "usbl_pitch": -10.101583480834961, + "usbl_roll": 176.2457275390625, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": -4.605856418609619, + "beacon_e_m": -2.751239776611328, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:49.396 | INFO | __main__:print_message:34 - printer:{ + "id": 8, + "role": 2, + "reserved": 0, + "timestamp_us": 1083501238, + "ping_counter": 0, + "carrier_counter": 32503017, + "distance_m": 5.3650007247924805, + "distance_unc": 0.0, + "azimuth_deg": 172.89999389648438, + "azimuth_unc": 0.0, + "elevation_deg": -11.900001525878906, + "elevation_unc": 0.0, + "snr": 17.99226951599121, + "beacon_x_m": -5.323861598968506, + "beacon_y_m": -0.6631224155426025, + "beacon_latitude": "nan", + "beacon_longitude": "nan", + "beacon_depth": 0.0, + "usbl_yaw": 138.96632385253906, + "usbl_pitch": -11.006739616394043, + "usbl_roll": 176.61827087402344, + "usbl_latitude": "nan", + "usbl_longitude": "nan", + "last_iTOW": 0, + "beacon_n_m": 3.5805747509002686, + "beacon_e_m": -3.9953370094299316, + "code_snr": [ + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan", + "nan" + ] +} +2026-03-22 08:49:50.179 | INFO | kogger_protocol_driver:_reader_thread_loop:568 - Reader thread finished. +2026-03-22 08:49:50.180 | INFO | kogger_protocol_driver:disconnect:352 - Serial port /dev/ttyAMA4 closed. +2026-03-22 08:49:50.181 | INFO | kogger_protocol_driver:disconnect:364 - Disconnected and cleaned up. diff --git a/driver/test/test_auv.py b/driver/test/test_auv.py new file mode 100755 index 0000000..e0ee383 --- /dev/null +++ b/driver/test/test_auv.py @@ -0,0 +1,178 @@ +#! /usr/bin/env python +# Test to run AUV. Will respond to all ping received and save the state received + +import time +import sys +import json +from loguru import logger +import os +import datetime + +# Add the parent directory to the Python path to find the driver module +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +try: + #from kogger_protocol_driver import KoggerSBPDevice, setup_logging + import kogger_protocol_driver +except ImportError: + logger.critical("Failed to import KoggerSBPDevice. Make sure kogger_protocol_driver.py is in the parent directory.") + sys.exit(1) + + +# --- Script Configuration --- +# Set the desired logging level: "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL" +LOG_LEVEL = "INFO" +timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") +LOG_FILE = "log/"+str(timestamp)+"_log_auv.log" +# Default serial port if not provided via command line +#DEFAULT_SERIAL_PORT = "/dev/ttyUSB0" +DEFAULT_SERIAL_PORT = "/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0" +DEFAULT_SERIAL_PORT = "/dev/ttyAMA4" +DEFAULT_SERIAL_SPEED = 921600 + +response_received = -1 + +antenna = None + +def print_message(message): + """ + This function is a callback that will be executed for each message + received from the antenna that is not a direct response to a command. + """ + parsed = json.loads(str(message).replace("nan", "'nan'").replace("'",'"')) + logger.info(json.dumps(parsed, indent=2)) + try: + global response_received + response_received = message["id"] + logger.info("resp="+str(response_received)) + except Exception as e: + logger.info("exception="+str(e)) + pass + +def test_callback(message): + global antenna + data=antenna.get_usbl_data() + global response_received + response_received = message["id"] + logger.info("test!!!!!!"+str(data)) + return message + +def check_all_getters(antenna): + """ + Calls all get_* methods on the antenna object and prints the results. + """ + print("\n--- Checking all getter methods ---") + + # A list of all getter methods to call. + # Some methods require arguments, which are provided as tuples. + # (method_name, args_tuple) + getters_to_test = [ + #('get_timestamp', ()), + #('get_distance', (0,)), + #('get_distance', (1,)), + #('get_chart_data', ()), + ('get_attitude', (0,)), #!!OK + #('get_attitude', (1,)), + #('get_temperature', ()), + #('get_dataset_config', (0,)), + #('get_distance_setup', ()), + #('get_chart_setup', ()), + #('get_transceiver_settings', ()), + #('get_sound_speed', ()), + #('get_uart_config', (1, 0)), # uart_id=1, version=0 + #('get_uart_config', (1, 1)), # uart_id=1, version=1 + ('get_version_info', ()), #!! OK + ('get_mark_status', ()), #!! OK + #('get_diagnostics', ()), + #('get_navigation_data', ()), + #('get_dvl_velocity_data', ()), + #('get_signal_encoder_data', ()), + #('get_signal_decoder_data', ()), + #('get_auto_response_timeout', ()), + #('get_auto_response_filter', ()), + #('get_auto_response_payload', ()), + ('get_usbl_solution', ()), + ] + + for method_name, args in getters_to_test: + try: + method = getattr(antenna, method_name) + print(f"Calling {method_name}{args}...") + result = method(*args) + print(f"Result: {result}") + except Exception as e: + print(f"An error occurred while calling {method_name}: {e}") + print("-" * 20) + time.sleep(0.01) # Give the device a moment between commands + + print("--- Finished checking all getter methods ---\n") + + +def main(): + """ + Main function to connect to the Kogger antenna and listen for messages. + """ + # Determine the serial port to use + if len(sys.argv) > 1: + serial_port = sys.argv[1] + else: + serial_port = DEFAULT_SERIAL_PORT + print(f"No serial port provided. Using default: {serial_port}") + + # Instantiate the driver + global antenna + antenna = kogger_protocol_driver.KoggerSBPDevice(serial_port, DEFAULT_SERIAL_SPEED, default_timeout=0.02, log_level=LOG_LEVEL, log_file=LOG_FILE) + + try: + # Connect to the antenna + if not antenna.connect(): + print(f"Failed to connect to the antenna on port {serial_port}", file=sys.stderr) + sys.exit(1) + print(f"Successfully connected to the antenna on {serial_port}.") + # Perform a one-time check of all getter functions + check_all_getters(antenna) + + result = antenna.set_auto_response_filter(0) + logger.info("set_auto_response_filter(0)="+str(result)) + result = antenna.set_auto_response_timeout(0xffffffff) + logger.info("set_auto_response_timeout(0xffffffff)="+str(result)) + result = antenna.set_auto_response_payload(0xff) + logger.info("set_auto_response_payload(0xff)="+str(result)) + antenna.register_precallback(kogger_protocol_driver.ID_USBL_SOLUTION, antenna.callback_usbl_solution) + antenna.register_callback(kogger_protocol_driver.ID_USBL_SOLUTION, test_callback) + antenna.register_default_callback(print_message) + response_received_old = -1 + global response_received + while True: + logger.info("response_received="+str(response_received)) + if response_received_old != response_received: + response_received_old = response_received + try: + result = antenna.set_auto_response_payload(response_received) + except: + result = antenna.set_auto_response_payload(0xff) + logger.info("antenna.set_auto_response_payload("+str(response_received)+")="+str(result)) + time.sleep(1) + for i in range(10000): + #result = antenna.send_acoustic_ping(i%9) + result = antenna.send_acoustic_ping(0) + print("sent "+str(i%9)+"result="+str(result)) + time.sleep(1) + + exit() + + + except KeyboardInterrupt: + print("\nExiting...") + except Exception as e: + print(f"An error occurred: {e}", file=sys.stderr) + exc_type, exc_obj, exc_tb = sys.exc_info() + fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] + print(f"type={exc_type}, fname={fname}, line={exc_tb.tb_lineno}") + finally: + # Ensure the connection is closed gracefully + print("Disconnecting from the antenna.") + antenna.disconnect() + +if __name__ == "__main__": + main() diff --git a/driver/test/test_kogger_driver.py b/driver/test/test_kogger_driver.py new file mode 100755 index 0000000..ac47e9a --- /dev/null +++ b/driver/test/test_kogger_driver.py @@ -0,0 +1,252 @@ +#! /usr/bin/env python +import os +import sys +import time +import io # For BytesIO to simulate file reading for the mock +from loguru import logger +import threading + +# Ensure the kogger_protocol_driver.py is in the Python path +# If it's in the same directory, this is usually fine. +# Otherwise, you might need to adjust sys.path: +# sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) # If driver is one dir up + +try: + from kogger_protocol_driver import KoggerSBPDevice, ID_TIMESTAMP, ID_ATTITUDE, ID_UART, ID_FLASH, RESP_OK, RESP_ERR_KEY +except ImportError: + logger.critical("Failed to import KoggerSBPDevice. Make sure kogger_protocol_driver.py is in the same directory or Python path.") + sys.exit(1) + +# --- Loguru Setup for the test script --- +logger.remove() +logger.add(sys.stderr, level="DEBUG", format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}") + +# --- Content for test_messages.bin --- +# Frame 1: Timestamp response (12345 ms) +frame1_ts_resp = b'\xBB\x55\x00\x01\x01\x04\x39\x30\x00\x00\x49\x95' +# Frame 2: Unsolicited Attitude data +frame2_att_unsol = b'\xBB\x55\x00\x01\x04\x06\xE8\x03\xF4\x01\x38\xFF\xC8\x28' +# Frame 3: RESP_OK to a hypothetical SET_UART_CONFIG command (ID 0x18), orig_chk A0B0 +frame3_uart_resp_ok = b'\xBB\x55\x00\x81\x18\x03\x01\xA0\xB0\x2D\x24' +# Frame 4: RESP_ERR_KEY to a hypothetical SAVE_SETTINGS_TO_FLASH command (ID 0x23), orig_chk C1D1 +frame4_flash_resp_err = b'\xBB\x55\x00\x81\x23\x03\x07\xC1\xD1\xB0\x0D' +# Frame 5: Another unsolicited Timestamp (67890 ms) +frame5_ts_unsol = b'\xBB\x55\x00\x01\x01\x04\x32\x09\x01\x00\x42\xCD' + +# Concatenate frames to simulate the binary file content +TEST_BINARY_DATA = frame1_ts_resp + frame2_att_unsol + frame3_uart_resp_ok + frame4_flash_resp_err + frame5_ts_unsol +TEST_BINARY_FILE_PATH = "test_messages.bin" + + +class MockSerial: + """ + A mock serial port that reads from a predefined byte stream (simulating a file) + and logs data written to it. + """ + def __init__(self, port, baudrate, timeout=0.1, binary_data=b""): + self.port = port + self.baudrate = baudrate + self.timeout = timeout # Read timeout + self._is_open = False + self.binary_stream = io.BytesIO(binary_data) # Use BytesIO to simulate file reading + self.written_data = bytearray() # To capture data written by the driver + + # For KoggerSBPDevice, serial_read_timeout is passed to its __init__ + # and used for its internal serial.Serial object. + # Our mock uses its own 'timeout' for its 'read' method. + # The driver's serial_read_timeout on KoggerSBPDevice will configure the *real* serial port + # if it were used. Here, it's less critical for the mock itself, but good to have. + logger.info(f"MockSerial initialized for port '{port}' at {baudrate} baud with {len(binary_data)} bytes of test data.") + + def open(self): + self._is_open = True + logger.info(f"MockSerial port {self.port} opened.") + + def close(self): + self._is_open = False + logger.info(f"MockSerial port {self.port} closed.") + self.binary_stream.close() + + @property + def is_open(self): + return self._is_open + + @property + def in_waiting(self): + if not self._is_open: + return 0 + current_pos = self.binary_stream.tell() + # Get the total number of bytes in the stream buffer + # For BytesIO, getbuffer().nbytes gives total size + total_size = self.binary_stream.getbuffer().nbytes + return total_size - current_pos + + + def read(self, size=1): + if not self._is_open: + raise serial.SerialException("Port not open") + + # Simulate timeout behavior: if no data, return empty bytes after timeout + # For this mock, if we reach EOF, read() returns b''. + # The driver's reader thread has its own loop and small reads. + data = self.binary_stream.read(size) + if data: + logger.debug(f"MockSerial: read {len(data)} bytes: {data.hex().upper()}") + # else: + # logger.debug(f"MockSerial: read attempt, no data (EOF or simulated timeout)") + return data + + def write(self, data): + if not self.is_open: + raise serial.SerialException("Port not open") + logger.info(f"MockSerial: driver wrote {len(data)} bytes: {data.hex().upper()}") + self.written_data.extend(data) + return len(data) + + def reset_input_buffer(self): + logger.info("MockSerial: reset_input_buffer called (clearing mock written_data for this example).") + # In a real scenario, this clears hardware buffers. Here, it's less meaningful + # unless the driver specifically expects some state change. + # The driver's internal _receive_buffer will still exist. + + def reset_output_buffer(self): + logger.info("MockSerial: reset_output_buffer called.") + # Clears OS transmit buffer. No direct equivalent for simple mock. + +# --- Test Callbacks --- +received_unsolicited_attitude = None +received_unsolicited_timestamps = [] + +def attitude_callback(frame): + global received_unsolicited_attitude + logger.success(f"[ATTITUDE CALLBACK] Received frame ID: {frame['id']:#02x}") + if frame['id'] == ID_ATTITUDE and frame.get('checksum_ok'): + payload = frame['payload'] + yaw, pitch, roll = struct.unpack(' 0 and 67890 in received_unsolicited_timestamps: + logger.success(f" PASS: Second unsolicited timestamp (67890ms) received: {received_unsolicited_timestamps}") + elif len(received_unsolicited_timestamps) > 0 : + logger.warning(f" WARN: Unsolicited timestamp(s) received, but 67890ms not found. Got: {received_unsolicited_timestamps}") + else: + logger.error(" FAIL: Second unsolicited timestamp (67890ms) NOT received via callback.") + + # --- Check remaining data in mock port --- + remaining_bytes = mock_port.in_waiting + if remaining_bytes == 0: + logger.success(f"\n--- All test data consumed from mock port. ---") + else: + logger.warning(f"\n--- {remaining_bytes} bytes remaining in mock port's data stream. ---") + logger.debug(f"Remaining data: {mock_port.read(remaining_bytes).hex().upper()}") + + + # Cleanup + logger.info("Stopping driver and closing mock port...") + driver.disconnect() # This will stop the thread and close the mock port + + # if os.path.exists(TEST_BINARY_FILE_PATH): + # os.remove(TEST_BINARY_FILE_PATH) + # logger.info(f"Removed test data file: {TEST_BINARY_FILE_PATH}") + +if __name__ == "__main__": + run_tests() + logger.info("Test script finished.") + diff --git a/driver/test/test_messages.bin b/driver/test/test_messages.bin new file mode 100644 index 0000000..b55ad48 --- /dev/null +++ b/driver/test/test_messages.bin @@ -0,0 +1,16 @@ +# This would be written to test_messages.bin by the test script, +# or you can create the file with these exact bytes. +# For clarity, showing how it's constructed: +# Frame 1: Timestamp response (12345 ms) +frame1 = b'\xBB\x55\x00\x01\x01\x04\x39\x30\x00\x00\x49\x95' +# Frame 2: Unsolicited Attitude data +frame2 = b'\xBB\x55\x00\x01\x04\x06\xE8\x03\xF4\x01\x38\xFF\xC8\x28' +# Frame 3: RESP_OK to a SET_UART_CONFIG command (ID 0x18) +frame3 = b'\xBB\x55\x00\x81\x18\x03\x01\xA0\xB0\x2D\x24' +# Frame 4: RESP_ERR_KEY to a SAVE_SETTINGS_TO_FLASH command (ID 0x23) +frame4 = b'\xBB\x55\x00\x81\x23\x03\x07\xC1\xD1\xB0\x0D' +# Frame 5: Another unsolicited Timestamp (67890 ms) +frame5 = b'\xBB\x55\x00\x01\x01\x04\x32\x09\x01\x00\x42\xCD' + +binary_file_content = frame1 + frame2 + frame3 + frame4 + frame5 + diff --git a/driver/test/test_multi_antenna.py b/driver/test/test_multi_antenna.py new file mode 100755 index 0000000..a4ac6e3 --- /dev/null +++ b/driver/test/test_multi_antenna.py @@ -0,0 +1,230 @@ +#! /usr/bin/env python +# Test to run multi antenna. +# arg 1 is the USB number +# arg 2 is the index slot +# arg 3 is the total slot + +import time +import sys +import tty +import termios +import select +import json +import os +from loguru import logger +import datetime + +# Add the parent directory to the Python path to find the driver module +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +try: + #from kogger_protocol_driver import KoggerSBPDevice, setup_logging + import kogger_protocol_driver +except ImportError: + logger.critical("Failed to import KoggerSBPDevice. Make sure kogger_protocol_driver.py is in the parent directory.") + sys.exit(1) + + +# --- Script Configuration --- +# Set the desired logging level: "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL" +LOG_LEVEL = "INFO" +timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") +LOG_FILE = "log/"+str(timestamp)+"_log_usv.log" +# Default serial port if not provided via command line +DEFAULT_SERIAL_PORT = "/dev/ttyUSB0" +#DEFAULT_SERIAL_PORT = "/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0" +DEFAULT_SERIAL_SPEED = 921600 + +antenna = None + + +def print_message(message): + """ + This function is a callback that will be executed for each message + received from the antenna that is not a direct response to a command. + """ + parsed = json.loads(str(message).replace("nan", "'nan'").replace("'",'"')) + logger.info("printer:"+json.dumps(parsed, indent=2)) + +def test_callback(message): + global antenna + data=antenna.get_usbl_data() + global response_received + response_received = message["id"] + logger.info("test!!!!!!"+str(data)) + return message + +def is_data(): + """ + Checks if there is data to be read from stdin. + """ + return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []) + +def check_all_getters(antenna): + """ + Calls all get_* methods on the antenna object and prints the results. + """ + print("\n--- Checking all getter methods ---") + + # A list of all getter methods to call. + # Some methods require arguments, which are provided as tuples. + # (method_name, args_tuple) + getters_to_test = [ + #('get_timestamp', ()), + #('get_distance', (0,)), + #('get_distance', (1,)), + #('get_chart_data', ()), + #('get_attitude', (0,)), #!!OK + #('get_attitude', (1,)), + #('get_temperature', ()), + #('get_dataset_config', (0,)), + #('get_distance_setup', ()), + #('get_chart_setup', ()), + #('get_transceiver_settings', ()), + #('get_sound_speed', ()), + #('get_uart_config', (1, 0)), # uart_id=1, version=0 + #('get_uart_config', (1, 1)), # uart_id=1, version=1 + ('get_version_info', ()), #!! OK + #('get_mark_status', ()), #!! OK + #('get_diagnostics', ()), + #('get_navigation_data', ()), + #('get_dvl_velocity_data', ()), + #('get_signal_encoder_data', ()), + #('get_signal_decoder_data', ()), + #('get_auto_response_timeout', ()), + #('get_auto_response_filter', ()), + #('get_auto_response_payload', ()), + #('get_usbl_solution', ()), + ] + + for method_name, args in getters_to_test: + try: + method = getattr(antenna, method_name) + print(f"Calling {method_name}{args}...") + result = method(*args) + print(f"Result: {result}") + except Exception as e: + print(f"An error occurred while calling {method_name}: {e}") + print("-" * 20) + time.sleep(0.01) # Give the device a moment between commands + + print("--- Finished checking all getter methods ---\n") + + +def main(): + """ + Main function to connect to the Kogger antenna and listen for messages. + """ + # Determine the serial port to use + slot_total = 0 + if len(sys.argv)>1: + if sys.argv[1].startswith("/dev/"): + DEFAULT_SERIAL_PORT = sys.argv[1] + serial_port = sys.argv[1] + slot_index = int(sys.argv[2]) + slot_total = int(sys.argv[3]) + slot_duration = 1 + else: + serial_port = "/dev/ttyUSB"+str(sys.argv[1]) + slot_index = 0 + slot_total = 0 + slot_duration = 1 + else: + serial_port = DEFAULT_SERIAL_PORT + print(f"No serial port provided. Using default: {serial_port}") + + # Instantiate the driver + global antenna + + antenna = kogger_protocol_driver.KoggerSBPDevice(serial_port, DEFAULT_SERIAL_SPEED, default_timeout=0.02, log_level=LOG_LEVEL, log_file=LOG_FILE) + + try: + # Connect to the antenna + if not antenna.connect(): + print(f"Failed to connect to the antenna on port {serial_port}", file=sys.stderr) + sys.exit(1) + print(f"Successfully connected to the antenna on {serial_port}.") + # Perform a one-time check of all getter functions + check_all_getters(antenna) + + result = antenna.set_auto_response_filter(0) + logger.info("set_auto_response_filter(0)="+str(result)) + result = antenna.set_auto_response_timeout(0xffffffff) + logger.info("set_auto_response_timeout(0xffffffff)="+str(result)) + result = antenna.set_auto_response_payload(0xff) + logger.info("set_auto_response_payload(0xff)="+str(result)) + if slot_index!=0: + result = antenna.set_echo_filter(True) + logger.info("set_echo_filter(True)="+str(result)) + else: + result = antenna.set_echo_filter(False) + logger.info("set_echo_filter(False)="+str(result)) + + if slot_total!=0: + if slot_index!=0: + enable_delay=True + else: + enable_delay=False + result = antenna.set_sync_mode(slot_total, slot_index, slot_duration, enable_delay) + logger.info(f"set_sync_mode({slot_total},{slot_index},{slot_duration},{enable_delay})={result}") + if slot_index !=0: + antenna.register_precallback(kogger_protocol_driver.ID_USBL_SOLUTION, antenna.callback_usbl_solution) + antenna.register_callback(kogger_protocol_driver.ID_USBL_SOLUTION, test_callback) + antenna.register_default_callback(print_message) + + print("\nPress a number (0-9) to send an acoustic ping. Press 'q' to quit.") + + data_number = 255 + last_ping_time = 0 + str_star_moving_ping = ['|','/','−','\\'] + cnt_star_moving_ping = 0 + str_star = "" + + while True: + # Handle keyboard input + if is_data(): + char = sys.stdin.read(1) + if char.isdigit(): + data_number = int(char) + print(f"\r\n--> Switched data number to: {data_number}") + if slot_index==0: + result = antenna.set_auto_response_payload(data_number) + elif char.lower() == 'q': + print("\r\nQuitting...") + break + elif ord(char) == 3: # Ctrl+C + raise KeyboardInterrupt + + # Send ping every 1 second + current_time = time.time() + if current_time - last_ping_time >= 1: + last_ping_time = current_time + if slot_index!=0: + result = antenna.send_acoustic_ping(data_number) + if result == True: + cnt_star_moving_ping = (cnt_star_moving_ping+1)%4 + str_star = str_star_moving_ping[cnt_star_moving_ping] + else: + str_star = "!" + + # Update the prompt on the same line + prompt = f"\r"+str_star+"--> Pinging with "+str(data_number)+". Press 0-9+ENTER to change, 'q'+ENTER to quit." + sys.stdout.write(prompt) + sys.stdout.flush() + + time.sleep(0.1) + + except KeyboardInterrupt: + print("\nExiting...") + except Exception as e: + print(f"An error occurred: {e}", file=sys.stderr) + exc_type, exc_obj, exc_tb = sys.exc_info() + fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] + print(f"type={exc_type}, fname={fname}, line={exc_tb.tb_lineno}") + finally: + # Ensure the connection is closed gracefully + print("Disconnecting from the antenna.") + antenna.disconnect() + +if __name__ == "__main__": + main() diff --git a/driver/test/test_usv.py b/driver/test/test_usv.py new file mode 100755 index 0000000..264ac6f --- /dev/null +++ b/driver/test/test_usv.py @@ -0,0 +1,183 @@ +#! /usr/bin/env python +# Test to run USV. Will ping AUV, and wait for response. + +import time +import sys +import tty +import termios +import select +import json +import os +from loguru import logger +import datetime + +# Add the parent directory to the Python path to find the driver module +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +from kogger_protocol_driver import KoggerSBPDevice + +# --- Script Configuration --- +# Set the desired logging level: "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL" +LOG_LEVEL = "INFO" +timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") +LOG_FILE = "log/"+str(timestamp)+"_log_usv.log" +# Default serial port if not provided via command line +DEFAULT_SERIAL_PORT = "/dev/ttyAMA4" +#DEFAULT_SERIAL_PORT = "/dev/serial/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.3:1.0" +DEFAULT_SERIAL_SPEED = 921600 + +def print_message(message): + """ + This function is a callback that will be executed for each message + received from the antenna that is not a direct response to a command. + """ + parsed = json.loads(str(message).replace("nan", "'nan'").replace("'",'"')) + logger.info("printer:"+json.dumps(parsed, indent=2)) + +def is_data(): + """ + Checks if there is data to be read from stdin. + """ + return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []) + +def check_all_getters(antenna): + """ + Calls all get_* methods on the antenna object and prints the results. + """ + print("\n--- Checking all getter methods ---") + + # A list of all getter methods to call. + # Some methods require arguments, which are provided as tuples. + # (method_name, args_tuple) + getters_to_test = [ + #('get_timestamp', ()), + #('get_distance', (0,)), + #('get_distance', (1,)), + #('get_chart_data', ()), + #('get_attitude', (0,)), #!!OK + #('get_attitude', (1,)), + #('get_temperature', ()), + #('get_dataset_config', (0,)), + #('get_distance_setup', ()), + #('get_chart_setup', ()), + #('get_transceiver_settings', ()), + #('get_sound_speed', ()), + #('get_uart_config', (1, 0)), # uart_id=1, version=0 + #('get_uart_config', (1, 1)), # uart_id=1, version=1 + ('get_version_info', ()), #!! OK + #('get_mark_status', ()), #!! OK + #('get_diagnostics', ()), + #('get_navigation_data', ()), + #('get_dvl_velocity_data', ()), + #('get_signal_encoder_data', ()), + #('get_signal_decoder_data', ()), + #('get_auto_response_timeout', ()), + #('get_auto_response_filter', ()), + #('get_auto_response_payload', ()), + #('get_usbl_solution', ()), + ] + + for method_name, args in getters_to_test: + try: + method = getattr(antenna, method_name) + print(f"Calling {method_name}{args}...") + result = method(*args) + print(f"Result: {result}") + except Exception as e: + print(f"An error occurred while calling {method_name}: {e}") + print("-" * 20) + time.sleep(0.01) # Give the device a moment between commands + + print("--- Finished checking all getter methods ---\n") + + +def main(): + """ + Main function to connect to the Kogger antenna and listen for messages. + """ + global DEFAULT_SERIAL_PORT + global DEFAULT_SERIAL_SPEED + global LOG_LEVEL + global LOG_FILE + + # Determine the serial port to use + if len(sys.argv)>1: + if sys.argv[1].startswith("/dev/"): + DEFAULT_SERIAL_PORT = sys.argv[1] + serial_port = sys.argv[1] + else: + serial_port = "/dev/ttyUSB"+str(sys.argv[1]) + else: + print(DEFAULT_SERIAL_PORT) + serial_port = DEFAULT_SERIAL_PORT + print(f"No serial port provided. Using default: {serial_port}") + + # Instantiate the driver + antenna = KoggerSBPDevice(serial_port, DEFAULT_SERIAL_SPEED, default_timeout=0.02, log_level=LOG_LEVEL, log_file=LOG_FILE) + + try: + # Connect to the antenna + if not antenna.connect(): + print(f"Failed to connect to the antenna on port {serial_port}", file=sys.stderr) + sys.exit(1) + print(f"Successfully connected to the antenna on {serial_port}.") + # Perform a one-time check of all getter functions + check_all_getters(antenna) + + result = antenna.set_auto_response_filter(0) + logger.info("set_auto_response_filter(0)="+str(result)) + result = antenna.set_auto_response_timeout(0xffffffff) + logger.info("set_auto_response_timeout(0xffffffff)="+str(result)) + result = antenna.set_auto_response_payload(0xff) + logger.info("set_auto_response_payload(0xff)="+str(result)) + antenna.register_default_callback(print_message) + + print("\nPress a number (0-9) to send an acoustic ping. Press 'q' to quit.") + + data_number = 255 + last_ping_time = 0 + str_star_moving_ping = ['|','/','−','\\'] + cnt_star_moving_ping = 0 + str_star = "" + + while True: + # Handle keyboard input + if is_data(): + char = sys.stdin.read(1) + if char.isdigit(): + data_number = int(char) + print(f"\r\n--> Switched data number to: {data_number}") + elif char.lower() == 'q': + print("\r\nQuitting...") + break + elif ord(char) == 3: # Ctrl+C + raise KeyboardInterrupt + + # Send ping every 1 second + current_time = time.time() + if current_time - last_ping_time >= 1: + last_ping_time = current_time + result = antenna.send_acoustic_ping(data_number) + if result == True: + cnt_star_moving_ping = (cnt_star_moving_ping+1)%4 + str_star = str_star_moving_ping[cnt_star_moving_ping] + else: + str_star = "!" + + # Update the prompt on the same line + prompt = f"\r"+str_star+"--> Pinging with "+str(data_number)+". Press 0-9+ENTER to change, 'q'+ENTER to quit." + sys.stdout.write(prompt) + sys.stdout.flush() + + time.sleep(0.1) + + except KeyboardInterrupt: + print("\nExiting...") + except Exception as e: + print(f"An error occurred: {e}", file=sys.stderr) + finally: + # Ensure the connection is closed gracefully + print("Disconnecting from the antenna.") + antenna.disconnect() + +if __name__ == "__main__": + main() diff --git a/examples/auv_slave.py b/examples/auv_slave.py new file mode 100644 index 0000000..b75a937 --- /dev/null +++ b/examples/auv_slave.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +""" +Exemple : un AUV slave Kogger en mode transpondeur continu. + +Le master USV envoie des pings adressés. Cet AUV répond automatiquement, logge +chaque ping reçu (timestamp, SNR, distance hardware), et publie en parallèle +sur stdout. Aucun arming par ping requis. + +Lancement : + python3 auv_slave.py --port /dev/ttyUSB0 --address 2 --vehicle AUV-2 + +Pour intégrer dans un nœud ROS2 / NATS / MQTT, remplacer `on_ping` ci-dessous +par un publisher. +""" + +import argparse +import time +import sys + +sys.path.insert(0, "..") # rend transponder_continu.py importable depuis examples/ +from transponder_continu import ContinuousTransponder, SyncSlot + + +def main(): + p = argparse.ArgumentParser() + p.add_argument("--port", required=True) + p.add_argument("--address", type=int, required=True) + p.add_argument("--vehicle", default="AUV") + p.add_argument("--slot-total", type=int, default=0, + help="Si >0 : enrôle l'AUV dans un slot TDMA partagé.") + p.add_argument("--slot-duration", type=float, default=2.0) + args = p.parse_args() + + sync = None + if args.slot_total > 0: + sync = SyncSlot( + slot_total=args.slot_total, + slot_index=args.address - 1, # convention : slot index = address - 1 + slot_duration=args.slot_duration, + ) + + t = ContinuousTransponder( + port=args.port, + my_address=args.address, + vehicle_name=args.vehicle, + sync=sync, + watchdog_timeout_s=15.0, # re-arm si silence > 15 s (USV down ?) + ) + + pings_log = [] + + def on_ping(msg): + rec = { + "t_local": time.time(), + "id": msg.get("id"), + "snr": msg.get("snr"), + "distance_m": t.state.last_distance_m, + "raw": msg, + } + pings_log.append(rec) + print( + f"[{rec['t_local']:.3f}] ping #{t.state.pings_received} " + f"id={rec['id']} snr={rec['snr']} " + f"d={rec['distance_m']:.2f}m" if rec['distance_m'] else "d=—" + ) + + t.on_ping_received(on_ping) + + print(f"=== {args.vehicle} en transpondeur continu, address={args.address} ===") + t.start() + try: + t.run_forever() + finally: + t.stop() + print(f"\nFin de session. {len(pings_log)} pings reçus au total.") + + +if __name__ == "__main__": + main() diff --git a/transponder_continu.py b/transponder_continu.py new file mode 100644 index 0000000..371f449 --- /dev/null +++ b/transponder_continu.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python3 +""" +Kogger USBL — mode transpondeur continu. + +Wrapper de haut niveau autour de `kogger_protocol_driver.KoggerSBPDevice` qui +configure l'antenne acoustique Kogger comme un *slave transpondeur permanent* : +elle écoute en continu les pings adressés à son ID, répond automatiquement +(turnaround géré côté hardware Kogger), ignore les pings destinés aux autres, +et expose un callback Python pour chaque ping reçu. + +Pas de re-armement par ping. Pas de boucle d'interrogation. Une seule init, +puis le device répond tant que le process tourne. + +Usage minimal : + + from transponder_continu import ContinuousTransponder + + t = ContinuousTransponder(port="/dev/ttyUSB0", my_address=2, + vehicle_name="AUV-2") + t.on_ping_received(lambda solution: print("ping →", solution)) + t.start() + try: + t.run_forever() # bloque le main thread + finally: + t.stop() + +CLI : + + python3 transponder_continu.py --port /dev/ttyUSB0 --address 2 + +Conception : +- Aucune modification du driver Kogger d'origine. +- N'utilise que des méthodes publiques déjà présentes (`set_usbl_transponder`, + `set_usbl_request_address_filter`, `set_usbl_monitor_config`, `set_sync_mode`, + `register_callback`, `set_auto_response_filter`). +- Compose les bons appels dans le bon ordre, en exposant une API "1-shot config". +- Watchdog optionnel : ré-applique la config si le device a rebooté (détecté + par perte de réponse > `watchdog_timeout_s`). + +Auteur : Poulpe (2026-04-27) pour Flag. +Licence : même que driver upstream (cosma-tech/kogger_acousticAntenna). +""" + +from __future__ import annotations + +import argparse +import sys +import threading +import time +from dataclasses import dataclass, field +from typing import Callable, Optional + +# Le driver upstream est attendu adjacent (cf. README pour le clone). +sys.path.insert(0, "driver") +from kogger_protocol_driver import KoggerSBPDevice, ID_USBL_SOLUTION, ID_DIST + + +@dataclass +class SyncSlot: + """Configuration TDMA optionnelle si plusieurs slaves se partagent le canal.""" + slot_total: int # nombre total de slots dans un cycle + slot_index: int # mon index de slot (0-indexed) + slot_duration: float # durée d'un slot en secondes + + +@dataclass +class TransponderState: + last_ping_at: float = 0.0 + last_distance_m: Optional[float] = None + last_snr: Optional[float] = None + last_ping_id: Optional[int] = None + pings_received: int = 0 + + +class ContinuousTransponder: + """ + Configure une antenne Kogger en transpondeur permanent sur une adresse donnée. + + Args + ---- + port : port série (ex. "/dev/ttyUSB0") + my_address : ID de cette antenne, 1..7 (0 = promiscuous) + baudrate : baudrate UART, défaut 921600 (recommandé Kogger) + vehicle_name : étiquette utilisée dans les logs CSV + echo_filter_us : suppression des échos en µs, défaut 400 ms + sync : SyncSlot ou None. Si fourni, le device attend son slot. + watchdog_timeout_s: si > 0, ré-applique la config en cas de silence prolongé. + + Notes + ----- + Adresse Kogger autorisées en transpondeur : 1..7. La valeur 0 est promiscuous + (répond à tous), généralement à éviter en multi-AUV. La valeur 0xFF = + "slot désactivé" dans le filtre 8-byte. + + Côté master : envoyez `set_usbl_ping_request_direct(address=N, cmd_id=K, ...)` + où N est l'adresse de ce transpondeur. Le hardware Kogger gère automatiquement + le pong (turnaround_B) — vous n'avez rien à faire côté slave une fois `start()` + appelé. + """ + + def __init__( + self, + port: str, + my_address: int, + baudrate: int = 921600, + vehicle_name: Optional[str] = None, + echo_filter_us: int = 400_000, + sync: Optional[SyncSlot] = None, + watchdog_timeout_s: float = 0.0, + log_level: str = "INFO", + log_file: str = "", + ): + if not 1 <= my_address <= 7: + raise ValueError(f"my_address must be 1..7, got {my_address}") + + self._device = KoggerSBPDevice( + port=port, + baudrate=baudrate, + device_address=my_address, + vehicleName=vehicle_name, + log_level=log_level, + log_file=log_file, + ) + self._my_address = my_address + self._echo_filter_us = echo_filter_us + self._sync = sync + self._watchdog_timeout_s = watchdog_timeout_s + + self.state = TransponderState() + self._on_ping: Optional[Callable] = None + self._on_distance: Optional[Callable] = None + + self._stop_event = threading.Event() + self._watchdog_thread: Optional[threading.Thread] = None + + # ------------------------------------------------------------------ public + + def on_ping_received(self, fn: Callable[[dict], None]) -> None: + """Callback (message: dict) appelé pour chaque USBL_SOLUTION reçu.""" + self._on_ping = fn + + def on_distance(self, fn: Callable[[dict], None]) -> None: + """Callback (message: dict) appelé pour chaque ID_DIST reçu.""" + self._on_distance = fn + + def start(self) -> None: + """Connecte le device, applique la configuration transpondeur continu.""" + self._device.connect() + + # 1. Filtre d'adresses : seules les requêtes pour my_address déclenchent une + # réponse. Les 7 autres slots sont 0xFF (désactivé). + addresses = [self._my_address] + [0xFF] * 7 + self._device.set_usbl_request_address_filter(addresses[:8]) + + # 2. Filtre d'écho actif (évite de répondre à son propre écho réverbéré). + self._device.set_usbl_monitor_config( + enable=True, + echo_filter_response_us=self._echo_filter_us, + echo_filter_request_us=self._echo_filter_us, + ) + + # 3. Sync slot TDMA optionnel (utile en multi-slave pour éviter collisions). + if self._sync is not None: + self._device.set_sync_mode( + slot_total=self._sync.slot_total, + slot_index=self._sync.slot_index, + slot_duration=self._sync.slot_duration, + enable_delay=False, # False côté slave (le master ordonne) + ) + + # 4. Active la fenêtre de réponse permanente (timeout = 0xFFFFFFFF µs). + self._device.set_usbl_transponder(enable=True) + + # 5. Branche les callbacks Python sur les frames asynchrones. + self._device.register_callback(ID_USBL_SOLUTION, self._handle_usbl_solution) + self._device.register_callback(ID_DIST, self._handle_distance) + + # 6. Watchdog optionnel. + if self._watchdog_timeout_s > 0: + self._watchdog_thread = threading.Thread( + target=self._watchdog_loop, daemon=True, name="kogger-watchdog" + ) + self._watchdog_thread.start() + + def stop(self) -> None: + """Désactive le transpondeur, déconnecte proprement.""" + self._stop_event.set() + try: + self._device.set_usbl_transponder(enable=False) + except Exception: + pass # le device peut déjà être déconnecté + self._device.unregister_callback(ID_USBL_SOLUTION) + self._device.unregister_callback(ID_DIST) + self._device.disconnect() + + def run_forever(self, tick_s: float = 0.5) -> None: + """Bloque le main thread jusqu'à `stop()` ou KeyboardInterrupt.""" + try: + while not self._stop_event.is_set(): + time.sleep(tick_s) + except KeyboardInterrupt: + pass + + # ------------------------------------------------------------------ internals + + def _handle_usbl_solution(self, message: dict) -> None: + self.state.last_ping_at = time.time() + self.state.pings_received += 1 + self.state.last_ping_id = message.get("id") + self.state.last_snr = message.get("snr") + # Distance hardware-computed côté antenne, exposée dans l'USBL solution. + for k in ("distance_m", "slant_range_m", "distance"): + if k in message: + self.state.last_distance_m = message[k] + break + if self._on_ping: + try: + self._on_ping(message) + except Exception as e: + # On n'interrompt jamais le reader thread du driver pour une + # exception applicative. + print(f"[ContinuousTransponder] on_ping callback error: {e}") + + def _handle_distance(self, message: dict) -> None: + if self._on_distance: + try: + self._on_distance(message) + except Exception as e: + print(f"[ContinuousTransponder] on_distance callback error: {e}") + + def _watchdog_loop(self) -> None: + """Si on n'a rien reçu depuis `watchdog_timeout_s`, ré-applique la config.""" + while not self._stop_event.wait(self._watchdog_timeout_s / 2): + silent_for = time.time() - self.state.last_ping_at + if ( + self.state.last_ping_at > 0 + and silent_for > self._watchdog_timeout_s + ): + print( + f"[ContinuousTransponder] watchdog: silent {silent_for:.1f}s, " + f"re-applying config" + ) + try: + self._device.set_usbl_transponder(enable=True) + except Exception as e: + print(f"[ContinuousTransponder] watchdog re-arm failed: {e}") + + +# ----------------------------------------------------------------------- CLI + + +def _main() -> int: + p = argparse.ArgumentParser(description="Kogger USBL — transpondeur continu") + p.add_argument("--port", required=True, help="Port série (ex. /dev/ttyUSB0)") + p.add_argument("--address", type=int, required=True, help="Mon ID Kogger (1..7)") + p.add_argument("--baudrate", type=int, default=921600) + p.add_argument("--vehicle", default=None, help="Étiquette véhicule (logs CSV)") + p.add_argument("--echo-filter-us", type=int, default=400_000) + p.add_argument("--watchdog-s", type=float, default=0.0, + help="Re-arm si silence > X secondes (0 = off)") + p.add_argument("--slot-total", type=int, default=0) + p.add_argument("--slot-index", type=int, default=0) + p.add_argument("--slot-duration", type=float, default=0.0) + p.add_argument("--log-level", default="INFO") + args = p.parse_args() + + sync = None + if args.slot_total > 0 and args.slot_duration > 0: + sync = SyncSlot(args.slot_total, args.slot_index, args.slot_duration) + + t = ContinuousTransponder( + port=args.port, + my_address=args.address, + baudrate=args.baudrate, + vehicle_name=args.vehicle, + echo_filter_us=args.echo_filter_us, + sync=sync, + watchdog_timeout_s=args.watchdog_s, + log_level=args.log_level, + ) + + def on_ping(msg): + d = t.state.last_distance_m + d_str = f"{d:.2f} m" if d is not None else "—" + print( + f"ping #{t.state.pings_received} from id={msg.get('id')} " + f"snr={msg.get('snr')} dist={d_str}" + ) + + t.on_ping_received(on_ping) + print(f"Starting transponder address={args.address} on {args.port} (Ctrl-C pour arrêter)") + t.start() + try: + t.run_forever() + finally: + t.stop() + print(f"Stopped. Total pings received: {t.state.pings_received}") + return 0 + + +if __name__ == "__main__": + sys.exit(_main())