190 lines
5.9 KiB
Python
190 lines
5.9 KiB
Python
import snap7
|
|
import struct
|
|
import time
|
|
import os
|
|
import json
|
|
import logging
|
|
import threading
|
|
from snap7.util import get_bool, get_dint, get_real, get_string
|
|
|
|
# Configurazione Logging
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
|
|
logger = logging.getLogger("PLCClientWeb")
|
|
|
|
class SiemensClient:
|
|
def __init__(self, config_path="config.json"):
|
|
self.config_path = config_path
|
|
self.load_config()
|
|
self.client = snap7.client.Client()
|
|
self.connected = False
|
|
self.last_data = {}
|
|
self.lock = threading.Lock()
|
|
|
|
def load_config(self):
|
|
with open(self.config_path, 'r') as f:
|
|
self.config = json.load(f)
|
|
self.ip = self.config["PLC"]["IP"]
|
|
self.rack = self.config["PLC"]["RACK"]
|
|
self.slot = self.config["PLC"]["SLOT"]
|
|
self.reconnect_delay = self.config["PLC"].get("RECONNECT_DELAY", 5.0)
|
|
|
|
def connect(self):
|
|
try:
|
|
self.client.connect(self.ip, self.rack, self.slot)
|
|
self.connected = True
|
|
logger.info(f"Connesso a {self.ip}")
|
|
return True
|
|
except Exception as e:
|
|
self.connected = False
|
|
logger.error(f"Errore di connessione a {self.ip}: {e}")
|
|
return False
|
|
|
|
def disconnect(self):
|
|
try:
|
|
self.client.disconnect()
|
|
except:
|
|
pass
|
|
self.connected = False
|
|
logger.info("Client disconnesso.")
|
|
|
|
def read_area(self, area_key):
|
|
if not self.connected:
|
|
return None, 0
|
|
|
|
area_cfg = self.config["MEMORIES"].get(area_key)
|
|
if not area_cfg:
|
|
return None, 0
|
|
|
|
db_id = area_cfg["id"]
|
|
size = area_cfg["size"]
|
|
|
|
try:
|
|
start_time = time.perf_counter()
|
|
data = self.client.db_read(db_id, 0, size)
|
|
duration = (time.perf_counter() - start_time) * 1000
|
|
return data, duration
|
|
except Exception as e:
|
|
logger.warning(f"Errore lettura DB{db_id}: {e}")
|
|
self.connected = False
|
|
return None, 0
|
|
|
|
def fetch_all_data(self):
|
|
"""Legge tutte le aree configurate e le decodifica."""
|
|
results = {}
|
|
|
|
# DB100
|
|
data, dur = self.read_area("db100")
|
|
if data:
|
|
cfg = self.config["MEMORIES"]["db100"]
|
|
params = {}
|
|
for p in cfg["parameters"]:
|
|
if p["type"] == "real":
|
|
params[p["name"]] = get_real(data, p["offset"])
|
|
elif p["type"] == "dint":
|
|
params[p["name"]] = get_dint(data, p["offset"])
|
|
|
|
strings = {}
|
|
for s in cfg["strings"]:
|
|
strings[s["name"]] = get_string(data, s["offset"], s["size"])
|
|
|
|
# Status bits (DBD0)
|
|
status_bytes = data[0:4]
|
|
acquisizione = (status_bytes[0] & 0x01) != 0
|
|
pronti = (status_bytes[1] & 0x80) != 0
|
|
|
|
results["db100"] = {
|
|
"status": {"acquisizione": acquisizione, "pronti": pronti},
|
|
"params": params,
|
|
"strings": strings,
|
|
"duration": dur
|
|
}
|
|
|
|
# DB901
|
|
data_901, dur_901 = self.read_area("db901")
|
|
if data_901:
|
|
cfg = self.config["MEMORIES"]["db901"]
|
|
params = {}
|
|
for p in cfg["parameters"]:
|
|
if p["type"] == "dint":
|
|
params[p["name"]] = get_dint(data_901, p["offset"])
|
|
elif p["type"] == "bool":
|
|
params[p["name"]] = (data_901[p["offset"]] & (1 << p["bit"])) != 0
|
|
|
|
results["db901"] = {
|
|
"params": params,
|
|
"duration": dur_901
|
|
}
|
|
|
|
# DB900
|
|
data_900, dur_900 = self.read_area("db900")
|
|
if data_900:
|
|
cfg = self.config["MEMORIES"]["db900"]
|
|
num_misure = get_dint(data_900, 0)
|
|
valori = []
|
|
for i in range(21):
|
|
offset = 4 + (i * 4)
|
|
if offset + 4 <= cfg["size"]:
|
|
valori.append(get_dint(data_900, offset))
|
|
else:
|
|
valori.append(0)
|
|
results["db900"] = {
|
|
"num_misure": num_misure,
|
|
"misure": valori,
|
|
"duration": dur_900
|
|
}
|
|
|
|
# DB101
|
|
data_101, dur_101 = self.read_area("db101")
|
|
if data_101:
|
|
# Per DB101 usiamo unpack come prima perché è un array omogeneo
|
|
values = struct.unpack('>40f', data_101)
|
|
results["db101"] = {
|
|
"values": values,
|
|
"duration": dur_101
|
|
}
|
|
|
|
return results
|
|
|
|
def run_loop(self):
|
|
"""Loop di aggiornamento dati in background."""
|
|
while True:
|
|
if not self.connected:
|
|
if not self.connect():
|
|
time.sleep(self.reconnect_delay)
|
|
continue
|
|
|
|
data = self.fetch_all_data()
|
|
with self.lock:
|
|
self.last_data = data
|
|
|
|
time.sleep(0.5)
|
|
|
|
class WebServer:
|
|
def __init__(self, client):
|
|
self.client = client
|
|
|
|
def get_data(self):
|
|
with self.client.lock:
|
|
return self.client.last_data
|
|
|
|
def run(self):
|
|
# Qui andrà implementata la logica del server web (es. Flask)
|
|
# Per ora simuliamo con un log periodico
|
|
print("Server Web pronto (Placeholder).")
|
|
while True:
|
|
data = self.get_data()
|
|
if data:
|
|
print(f"Dati aggiornati: DB100.x_live={data['db100']['params'].get('x_live')}")
|
|
time.sleep(1)
|
|
|
|
if __name__ == "__main__":
|
|
client = SiemensClient("config.json")
|
|
|
|
# Avviamo il thread di aggiornamento dati
|
|
thread = threading.Thread(target=client.run_loop, daemon=True)
|
|
thread.start()
|
|
|
|
# Avviamo il server web (placeholder)
|
|
server = WebServer(client)
|
|
server.run()
|