DeaD_EyE

compile_s7_format2

Aug 23rd, 2026
6,157
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.32 KB | None | 0 0
  1. import re
  2. import struct
  3.  
  4. class S7String:
  5.     def __init__(self, s: bytes | str, size: int = 254, is_wstring: bool = False):
  6.         """
  7.        Kapselt Siemens S7 STRINGS und WSTRINGS.
  8.        - STRING (is_wstring=False): Max 254 Zeichen. Header: 2 Bytes (Max, Aktuell).
  9.        - WSTRING (is_wstring=True): Max 16382 Zeichen. Header: 4 Bytes (Max, Aktuell als UInt16).
  10.        """
  11.         self.is_wstring = is_wstring
  12.         self.char_size = 2 if is_wstring else 1
  13.         self._encoding = "utf-16-be" if is_wstring else "ascii"
  14.        
  15.         max_limit = 16382 if is_wstring else 254
  16.         if not 0 <= size <= max_limit:
  17.             raise ValueError(f"Größe für {'W' if is_wstring else ''}STRING muss zwischen 0 und {max_limit} liegen.")
  18.  
  19.         match s:
  20.             case str():
  21.                 encoded = s.encode(self._encoding)
  22.                 if len(encoded) // self.char_size > size:
  23.                     raise ValueError(f"String zu lang für die deklarierte Kapazität von {size} Zeichen.")
  24.  
  25.                 header_size = 4 if is_wstring else 2
  26.                 self.buffer = bytearray(header_size + (size * self.char_size))
  27.                 self.size = size
  28.                
  29.                 # Header schreiben
  30.                 if is_wstring:
  31.                     self.buffer[0:4] = struct.pack(">HH", size, len(encoded) // 2)
  32.                 else:
  33.                     self.buffer[0:2] = size, len(encoded)
  34.                    
  35.                 # Inhalt schreiben
  36.                 self.buffer[header_size : header_size + len(encoded)] = encoded
  37.  
  38.             case bytes():
  39.                 header_size = 4 if is_wstring else 2
  40.                 if len(s) < header_size:
  41.                     raise ValueError("Bytes müssen mindestens die Header-Bytes enthalten.")
  42.                 self.buffer = bytearray(s)
  43.                 if is_wstring:
  44.                     self.size = struct.unpack(">H", self.buffer[0:2])[0]
  45.                 else:
  46.                     self.size = self.buffer[0]
  47.  
  48.     def __len__(self):
  49.         """Gibt die maximal deklarierte Kapazität zurück."""
  50.         return self.size
  51.  
  52.     @property
  53.     def slen(self):
  54.         """Gibt die aktuelle Textlänge in Zeichen zurück."""
  55.         if self.is_wstring:
  56.             return struct.unpack(">H", self.buffer[2:4])[0]
  57.         return self.buffer[1]
  58.  
  59.     def set(self, s: str):
  60.         """Überschreibt den String-Inhalt sicher."""
  61.         encoded = s.encode(self._encoding)
  62.         if len(encoded) // self.char_size > self.size:
  63.             raise ValueError(f"String ist zu lang für die Puffergröße von {self.size}")
  64.            
  65.         self.clear()
  66.         header_size = 4 if self.is_wstring else 2
  67.        
  68.         if self.is_wstring:
  69.             self.buffer[2:4] = struct.pack(">H", len(encoded) // 2)
  70.         else:
  71.             self.buffer[1] = len(encoded)
  72.            
  73.         self.buffer[header_size : header_size + len(encoded)] = encoded
  74.  
  75.     def clear(self):
  76.         """Setzt die aktuelle Länge auf 0 und nullt den Datenbereich."""
  77.         header_size = 4 if self.is_wstring else 2
  78.         if self.is_wstring:
  79.             self.buffer[2:4] = b"\x00\x00"
  80.         else:
  81.             self.buffer[1] = 0
  82.         self.buffer[header_size:] = b"\x00" * (self.size * self.char_size)
  83.  
  84.     def __str__(self):
  85.         """Konvertiert in einen nativen Python-String."""
  86.         header_size = 4 if self.is_wstring else 2
  87.         actual_bytes = self.slen * self.char_size
  88.         return self.buffer[header_size : header_size + actual_bytes].decode(self._encoding)
  89.  
  90.     def __bytes__(self):
  91.         """Gibt das vollständige S7-Byte-Array zurück."""
  92.         return bytes(self.buffer)
  93.  
  94.     @classmethod
  95.     def from_bytes(cls, raw_bytes: bytes, is_wstring: bool = False):
  96.         """Factory-Methode: Erzeugt ein S7String-Objekt aus den von struct.unpack kommenden Rohdaten."""
  97.         return cls(raw_bytes, is_wstring=is_wstring)
  98.  
  99.  
  100. def compile_s7_format(fmt: str) -> str:
  101.     """
  102.    Kompiliert S7-Formate inklusive Siemens-Strings (S=STRING, W=WSTRING)
  103.    in standardkonforme Python struct-Formatstrings unter Beachtung des S7 Natural Alignments.
  104.    """
  105.     s7_fmt = ">"
  106.     current_size = 0
  107.  
  108.     for match in re.finditer(r"(\d*)([a-zA-Z])", fmt):
  109.         count_str, dtype = match.groups()
  110.         count = int(count_str) if count_str else 1
  111.        
  112.         # 1. Füllbytes (x) direkt anhängen
  113.         if dtype == "x":
  114.             s7_fmt += f"{count}x"
  115.             current_size += count
  116.             continue
  117.  
  118.         # 2. Siemens STRINGS (S) und WSTRINGS (W)
  119.         if dtype in ("S", "W"):
  120.             is_wstring = (dtype == "W")
  121.             char_size = 2 if is_wstring else 1
  122.             header_size = 4 if is_wstring else 2
  123.            
  124.             total_bytes = header_size + (count * char_size)
  125.             ssize = 2 if is_wstring else 1  # WSTRING richtet sich Wortweise (2 Bytes) aus
  126.            
  127.             if (remainder := current_size % ssize) != 0:
  128.                 padding = ssize - remainder
  129.                 s7_fmt += f"{padding}x"
  130.                 current_size += padding
  131.                
  132.             s7_fmt += f"{total_bytes}s"
  133.             current_size += total_bytes
  134.             continue
  135.  
  136.         # 3. Standard-Datentypen
  137.         ssize = struct.calcsize(f">{dtype}")
  138.         if (remainder := current_size % ssize) != 0:
  139.             padding_bytes = ssize - remainder
  140.             s7_fmt += f"{padding_bytes}x"
  141.             current_size += padding_bytes
  142.  
  143.         s7_fmt += f"{count_str}{dtype}"
  144.         current_size += ssize * count
  145.  
  146.     return s7_fmt
  147.  
  148.  
  149.  
  150. # S7-Strukturdefinition (z.B. aus einem globalen DB):
  151. # 1 Byte (B), 1 normaler STRING mit max 10 Zeichen (10S), 1 Real/Float (f), 1 WSTRING mit max 5 Zeichen (5W)
  152. s7_schema = "b 10S f 5W"
  153.  
  154. # 1. Format-String einmalig für das Projekt kompilieren
  155. python_fmt = compile_s7_format(s7_schema)
  156. print(f"Kompiliertes Python-Format: {python_fmt}\n")
  157. # Erwartete Ausgabe enthält automatisch berechnete Füllbytes (x), um das Alignment zu halten
  158.  
  159. # ==============================================================================
  160. # LEGENDE 1: DATEN PACKEN (Senden zur SPS)
  161. # ==============================================================================
  162. my_string = S7String("Siemens", size=10, is_wstring=False)
  163. my_wstring = S7String("S7-15", size=5, is_wstring=True)
  164.  
  165. # Datentupel vorbereiten. Die S7String-Objekte müssen per bytes() übergeben werden.
  166. werte_zum_senden = (42, bytes(my_string), 123.45, bytes(my_wstring))
  167.  
  168. packed_bytes = struct.pack(python_fmt, *werte_zum_senden)
  169. print("SPS-Sende-Payload (Hex):", packed_bytes.hex())
  170.  
  171. # ==============================================================================
  172. # LEGENDE 2: DATEN ENTPACKEN (Empfangen von SPS via struct.unpack)
  173. # ==============================================================================
  174. # Wir simulieren den Empfang der 'packed_bytes' und jagen sie durch unpack
  175. unpacked_raw = struct.unpack(python_fmt, packed_bytes)
  176.  
  177. # Die entpackten Felder den Python-Variablen zuordnen
  178. s7_byte_val = unpacked_raw[0]
  179. s7_string_obj = S7String.from_bytes(unpacked_raw[1], is_wstring=False)
  180. s7_float_val = unpacked_raw[2]
  181. s7_wstring_obj = S7String.from_bytes(unpacked_raw[3], is_wstring=True)
  182.  
  183. print("\n--- Ausgelesene SPS-Daten ---")
  184. print(f"Byte-Wert: {s7_byte_val}")
  185. print(f"STRING (Max={len(s7_string_obj)}, Aktuell={s7_string_obj.slen}): '{s7_string_obj}'")
  186. print(f"Float-Wert: {s7_float_val:.2f}")
  187. print(f"WSTRING (Max={len(s7_wstring_obj)}, Aktuell={s7_wstring_obj.slen}): '{s7_wstring_obj}'")
  188.  
  189.  
  190. raise SystemExit("2. Teil nur mit sps")
  191.  
  192. import struct
  193. import time
  194. import snap7
  195. from snap7.util import *
  196.  
  197. # --- [Hier die S7String-Klasse und compile_s7_format einfügen] ---
  198. # (Aus der vorherigen Nachricht übernehmen)
  199.  
  200. # 1. Konfiguration der SPS-Verbindung und Datenstruktur
  201. PLC_IP = "192.168.0.1"
  202. PLC_RACK = 0
  203. PLC_SLOT = 1  # Bei S7-1200 meist 0, bei S7-1500 meist 1
  204. DB_NUMBER = 10  # Die Nummer des Datenbausteins (DB10)
  205.  
  206. # Unsere bekannte SPS-Struktur: 1 Byte, 1 String (max 12), 1 Füllbyte, 1 Real (Float)
  207. S7_STRUCTURE = "B 12S x f"
  208. python_format = compile_s7_format(S7_STRUCTURE)
  209. BUFFER_SIZE = struct.calcsize(python_format)
  210.  
  211. print(f"Kompiliertes Format: {python_format}")
  212. print(f"Erwartete Bytegröße aus DB: {BUFFER_SIZE} Bytes\n")
  213.  
  214. # 2. Verbindung zur SPS aufbauen
  215. client = snap7.client.Client()
  216. try:
  217.     client.connect(PLC_IP, PLC_RACK, PLC_SLOT)
  218.     print(f"Erfolgreich mit S7-SPS ({PLC_IP}) verbunden.")
  219.  
  220.     # ==========================================
  221.     # TEIL 1: DATEN AUS DEM DB LESEN
  222.     # ==========================================
  223.     print("\n--- Lese Daten aus DB ---")
  224.     # Liest den gesamten DB-Bereich ab Offset 0 in ein Bytearray
  225.     raw_bytes = client.db_read(DB_NUMBER, 0, BUFFER_SIZE)
  226.    
  227.     # Rohe Bytes entpacken
  228.     unpacked_list = struct.unpack(python_format, raw_bytes)
  229.     data_iter = iter(unpacked_list)
  230.    
  231.     # Den Werten Variablen zuweisen (Reihenfolge muss exakt stimmen!)
  232.     res_byte = next(data_iter)
  233.     res_string = S7String.from_struct_unpack(
  234.         max_len=next(data_iter),
  235.         act_len=next(data_iter),
  236.         payload_bytes=next(data_iter)
  237.     )
  238.     res_float = next(data_iter)
  239.    
  240.     print(f"Aktueller Wert Byte:   {res_byte}")
  241.     print(f"Aktueller Wert String: '{res_string}'")
  242.     print(f"Aktueller Wert Float:  {res_float}")
  243.  
  244.     # ==========================================
  245.     # TEIL 2: DATEN MODIFIZIEREN UND SCHREIBEN
  246.     # ==========================================
  247.     print("\n--- Schreibe neue Daten in DB ---")
  248.    
  249.     # Neue Werte definieren
  250.     neues_byte = 105
  251.     res_string.set("SPS laeuft!")  # Nutzt die sichere .set() Methode der Klasse
  252.     neuer_float = 45.67
  253.    
  254.     # Payload für struct.pack vorbereiten
  255.     payload = (neues_byte, *res_string.to_struct_args(), neuer_float)
  256.    
  257.     # Daten packen (erzeugt das perfekt ausgerichtete Big-Endian Byte-Paket)
  258.     send_bytes = struct.pack(python_format, *payload)
  259.    
  260.     # Bytes zurück in die SPS schreiben (DB_NUMBER, Start-Offset, Daten)
  261.     client.db_write(DB_NUMBER, 0, send_bytes)
  262.     print("Neue Daten erfolgreich in die SPS übertragen.")
  263.  
  264. except Exception as e:
  265.     print(f"Fehler bei der SPS-Kommunikation: {e}")
  266.  
  267. finally:
  268.     # Verbindung sauber trennen
  269.     if client.get_connected():
  270.         client.disconnect()
  271.         print("\nVerbindung zur SPS getrennt.")
  272.  
Advertisement
Add Comment
Please, Sign In to add comment