1+ # ==================== PYMODBUS (RTU/TCP) ====================
2+
3+ import csv
4+ import time
5+ import os
6+ from datetime import datetime
7+ from pymodbus .client import ModbusTcpClient , ModbusSerialClient
8+
9+ # ==================== CONFIGURATION ====================
10+
11+ # Choose Modbus Type: "tcp" for Ethernet, "rtu" for Serial
12+ MODBUS_TYPE = "rtu" # Change to "rtu" if using Serial (RS-485)
13+
14+ # Modbus TCP (Ethernet) Configuration
15+ PLC_IP = "192.168.1.10" # Change to your PLC's IP Address
16+ PLC_PORT = 502 # Default Modbus TCP Port
17+
18+ # Modbus RTU (Serial) Configuration RS-485
19+ SERIAL_PORT = "COM3" # Windows: COM3, Linux: "/dev/ttyUSB0"
20+ BAUDRATE = 9600 # Adjust based on PLC settings
21+ PARITY = "O" # None (N), Even (E), or Odd (O)
22+ STOPBITS = 1
23+ BYTESIZE = 8
24+ TIMEOUT = 1
25+
26+ # Modbus Register Settings
27+ REGISTER_ADDRESS = 0x6304 # Change based on PLC's register map
28+ REGISTER_COUNT = 1 # Number of registers to read
29+ UNIT_ID = 1 # Usually 1 for a single PLC
30+
31+ # CSV File Name
32+ CSV_FILE = "plc_data.csv"
33+
34+ # Data Logging Interval (Seconds)
35+ LOG_INTERVAL = 5 # Change to your desired logging frequency
36+
37+ # Max reconnection attempts
38+ MAX_RECONNECT_ATTEMPTS = 3
39+
40+ # ==================== CONNECT TO PLC ====================
41+
42+ def connect_to_plc ():
43+ """Connects to the PLC using either Modbus TCP or Modbus RTU."""
44+ try :
45+ if MODBUS_TYPE == "tcp" :
46+ client = ModbusTcpClient (PLC_IP , port = PLC_PORT )
47+ else :
48+ # Updated for newer pymodbus versions
49+ client = ModbusSerialClient (
50+ port = SERIAL_PORT ,
51+ baudrate = BAUDRATE ,
52+ parity = PARITY ,
53+ stopbits = STOPBITS ,
54+ bytesize = BYTESIZE ,
55+ timeout = TIMEOUT ,
56+ )
57+
58+ if client .connect ():
59+ print (f"✅ Connected to PLC via Modbus { MODBUS_TYPE .upper ()} " )
60+ return client
61+ else :
62+ print ("❌ Failed to connect to PLC" )
63+ return None
64+
65+ except Exception as e :
66+ print (f"❌ Exception while connecting to PLC: { str (e )} " )
67+ return None
68+
69+ # ==================== READ DATA FROM PLC ====================
70+
71+ def read_plc_data (client ):
72+ """Reads data from PLC coils."""
73+ try :
74+ response = client .read_coils (address = 6304 ,count = 1 ) # Read 1 coil at address 6304
75+
76+ if response .isError ():
77+ print (f"❌ Modbus Error: { response } " )
78+ return None
79+ else :
80+ return response .bits # ✅ Correct attribute for coils (True/False list)
81+
82+ except Exception as e :
83+ print (f"❌ Exception while reading PLC: { str (e )} " )
84+ return None
85+
86+
87+ # ==================== SAVE DATA TO CSV ====================
88+
89+ def initialize_csv ():
90+ """Creates a new CSV file with headers if it doesn't exist."""
91+ if not os .path .exists (CSV_FILE ):
92+ with open (CSV_FILE , mode = "w" , newline = "" ) as file :
93+ writer = csv .writer (file )
94+ headers = ["Timestamp" ] + [f"Register_{ REGISTER_ADDRESS + i } " for i in range (REGISTER_COUNT )]
95+ writer .writerow (headers )
96+ print (f"✅ Created new CSV file: { CSV_FILE } " )
97+
98+ def save_to_csv (data ):
99+ """Saves PLC data to a CSV file with timestamps."""
100+ if data is None :
101+ print ("⚠️ No data to save" )
102+ return
103+
104+ timestamp = datetime .now ().strftime ("%Y-%m-%d %H:%M:%S" )
105+
106+ # Open CSV in append mode
107+ with open (CSV_FILE , mode = "a" , newline = "" ) as file :
108+ writer = csv .writer (file )
109+ writer .writerow ([timestamp ] + data )
110+
111+ print (f"✅ Data logged at { timestamp } : { data } " )
112+
113+ # ==================== MAIN LOOP ====================
114+
115+ def main ():
116+ """Continuously reads and logs PLC data."""
117+ # Initialize CSV file with headers
118+ initialize_csv ()
119+
120+ # Connect to PLC
121+ client = connect_to_plc ()
122+ if client is None :
123+ retry_count = 0
124+ while client is None and retry_count < MAX_RECONNECT_ATTEMPTS :
125+ print (f"Retrying connection ({ retry_count + 1 } /{ MAX_RECONNECT_ATTEMPTS } )..." )
126+ time .sleep (5 ) # Wait before retry
127+ retry_count += 1
128+ client = connect_to_plc ()
129+
130+ if client is None :
131+ print ("Failed to connect after multiple attempts. Exiting." )
132+ return
133+
134+ print ("📡 Starting data logging... (Press CTRL+C to stop)" )
135+
136+ try :
137+ reconnect_count = 0
138+ while True :
139+ data = read_plc_data (client )
140+
141+ # Handle connection loss during operation
142+ if data is None and reconnect_count < MAX_RECONNECT_ATTEMPTS :
143+ print (f"Connection may be lost. Attempting to reconnect ({ reconnect_count + 1 } /{ MAX_RECONNECT_ATTEMPTS } )..." )
144+ client .close ()
145+ time .sleep (2 )
146+ client = connect_to_plc ()
147+ reconnect_count += 1
148+ if client is None :
149+ continue
150+ elif data is None :
151+ print ("Too many failed read attempts. Exiting." )
152+ break
153+ else :
154+ reconnect_count = 0 # Reset counter on successful read
155+
156+ save_to_csv (data )
157+ time .sleep (LOG_INTERVAL ) # Wait before next read
158+
159+ except KeyboardInterrupt :
160+ print ("\n 🛑 Stopping data logging..." )
161+ finally :
162+ if client is not None :
163+ client .close ()
164+ print ("Connection closed. Exiting." )
165+
166+ if __name__ == "__main__" :
167+ main ()
0 commit comments