diff --git a/src/RSIPI/cli.py b/src/RSIPI/cli.py index 5dc7987..20f418b 100644 --- a/src/RSIPI/cli.py +++ b/src/RSIPI/cli.py @@ -59,6 +59,8 @@ class RSICommandLineInterface: self.adjust_speed(parts[1], parts[2]) elif cmd == "override" and len(parts) == 2: self.override_safety(parts[1]) + elif cmd == "log" and len(parts) >= 2: + self.handle_logging_command(parts) elif cmd == "exit": self.stop_rsi() self.running = False @@ -161,6 +163,22 @@ class RSICommandLineInterface: """Override safety limits.""" print(f"⚠️ Overriding safety limit: {limit}") + def handle_logging_command(self, parts): + """Handles logging-related commands.""" + subcmd = parts[1] + if subcmd == "start" and len(parts) == 3: + filename = parts[2] + self.client.start_logging(filename) + print(f"✅ Logging started: {filename}") + elif subcmd == "stop": + self.client.stop_logging() + print("🛑 Logging stopped.") + elif subcmd == "status": + status = "ON" if self.client.is_logging_active() else "OFF" + print(f"📊 Logging Status: {status}") + else: + print("❌ Invalid log command. Use 'log start .csv', 'log stop', or 'log status'.") + def show_help(self): """Displays the list of available commands.""" print(""" @@ -170,6 +188,7 @@ Available Commands: toggle <0/1>, move_external correct , speed override + log start .csv, log stop, log status """) if __name__ == "__main__": diff --git a/src/RSIPI/network_process.py b/src/RSIPI/network_process.py index 3e80969..7769c62 100644 --- a/src/RSIPI/network_process.py +++ b/src/RSIPI/network_process.py @@ -1,25 +1,15 @@ + import multiprocessing import socket import time +import csv import logging -from config_parser import ConfigParser -import sys -import os -import socket -import xml.etree.ElementTree as ET -import multiprocessing -from xml_handler import XMLGenerator # ✅ Import XML generator module -from config_parser import ConfigParser # ✅ Import the updated config parser - - -import multiprocessing -import socket -import logging +import xml.etree.ElementTree as ET # ✅ FIX: Import ElementTree from config_parser import ConfigParser from xml_handler import XMLGenerator class NetworkProcess(multiprocessing.Process): - """Handles UDP communication in a separate process.""" + """Handles UDP communication and optional CSV logging in a separate process.""" def __init__(self, ip, port, send_variables, receive_variables, stop_event, config_parser): super().__init__() @@ -29,14 +19,17 @@ class NetworkProcess(multiprocessing.Process): self.config_parser = config_parser self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - # ✅ Fallback IP handling self.client_address = (ip, port) + if not self.is_valid_ip(ip): logging.warning(f"Invalid IP address '{ip}' detected. Falling back to '0.0.0.0'.") print(f"⚠️ Invalid IP '{ip}', falling back to '0.0.0.0'.") self.client_address = ('0.0.0.0', port) else: self.client_address = (ip, port) + self.logging_active = multiprocessing.Value('b', False) # Shared flag for logging + self.log_filename = multiprocessing.Array('c', 256) # Shared memory for filename + self.csv_process = None try: self.udp_socket.bind(self.client_address) @@ -63,44 +56,74 @@ class NetworkProcess(multiprocessing.Process): print("[DEBUG] Network process started.") while not self.stop_event.is_set(): try: - print("[DEBUG] Waiting for incoming message...") self.udp_socket.settimeout(5) data_received, self.controller_ip_and_port = self.udp_socket.recvfrom(1024) message = data_received.decode() - print(f"[DEBUG] Received message: {message}") self.process_received_data(message) - - # ✅ Generate the send XML using the updated XMLGenerator send_xml = XMLGenerator.generate_send_xml(self.send_variables, self.config_parser.network_settings) - print(f"[DEBUG] Sending response: {send_xml}") - self.udp_socket.sendto(send_xml.encode(), self.controller_ip_and_port) + # ✅ If logging is active, write data to CSV + if self.logging_active.value: + self.log_to_csv() + except socket.timeout: print("[WARNING] No message received within timeout period.") except Exception as e: print(f"[ERROR] Network process error: {e}") - - def stop_network(self): - """Safely stop the network process.""" - if self.udp_socket: - self.udp_socket.close() - print("✅ Network socket closed.") - def process_received_data(self, xml_string): """Parse incoming XML and update shared variables.""" try: root = ET.fromstring(xml_string) for element in root: if element.tag in self.receive_variables: - if len(element.attrib) > 0: # ✅ Handle structured data (dictionaries) + if len(element.attrib) > 0: self.receive_variables[element.tag] = {k: float(v) for k, v in element.attrib.items()} else: self.receive_variables[element.tag] = element.text - - print(f"[DEBUG] Updated received variables: {self.receive_variables}") except Exception as e: print(f"[ERROR] Error parsing received message: {e}") + def log_to_csv(self): + """Write send/receive variables to the CSV log.""" + filename = self.log_filename.value.decode().strip() + if not filename: + return + + try: + with open(filename, mode="a", newline="") as file: + writer = csv.writer(file) + + # Write header if the file is new + if file.tell() == 0: + headers = ["Timestamp", "IPOC"] + headers += [f"Send.{k}" for k in self.send_variables.keys()] + headers += [f"Receive.{k}" for k in self.receive_variables.keys()] + writer.writerow(headers) + + # Write current data + timestamp = time.strftime("%d-%m-%Y %H:%M:%S") + ipoc = self.receive_variables.get("IPOC", 0) + send_data = [self.send_variables.get(k, "") for k in self.send_variables.keys()] + receive_data = [self.receive_variables.get(k, "") for k in self.receive_variables.keys()] + writer.writerow([timestamp, ipoc] + send_data + receive_data) + + except Exception as e: + print(f"[ERROR] Failed to log data to CSV: {e}") + + def start_logging(self, filename): + """Start logging RSI data to CSV.""" + self.logging_active.value = True + self.log_filename.value = filename.encode() + print(f"✅ CSV Logging started: {filename}") + + def stop_logging(self): + """Stop logging RSI data.""" + self.logging_active.value = False + print("🛑 CSV Logging stopped.") + + def is_logging_active(self): + """Return logging status.""" + return self.logging_active.value diff --git a/src/RSIPI/rsi_api.py b/src/RSIPI/rsi_api.py index 1cb5325..ce22787 100644 --- a/src/RSIPI/rsi_api.py +++ b/src/RSIPI/rsi_api.py @@ -76,4 +76,18 @@ class RSIAPI: "network": self.client.config_parser.get_network_settings(), "send_variables": dict(self.client.send_variables), "receive_variables": dict(self.client.receive_variables) - } \ No newline at end of file + } + + def start_logging(self, filename): + """Start logging RSI data to CSV.""" + self.client.start_logging(filename) + return f"✅ CSV Logging started: {filename}" + + def stop_logging(self): + """Stop logging RSI data.""" + self.client.stop_logging() + return "🛑 CSV Logging stopped." + + def is_logging_active(self): + """Return logging status.""" + return self.client.is_logging_active() \ No newline at end of file