Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import re
- import struct
- class S7String:
- def __init__(self, s: bytes | str, size: int = 254, is_wstring: bool = False):
- """
- Kapselt Siemens S7 STRINGS und WSTRINGS.
- - STRING (is_wstring=False): Max 254 Zeichen. Header: 2 Bytes (Max, Aktuell).
- - WSTRING (is_wstring=True): Max 16382 Zeichen. Header: 4 Bytes (Max, Aktuell als UInt16).
- """
- self.is_wstring = is_wstring
- self.char_size = 2 if is_wstring else 1
- self._encoding = "utf-16-be" if is_wstring else "ascii"
- max_limit = 16382 if is_wstring else 254
- if not 0 <= size <= max_limit:
- raise ValueError(f"Größe für {'W' if is_wstring else ''}STRING muss zwischen 0 und {max_limit} liegen.")
- match s:
- case str():
- encoded = s.encode(self._encoding)
- if len(encoded) // self.char_size > size:
- raise ValueError(f"String zu lang für die deklarierte Kapazität von {size} Zeichen.")
- header_size = 4 if is_wstring else 2
- self.buffer = bytearray(header_size + (size * self.char_size))
- self.size = size
- # Header schreiben
- if is_wstring:
- self.buffer[0:4] = struct.pack(">HH", size, len(encoded) // 2)
- else:
- self.buffer[0:2] = size, len(encoded)
- # Inhalt schreiben
- self.buffer[header_size : header_size + len(encoded)] = encoded
- case bytes():
- header_size = 4 if is_wstring else 2
- if len(s) < header_size:
- raise ValueError("Bytes müssen mindestens die Header-Bytes enthalten.")
- self.buffer = bytearray(s)
- if is_wstring:
- self.size = struct.unpack(">H", self.buffer[0:2])[0]
- else:
- self.size = self.buffer[0]
- def __len__(self):
- """Gibt die maximal deklarierte Kapazität zurück."""
- return self.size
- @property
- def slen(self):
- """Gibt die aktuelle Textlänge in Zeichen zurück."""
- if self.is_wstring:
- return struct.unpack(">H", self.buffer[2:4])[0]
- return self.buffer[1]
- def set(self, s: str):
- """Überschreibt den String-Inhalt sicher."""
- encoded = s.encode(self._encoding)
- if len(encoded) // self.char_size > self.size:
- raise ValueError(f"String ist zu lang für die Puffergröße von {self.size}")
- self.clear()
- header_size = 4 if self.is_wstring else 2
- if self.is_wstring:
- self.buffer[2:4] = struct.pack(">H", len(encoded) // 2)
- else:
- self.buffer[1] = len(encoded)
- self.buffer[header_size : header_size + len(encoded)] = encoded
- def clear(self):
- """Setzt die aktuelle Länge auf 0 und nullt den Datenbereich."""
- header_size = 4 if self.is_wstring else 2
- if self.is_wstring:
- self.buffer[2:4] = b"\x00\x00"
- else:
- self.buffer[1] = 0
- self.buffer[header_size:] = b"\x00" * (self.size * self.char_size)
- def __str__(self):
- """Konvertiert in einen nativen Python-String."""
- header_size = 4 if self.is_wstring else 2
- actual_bytes = self.slen * self.char_size
- return self.buffer[header_size : header_size + actual_bytes].decode(self._encoding)
- def __bytes__(self):
- """Gibt das vollständige S7-Byte-Array zurück."""
- return bytes(self.buffer)
- @classmethod
- def from_bytes(cls, raw_bytes: bytes, is_wstring: bool = False):
- """Factory-Methode: Erzeugt ein S7String-Objekt aus den von struct.unpack kommenden Rohdaten."""
- return cls(raw_bytes, is_wstring=is_wstring)
- def compile_s7_format(fmt: str) -> str:
- """
- Kompiliert S7-Formate inklusive Siemens-Strings (S=STRING, W=WSTRING)
- in standardkonforme Python struct-Formatstrings unter Beachtung des S7 Natural Alignments.
- """
- s7_fmt = ">"
- current_size = 0
- for match in re.finditer(r"(\d*)([a-zA-Z])", fmt):
- count_str, dtype = match.groups()
- count = int(count_str) if count_str else 1
- # 1. Füllbytes (x) direkt anhängen
- if dtype == "x":
- s7_fmt += f"{count}x"
- current_size += count
- continue
- # 2. Siemens STRINGS (S) und WSTRINGS (W)
- if dtype in ("S", "W"):
- is_wstring = (dtype == "W")
- char_size = 2 if is_wstring else 1
- header_size = 4 if is_wstring else 2
- total_bytes = header_size + (count * char_size)
- ssize = 2 if is_wstring else 1 # WSTRING richtet sich Wortweise (2 Bytes) aus
- if (remainder := current_size % ssize) != 0:
- padding = ssize - remainder
- s7_fmt += f"{padding}x"
- current_size += padding
- s7_fmt += f"{total_bytes}s"
- current_size += total_bytes
- continue
- # 3. Standard-Datentypen
- ssize = struct.calcsize(f">{dtype}")
- if (remainder := current_size % ssize) != 0:
- padding_bytes = ssize - remainder
- s7_fmt += f"{padding_bytes}x"
- current_size += padding_bytes
- s7_fmt += f"{count_str}{dtype}"
- current_size += ssize * count
- return s7_fmt
- # S7-Strukturdefinition (z.B. aus einem globalen DB):
- # 1 Byte (B), 1 normaler STRING mit max 10 Zeichen (10S), 1 Real/Float (f), 1 WSTRING mit max 5 Zeichen (5W)
- s7_schema = "b 10S f 5W"
- # 1. Format-String einmalig für das Projekt kompilieren
- python_fmt = compile_s7_format(s7_schema)
- print(f"Kompiliertes Python-Format: {python_fmt}\n")
- # Erwartete Ausgabe enthält automatisch berechnete Füllbytes (x), um das Alignment zu halten
- # ==============================================================================
- # LEGENDE 1: DATEN PACKEN (Senden zur SPS)
- # ==============================================================================
- my_string = S7String("Siemens", size=10, is_wstring=False)
- my_wstring = S7String("S7-15", size=5, is_wstring=True)
- # Datentupel vorbereiten. Die S7String-Objekte müssen per bytes() übergeben werden.
- werte_zum_senden = (42, bytes(my_string), 123.45, bytes(my_wstring))
- packed_bytes = struct.pack(python_fmt, *werte_zum_senden)
- print("SPS-Sende-Payload (Hex):", packed_bytes.hex())
- # ==============================================================================
- # LEGENDE 2: DATEN ENTPACKEN (Empfangen von SPS via struct.unpack)
- # ==============================================================================
- # Wir simulieren den Empfang der 'packed_bytes' und jagen sie durch unpack
- unpacked_raw = struct.unpack(python_fmt, packed_bytes)
- # Die entpackten Felder den Python-Variablen zuordnen
- s7_byte_val = unpacked_raw[0]
- s7_string_obj = S7String.from_bytes(unpacked_raw[1], is_wstring=False)
- s7_float_val = unpacked_raw[2]
- s7_wstring_obj = S7String.from_bytes(unpacked_raw[3], is_wstring=True)
- print("\n--- Ausgelesene SPS-Daten ---")
- print(f"Byte-Wert: {s7_byte_val}")
- print(f"STRING (Max={len(s7_string_obj)}, Aktuell={s7_string_obj.slen}): '{s7_string_obj}'")
- print(f"Float-Wert: {s7_float_val:.2f}")
- print(f"WSTRING (Max={len(s7_wstring_obj)}, Aktuell={s7_wstring_obj.slen}): '{s7_wstring_obj}'")
- raise SystemExit("2. Teil nur mit sps")
- import struct
- import time
- import snap7
- from snap7.util import *
- # --- [Hier die S7String-Klasse und compile_s7_format einfügen] ---
- # (Aus der vorherigen Nachricht übernehmen)
- # 1. Konfiguration der SPS-Verbindung und Datenstruktur
- PLC_IP = "192.168.0.1"
- PLC_RACK = 0
- PLC_SLOT = 1 # Bei S7-1200 meist 0, bei S7-1500 meist 1
- DB_NUMBER = 10 # Die Nummer des Datenbausteins (DB10)
- # Unsere bekannte SPS-Struktur: 1 Byte, 1 String (max 12), 1 Füllbyte, 1 Real (Float)
- S7_STRUCTURE = "B 12S x f"
- python_format = compile_s7_format(S7_STRUCTURE)
- BUFFER_SIZE = struct.calcsize(python_format)
- print(f"Kompiliertes Format: {python_format}")
- print(f"Erwartete Bytegröße aus DB: {BUFFER_SIZE} Bytes\n")
- # 2. Verbindung zur SPS aufbauen
- client = snap7.client.Client()
- try:
- client.connect(PLC_IP, PLC_RACK, PLC_SLOT)
- print(f"Erfolgreich mit S7-SPS ({PLC_IP}) verbunden.")
- # ==========================================
- # TEIL 1: DATEN AUS DEM DB LESEN
- # ==========================================
- print("\n--- Lese Daten aus DB ---")
- # Liest den gesamten DB-Bereich ab Offset 0 in ein Bytearray
- raw_bytes = client.db_read(DB_NUMBER, 0, BUFFER_SIZE)
- # Rohe Bytes entpacken
- unpacked_list = struct.unpack(python_format, raw_bytes)
- data_iter = iter(unpacked_list)
- # Den Werten Variablen zuweisen (Reihenfolge muss exakt stimmen!)
- res_byte = next(data_iter)
- res_string = S7String.from_struct_unpack(
- max_len=next(data_iter),
- act_len=next(data_iter),
- payload_bytes=next(data_iter)
- )
- res_float = next(data_iter)
- print(f"Aktueller Wert Byte: {res_byte}")
- print(f"Aktueller Wert String: '{res_string}'")
- print(f"Aktueller Wert Float: {res_float}")
- # ==========================================
- # TEIL 2: DATEN MODIFIZIEREN UND SCHREIBEN
- # ==========================================
- print("\n--- Schreibe neue Daten in DB ---")
- # Neue Werte definieren
- neues_byte = 105
- res_string.set("SPS laeuft!") # Nutzt die sichere .set() Methode der Klasse
- neuer_float = 45.67
- # Payload für struct.pack vorbereiten
- payload = (neues_byte, *res_string.to_struct_args(), neuer_float)
- # Daten packen (erzeugt das perfekt ausgerichtete Big-Endian Byte-Paket)
- send_bytes = struct.pack(python_format, *payload)
- # Bytes zurück in die SPS schreiben (DB_NUMBER, Start-Offset, Daten)
- client.db_write(DB_NUMBER, 0, send_bytes)
- print("Neue Daten erfolgreich in die SPS übertragen.")
- except Exception as e:
- print(f"Fehler bei der SPS-Kommunikation: {e}")
- finally:
- # Verbindung sauber trennen
- if client.get_connected():
- client.disconnect()
- print("\nVerbindung zur SPS getrennt.")
Advertisement
Add Comment
Please, Sign In to add comment